| New file |
| | |
| | | using UnityEngine; |
| | | using System.IO; |
| | | |
| | | #if UNITY_XCODE_API_BUILD |
| | | namespace UnityEditor.iOS.Xcode |
| | | #else |
| | | namespace UnityEditor.iOS.Xcode.Custom |
| | | #endif |
| | | { |
| | | public class XClass : System.IDisposable |
| | | { |
| | | private string filePath; |
| | | |
| | | public XClass(string fPath) |
| | | { |
| | | filePath = fPath; |
| | | if (!System.IO.File.Exists(filePath)) |
| | | { |
| | | Debug.LogError(filePath + "路径下文件不存在"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | //在指定代码上方插入 |
| | | //public void WriteAbove(string below,string text) |
| | | //{ |
| | | // StreamReader streamReader = new StreamReader(filePath); |
| | | // string text_all = streamReader.ReadToEnd(); |
| | | // streamReader.Close(); |
| | | |
| | | // int beginIndex = text_all.IndexOf(below, System.StringComparison.CurrentCulture); |
| | | // if (beginIndex == -1) |
| | | // { |
| | | // Debug.LogError(filePath + "中没有找到标致" + below); |
| | | // return; |
| | | // } |
| | | |
| | | // int endIndex = text_all.LastIndexOf("\n", beginIndex + below.Length, System.StringComparison.CurrentCulture); |
| | | |
| | | // text_all = text_all.Substring(0, endIndex) + "\n" + text + "\n" + text_all.Substring(endIndex); |
| | | |
| | | // StreamWriter streamWriter = new StreamWriter(filePath); |
| | | // streamWriter.Write(text_all); |
| | | // streamWriter.Close(); |
| | | //} |
| | | |
| | | //在指定代码下方插入 |
| | | public void WriteBelow(string below, string text) |
| | | { |
| | | StreamReader streamReader = new StreamReader(filePath); |
| | | string text_all = streamReader.ReadToEnd(); |
| | | streamReader.Close(); |
| | | |
| | | int beginIndex = text_all.IndexOf(below, System.StringComparison.CurrentCulture); |
| | | if (beginIndex == -1) |
| | | { |
| | | Debug.LogError(filePath + "中没有找到标致" + below); |
| | | return; |
| | | } |
| | | |
| | | int endIndex = text_all.LastIndexOf("\n", beginIndex + below.Length, System.StringComparison.CurrentCulture); |
| | | |
| | | text_all = text_all.Substring(0, endIndex) + "\n" + text + "\n" + text_all.Substring(endIndex); |
| | | |
| | | StreamWriter streamWriter = new StreamWriter(filePath); |
| | | streamWriter.Write(text_all); |
| | | streamWriter.Close(); |
| | | } |
| | | |
| | | public void Replace(string below, string newText) |
| | | { |
| | | StreamReader streamReader = new StreamReader(filePath); |
| | | string text_all = streamReader.ReadToEnd(); |
| | | streamReader.Close(); |
| | | |
| | | int beginIndex = text_all.IndexOf(below, System.StringComparison.CurrentCulture); |
| | | if (beginIndex == -1) |
| | | { |
| | | Debug.LogError(filePath + "中没有找到标致" + below); |
| | | return; |
| | | } |
| | | |
| | | text_all = text_all.Replace(below, newText); |
| | | StreamWriter streamWriter = new StreamWriter(filePath); |
| | | streamWriter.Write(text_all); |
| | | streamWriter.Close(); |
| | | } |
| | | |
| | | public void Dispose() |
| | | { |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: c889d5f646f816a40a87c3cec871181b |
| | | timeCreated: 1525994900 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using DG.Tweening.Plugins.Core.PathCore; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.IO; |
| | | using UnityEditor; |
| | | using UnityEditor.iOS.Xcode.Custom; |
| | | using UnityEngine; |
| | | |
| | | public class XCodeProjectMod |
| | | { |
| | | private const string CODE_SIGN_DEVELOPER = ""; |
| | | private const string CODE_SIGN_DISTRIBUTION = ""; |
| | | private const string PROVISIONING_DEVELOPER = ""; |
| | | private const string PROVISIONING_DISTRIBUTION = ""; |
| | | private const string TEAM = ""; |
| | | |
| | | [UnityEditor.Callbacks.PostProcessBuild(999)] |
| | | public static void OnPostprocessBuild(BuildTarget buildTarget, string path) |
| | | { |
| | | if (buildTarget != BuildTarget.iOS) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | |
| | | DoPBXProject(path); |
| | | BuildPlist(path); |
| | | ModifyFile(path); |
| | | |
| | | |
| | | |
| | | } |
| | | |
| | | static void DoPBXProject(string path) |
| | | { |
| | | string _projectPath = PBXProject.GetPBXProjectPath(path); |
| | | |
| | | PBXProject project = new PBXProject(); |
| | | project.ReadFromString(File.ReadAllText(_projectPath)); |
| | | |
| | | string targetGUID = project.TargetGuidByName(PBXProject.GetUnityTargetName()); |
| | | string fwTargetGUID = project.TargetGuidByName("UnityFramework"); |
| | | |
| | | // BuildSetting里的相关设置 |
| | | project.SetBuildProperty(project.ProjectGuid(), "ENABLE_BITCODE", "NO"); |
| | | project.SetBuildProperty(targetGUID, "ENABLE_BITCODE", "NO"); |
| | | project.SetBuildProperty(targetGUID, "DEVELOPMENT_TEAM", TEAM); |
| | | |
| | | project.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-ObjC"); |
| | | project.AddBuildProperty(targetGUID, "GCC_GENERATE_DEBUGGING_SYMBOLS", "NO"); |
| | | |
| | | //wkwebview |
| | | project.AddFrameworkToProject(fwTargetGUID, "WebKit.framework", true); |
| | | |
| | | project.AddFrameworkToProject(fwTargetGUID, "SystemConfiguration.framework", true); |
| | | project.AddFrameworkToProject(fwTargetGUID, "Security.framework", true); |
| | | project.AddFrameworkToProject(fwTargetGUID, "JavaScriptCore.framework", true); |
| | | project.AddFrameworkToProject(fwTargetGUID, "Accelerate.framework", true); |
| | | |
| | | // fimpfunSDK.framework 添加到工程并设置为 Embed & Sign |
| | | string fimpfunFrameworkPath = path + "/Frameworks/fimpfunSDK.framework"; |
| | | if (Directory.Exists(fimpfunFrameworkPath)) |
| | | { |
| | | string fileGuid = project.AddFile(fimpfunFrameworkPath, "Frameworks/fimpfunSDK.framework", PBXSourceTree.Source); |
| | | project.AddFileToBuild(fwTargetGUID, fileGuid); |
| | | // Embed & Sign: 添加到 main target 的 Embed Frameworks |
| | | project.AddFileToBuild(targetGUID, fileGuid); |
| | | } |
| | | |
| | | // 处理 库文件 |
| | | project.AddFileToBuild(fwTargetGUID, project.AddFile("/usr/lib/libz.tbd", "Frameworks/libz.tbd", PBXSourceTree.Sdk)); |
| | | project.AddFileToBuild(fwTargetGUID, project.AddFile("/usr/lib/libsqlite3.tbd", "Frameworks/libsqlite3.tbd", PBXSourceTree.Sdk)); |
| | | project.AddFileToBuild(fwTargetGUID, project.AddFile("/usr/lib/libc++.tbd", "Frameworks/libc++.tbd", PBXSourceTree.Sdk)); |
| | | project.AddFileToBuild(fwTargetGUID, project.AddFile("/usr/lib/libiconv.tbd", "Frameworks/libiconv.tbd", PBXSourceTree.Sdk)); |
| | | project.AddFileToBuild(fwTargetGUID, project.AddFile("/usr/lib/libresolv.tbd", "Frameworks/libresolv.tbd", PBXSourceTree.Sdk)); |
| | | |
| | | //UnityFramework |
| | | project.AddBuildProperty(fwTargetGUID, "OTHER_LDFLAGS", "-ObjC"); |
| | | |
| | | // 添加 GoogleService-Info.plist 和 funshine.plist 到工程 |
| | | string iosPluginPath = path + "/Libraries/Plugins/iOS/Channel"; |
| | | string[] configPlists = { "GoogleService-Info.plist", "funshine.plist" }; |
| | | foreach (var plistName in configPlists) |
| | | { |
| | | string srcPath = iosPluginPath + "/" + plistName; |
| | | if (File.Exists(srcPath)) |
| | | { |
| | | string destPath = path + "/" + plistName; |
| | | File.Copy(srcPath, destPath, true); |
| | | string fileGuid = project.AddFile(plistName, plistName, PBXSourceTree.Source); |
| | | project.AddFileToBuild(targetGUID, fileGuid); |
| | | } |
| | | } |
| | | |
| | | File.WriteAllText(_projectPath, project.WriteToString()); |
| | | } |
| | | |
| | | private static void BuildPlist(string path) |
| | | { |
| | | string _plistPath = path + "/Info.plist"; |
| | | |
| | | PlistDocument _plist = new PlistDocument(); |
| | | _plist.ReadFromString(File.ReadAllText(_plistPath)); |
| | | |
| | | PlistElementDict _rootDict = _plist.root; |
| | | PlistElementDict _nsAppTransportSecurity = _rootDict.CreateDict("NSAppTransportSecurity"); |
| | | _nsAppTransportSecurity.SetBoolean("NSAllowsArbitraryLoads", true); |
| | | |
| | | _rootDict.SetString("NSPhotoLibraryAddUsageDescription", "You must grant the photo album permission to take screenshots and save data for users"); |
| | | _rootDict.SetString("NSCameraUsageDescription", "Please allow application permission access, otherwise the function cannot be started"); |
| | | _rootDict.SetString("NSUserTrackingUsageDescription", "This guide will display personalized ads specifically for you"); |
| | | |
| | | // Adjust (funshine.plist: kAdjustAppToken) |
| | | _rootDict.SetString("AdjustAppToken", "nbevz727gy68"); |
| | | |
| | | // Facebook (funshine.plist: kFacebookAppID / kFacebookAppSecret) |
| | | _rootDict.SetString("FacebookAppID", "1445747540428317"); |
| | | _rootDict.SetString("FacebookClientToken", ""); |
| | | _rootDict.SetString("FacebookDisplayName", "Legend of the Sword Master"); |
| | | |
| | | _rootDict.SetBoolean("FacebookAutoLogAppEventsEnabled", true); |
| | | _rootDict.SetBoolean("FacebookAdvertiserIDCollectionEnabled", true); |
| | | |
| | | PlistElementArray nsURLTypes = _rootDict.CreateArray("CFBundleURLTypes"); |
| | | var dict0 = nsURLTypes.AddDict(); |
| | | dict0.SetString("CFBundleTypeRole", "Editor"); |
| | | dict0.SetString("CFBundleURLName", "fb"); |
| | | var schemesArray = dict0.CreateArray("CFBundleURLSchemes"); |
| | | schemesArray.AddString("fb1445747540428317"); |
| | | |
| | | var array1 = _rootDict.CreateArray("LSApplicationQueriesSchemes"); |
| | | array1.AddString("fbapi"); |
| | | array1.AddString("fbapi20130214"); |
| | | array1.AddString("fbapi20130410"); |
| | | array1.AddString("fbapi20130702"); |
| | | array1.AddString("fbapi20131010"); |
| | | array1.AddString("fbapi20131219"); |
| | | array1.AddString("fbapi20140410"); |
| | | array1.AddString("fbapi20140116"); |
| | | array1.AddString("fbapi20150313"); |
| | | array1.AddString("fbapi20150629"); |
| | | array1.AddString("fbapi20160328"); |
| | | array1.AddString("fbauth"); |
| | | array1.AddString("fb-messenger-share-api"); |
| | | array1.AddString("fbauth2"); |
| | | array1.AddString("fbshareextension"); |
| | | |
| | | File.WriteAllText(_plistPath, _plist.WriteToString()); |
| | | } |
| | | |
| | | |
| | | |
| | | private static void ModifyFile(string projectPath) |
| | | { |
| | | //修改UnityAppController.mm 文件 |
| | | var _xclass = new XClass(projectPath + "/Classes/UnityAppController.mm"); |
| | | //引入fimpfunSDK |
| | | _xclass.WriteBelow("#include \"PluginBase/AppDelegateListener.h\"", "#import \"fimpfunSDK.h\""); |
| | | //unity交互调用的方法 |
| | | string newCode = "\n" + |
| | | "extern \"C\" void IOSUniyMessageHandle(const char* jsonString) {\n" + |
| | | " [GetAppController().fimpfunBridge HandleGameMessage:[NSString stringWithUTF8String:jsonString]];\n" + |
| | | "}"; |
| | | _xclass.WriteBelow("extern \"C\" ScreenOrientation UnityCurrentOrientation() { return GetAppController().unityView.contentOrientation; }", newCode); |
| | | //初始化fimpfunSDK |
| | | newCode = "\n" + |
| | | " _fimpfunBridge = [[FimpfunBridge alloc] init];\n [_fimpfunBridge YngameSDKInit:application didFinishLaunchingWithOptions:launchOptions];"; |
| | | _xclass.WriteBelow("::printf(\"-> applicationDidFinishLaunching()\\n\");", newCode); |
| | | |
| | | |
| | | // fimpfunSDK 生命周期 |
| | | _xclass.WriteBelow("- (BOOL)application:(UIApplication*)app openURL:(NSURL*)url options:(NSDictionary<NSString*, id>*)options\n{", "\n [_fimpfunBridge application:app openURL:url options:options];"); |
| | | _xclass.WriteBelow("::printf(\"-> applicationDidBecomeActive()\\n\");", "[_fimpfunBridge applicationDidBecomeActive:application];"); |
| | | |
| | | |
| | | //修改UnityAppController.h 文件 |
| | | _xclass = new XClass(projectPath + "/Classes/UnityAppController.h"); |
| | | _xclass.WriteBelow("@class DisplayConnection;", "@class FimpfunBridge;"); |
| | | _xclass.WriteBelow("DisplayConnection* _mainDisplay;", "FimpfunBridge* _fimpfunBridge;"); |
| | | _xclass.WriteBelow("@property (readonly, copy, nonatomic) DisplayConnection* mainDisplay;", |
| | | "@property (readonly, copy, nonatomic) FimpfunBridge* fimpfunBridge;"); |
| | | } |
| | | |
| | | |
| | | |
| | | private static string GetBuildPath() |
| | | { |
| | | string _buildPath = Application.dataPath + "/../IOS_BUILD"; |
| | | |
| | | if (!Directory.Exists(_buildPath)) |
| | | { |
| | | Directory.CreateDirectory(_buildPath); |
| | | } |
| | | |
| | | return new DirectoryInfo(_buildPath).FullName; |
| | | } |
| | | |
| | | public static string[] GetBuildLevels() |
| | | { |
| | | List<string> _sceneNames = new List<string>(); |
| | | foreach (var _scene in EditorBuildSettings.scenes) |
| | | { |
| | | if (_scene == null) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | if (_scene.enabled) |
| | | { |
| | | _sceneNames.Add(_scene.path); |
| | | } |
| | | } |
| | | return _sceneNames.ToArray(); |
| | | } |
| | | |
| | | private static bool s_IsAppend = false; |
| | | |
| | | public static void BuildIOSProject_Append() |
| | | { |
| | | string _buildPath = GetBuildPath(); |
| | | UnityEngine.Debug.Log(_buildPath); |
| | | if (string.IsNullOrEmpty(_buildPath)) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | s_IsAppend = true; |
| | | PlayerSettings.SetScriptingBackend(BuildTargetGroup.iOS, ScriptingImplementation.IL2CPP); |
| | | BuildPipeline.BuildPlayer(ClientPackage.baseLevels, _buildPath, BuildTarget.iOS, BuildOptions.AcceptExternalModificationsToPlayer); |
| | | } |
| | | |
| | | public static void BuildIOSProject_Replace() |
| | | { |
| | | s_IsAppend = false; |
| | | PlayerSettings.SetScriptingBackend(BuildTargetGroup.iOS, ScriptingImplementation.IL2CPP); |
| | | BuildPipeline.BuildPlayer(ClientPackage.baseLevels, GetBuildPath(), BuildTarget.iOS, BuildOptions.None); |
| | | } |
| | | |
| | | [MenuItem("Build/ipa")] |
| | | public static void BuildIPA() |
| | | { |
| | | var p = new System.Diagnostics.Process(); |
| | | p.StartInfo.FileName = "osascript"; |
| | | p.StartInfo.Arguments = string.Format("-e 'tell application \"Terminal\" \n activate \n do script \"cd {0} && sh {1} {2}\" \n end tell'", |
| | | Application.dataPath + "/../", |
| | | "buildipa.sh", |
| | | GetBuildPath()); |
| | | p.StartInfo.UseShellExecute = false; |
| | | p.StartInfo.RedirectStandardOutput = false; |
| | | p.Start(); |
| | | p.WaitForExit(); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 2ada0c62dcf8b4e68ba03e4c30192a44 |
| | | timeCreated: 1525492415 |
| | | licenseType: Free |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 73a45294a3fb2f24596771f440beb9a6 |
| | | folderAsset: yes |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | DefaultImporter: |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.IO; |
| | | using System.Linq; |
| | | |
| | | #if UNITY_XCODE_API_BUILD |
| | | namespace UnityEditor.iOS.Xcode |
| | | #else |
| | | namespace UnityEditor.iOS.Xcode.Custom |
| | | #endif |
| | | { |
| | | internal class DeviceTypeRequirement |
| | | { |
| | | public static readonly string Key = "idiom"; |
| | | public static readonly string Any = "universal"; |
| | | public static readonly string iPhone = "iphone"; |
| | | public static readonly string iPad = "ipad"; |
| | | public static readonly string Mac = "mac"; |
| | | public static readonly string iWatch = "watch"; |
| | | } |
| | | |
| | | internal class MemoryRequirement |
| | | { |
| | | public static readonly string Key = "memory"; |
| | | public static readonly string Any = ""; |
| | | public static readonly string Mem1GB = "1GB"; |
| | | public static readonly string Mem2GB = "2GB"; |
| | | } |
| | | |
| | | internal class GraphicsRequirement |
| | | { |
| | | public static readonly string Key = "graphics-feature-set"; |
| | | public static readonly string Any = ""; |
| | | public static readonly string Metal1v2 = "metal1v2"; |
| | | public static readonly string Metal2v2 = "metal2v2"; |
| | | } |
| | | |
| | | // only used for image sets |
| | | internal class SizeClassRequirement |
| | | { |
| | | public static readonly string HeightKey = "height-class"; |
| | | public static readonly string WidthKey = "width-class"; |
| | | public static readonly string Any = ""; |
| | | public static readonly string Compact = "compact"; |
| | | public static readonly string Regular = "regular"; |
| | | } |
| | | |
| | | // only used for image sets |
| | | internal class ScaleRequirement |
| | | { |
| | | public static readonly string Key = "scale"; |
| | | public static readonly string Any = ""; // vector image |
| | | public static readonly string X1 = "1x"; |
| | | public static readonly string X2 = "2x"; |
| | | public static readonly string X3 = "3x"; |
| | | } |
| | | |
| | | internal class DeviceRequirement |
| | | { |
| | | internal Dictionary<string, string> values = new Dictionary<string, string>(); |
| | | |
| | | public DeviceRequirement AddDevice(string device) |
| | | { |
| | | AddCustom(DeviceTypeRequirement.Key, device); |
| | | return this; |
| | | } |
| | | |
| | | public DeviceRequirement AddMemory(string memory) |
| | | { |
| | | AddCustom(MemoryRequirement.Key, memory); |
| | | return this; |
| | | } |
| | | |
| | | public DeviceRequirement AddGraphics(string graphics) |
| | | { |
| | | AddCustom(GraphicsRequirement.Key, graphics); |
| | | return this; |
| | | } |
| | | |
| | | public DeviceRequirement AddWidthClass(string sizeClass) |
| | | { |
| | | AddCustom(SizeClassRequirement.WidthKey, sizeClass); |
| | | return this; |
| | | } |
| | | |
| | | public DeviceRequirement AddHeightClass(string sizeClass) |
| | | { |
| | | AddCustom(SizeClassRequirement.HeightKey, sizeClass); |
| | | return this; |
| | | } |
| | | |
| | | public DeviceRequirement AddScale(string scale) |
| | | { |
| | | AddCustom(ScaleRequirement.Key, scale); |
| | | return this; |
| | | } |
| | | |
| | | public DeviceRequirement AddCustom(string key, string value) |
| | | { |
| | | if (values.ContainsKey(key)) |
| | | values.Remove(key); |
| | | values.Add(key, value); |
| | | return this; |
| | | } |
| | | |
| | | public DeviceRequirement() |
| | | { |
| | | values.Add("idiom", DeviceTypeRequirement.Any); |
| | | } |
| | | } |
| | | |
| | | internal class AssetCatalog |
| | | { |
| | | AssetFolder m_Root; |
| | | |
| | | public string path { get { return m_Root.path; } } |
| | | public AssetFolder root { get { return m_Root; } } |
| | | |
| | | public AssetCatalog(string path, string authorId) |
| | | { |
| | | if (Path.GetExtension(path) != ".xcassets") |
| | | throw new Exception("Asset catalogs must have xcassets extension"); |
| | | m_Root = new AssetFolder(path, null, authorId); |
| | | } |
| | | |
| | | AssetFolder OpenFolderForResource(string relativePath) |
| | | { |
| | | var pathItems = PBXPath.Split(relativePath).ToList(); |
| | | |
| | | // remove path filename |
| | | pathItems.RemoveAt(pathItems.Count - 1); |
| | | |
| | | AssetFolder folder = root; |
| | | foreach (var pathItem in pathItems) |
| | | folder = folder.OpenFolder(pathItem); |
| | | return folder; |
| | | } |
| | | |
| | | // Checks if a dataset at the given path exists and returns it if it does. |
| | | // Otherwise, creates a new dataset. Parent folders are created if needed. |
| | | // Note: the path is filesystem path, not logical asset name formed |
| | | // only from names of the folders that have "provides namespace" attribute. |
| | | // If you want to put certain resources in folders with namespace, first |
| | | // manually create the folders and then set the providesNamespace attribute. |
| | | // OpenNamespacedFolder may help to do this. |
| | | public AssetDataSet OpenDataSet(string relativePath) |
| | | { |
| | | var folder = OpenFolderForResource(relativePath); |
| | | return folder.OpenDataSet(Path.GetFileName(relativePath)); |
| | | } |
| | | |
| | | public AssetImageSet OpenImageSet(string relativePath) |
| | | { |
| | | var folder = OpenFolderForResource(relativePath); |
| | | return folder.OpenImageSet(Path.GetFileName(relativePath)); |
| | | } |
| | | |
| | | public AssetImageStack OpenImageStack(string relativePath) |
| | | { |
| | | var folder = OpenFolderForResource(relativePath); |
| | | return folder.OpenImageStack(Path.GetFileName(relativePath)); |
| | | } |
| | | |
| | | public AssetBrandAssetGroup OpenBrandAssetGroup(string relativePath) |
| | | { |
| | | var folder = OpenFolderForResource(relativePath); |
| | | return folder.OpenBrandAssetGroup(Path.GetFileName(relativePath)); |
| | | } |
| | | |
| | | // Checks if a folder with given path exists and returns it if it does. |
| | | // Otherwise, creates a new folder. Parent folders are created if needed. |
| | | public AssetFolder OpenFolder(string relativePath) |
| | | { |
| | | if (relativePath == null) |
| | | return root; |
| | | var pathItems = PBXPath.Split(relativePath); |
| | | if (pathItems.Length == 0) |
| | | return root; |
| | | AssetFolder folder = root; |
| | | foreach (var pathItem in pathItems) |
| | | folder = folder.OpenFolder(pathItem); |
| | | return folder; |
| | | } |
| | | |
| | | // Creates a directory structure with "provides namespace" attribute. |
| | | // First, retrieves or creates the directory at relativeBasePath, creating parent |
| | | // directories if needed. Effectively calls OpenFolder(relativeBasePath). |
| | | // Then, relative to this directory, creates namespacePath directories with "provides |
| | | // namespace" attribute set. Fails if the attribute can't be set. |
| | | public AssetFolder OpenNamespacedFolder(string relativeBasePath, string namespacePath) |
| | | { |
| | | var folder = OpenFolder(relativeBasePath); |
| | | var pathItems = PBXPath.Split(namespacePath); |
| | | foreach (var pathItem in pathItems) |
| | | { |
| | | folder = folder.OpenFolder(pathItem); |
| | | folder.providesNamespace = true; |
| | | } |
| | | return folder; |
| | | } |
| | | |
| | | public void Write() |
| | | { |
| | | Write(null); |
| | | } |
| | | |
| | | public void Write(List<string> warnings) |
| | | { |
| | | m_Root.Write(warnings); |
| | | } |
| | | } |
| | | |
| | | internal abstract class AssetCatalogItem |
| | | { |
| | | public readonly string name; |
| | | public readonly string authorId; |
| | | public string path { get { return m_Path; } } |
| | | |
| | | protected Dictionary<string, string> m_Properties = new Dictionary<string, string>(); |
| | | |
| | | protected string m_Path; |
| | | |
| | | public AssetCatalogItem(string name, string authorId) |
| | | { |
| | | if (name != null && name.Contains("/")) |
| | | throw new Exception("Asset catalog item must not have slashes in name"); |
| | | this.name = name; |
| | | this.authorId = authorId; |
| | | } |
| | | |
| | | protected JsonElementDict WriteInfoToJson(JsonDocument doc) |
| | | { |
| | | var info = doc.root.CreateDict("info"); |
| | | info.SetInteger("version", 1); |
| | | info.SetString("author", authorId); |
| | | return info; |
| | | } |
| | | |
| | | public abstract void Write(List<string> warnings); |
| | | } |
| | | |
| | | internal class AssetFolder : AssetCatalogItem |
| | | { |
| | | List<AssetCatalogItem> m_Items = new List<AssetCatalogItem>(); |
| | | bool m_ProvidesNamespace = false; |
| | | |
| | | public bool providesNamespace |
| | | { |
| | | get { return m_ProvidesNamespace; } |
| | | set { |
| | | if (m_Items.Count > 0 && value != m_ProvidesNamespace) |
| | | throw new Exception("Asset folder namespace providing status can't be "+ |
| | | "changed after items have been added"); |
| | | m_ProvidesNamespace = value; |
| | | } |
| | | } |
| | | |
| | | internal AssetFolder(string parentPath, string name, string authorId) : base(name, authorId) |
| | | { |
| | | if (name != null) |
| | | m_Path = Path.Combine(parentPath, name); |
| | | else |
| | | m_Path = parentPath; |
| | | } |
| | | |
| | | // Checks if a folder with given name exists and returns it if it does. |
| | | // Otherwise, creates a new folder. |
| | | public AssetFolder OpenFolder(string name) |
| | | { |
| | | var item = GetChild(name); |
| | | if (item != null) |
| | | { |
| | | if (item is AssetFolder) |
| | | return item as AssetFolder; |
| | | throw new Exception("The given path is already occupied with an asset"); |
| | | } |
| | | |
| | | var folder = new AssetFolder(m_Path, name, authorId); |
| | | m_Items.Add(folder); |
| | | return folder; |
| | | } |
| | | |
| | | T GetExistingItemWithType<T>(string name) where T : class |
| | | { |
| | | var item = GetChild(name); |
| | | if (item != null) |
| | | { |
| | | if (item is T) |
| | | return item as T; |
| | | throw new Exception("The given path is already occupied with an asset"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | // Checks if a dataset with given name exists and returns it if it does. |
| | | // Otherwise, creates a new data set. |
| | | public AssetDataSet OpenDataSet(string name) |
| | | { |
| | | var item = GetExistingItemWithType<AssetDataSet>(name); |
| | | if (item != null) |
| | | return item; |
| | | |
| | | var dataset = new AssetDataSet(m_Path, name, authorId); |
| | | m_Items.Add(dataset); |
| | | return dataset; |
| | | } |
| | | |
| | | // Checks if an imageset with given name exists and returns it if it does. |
| | | // Otherwise, creates a new image set. |
| | | public AssetImageSet OpenImageSet(string name) |
| | | { |
| | | var item = GetExistingItemWithType<AssetImageSet>(name); |
| | | if (item != null) |
| | | return item; |
| | | |
| | | var imageset = new AssetImageSet(m_Path, name, authorId); |
| | | m_Items.Add(imageset); |
| | | return imageset; |
| | | } |
| | | |
| | | // Checks if a image stack with given name exists and returns it if it does. |
| | | // Otherwise, creates a new image stack. |
| | | public AssetImageStack OpenImageStack(string name) |
| | | { |
| | | var item = GetExistingItemWithType<AssetImageStack>(name); |
| | | if (item != null) |
| | | return item; |
| | | |
| | | var imageStack = new AssetImageStack(m_Path, name, authorId); |
| | | m_Items.Add(imageStack); |
| | | return imageStack; |
| | | } |
| | | |
| | | // Checks if a brand asset with given name exists and returns it if it does. |
| | | // Otherwise, creates a new brand asset. |
| | | public AssetBrandAssetGroup OpenBrandAssetGroup(string name) |
| | | { |
| | | var item = GetExistingItemWithType<AssetBrandAssetGroup>(name); |
| | | if (item != null) |
| | | return item; |
| | | |
| | | var brandAsset = new AssetBrandAssetGroup(m_Path, name, authorId); |
| | | m_Items.Add(brandAsset); |
| | | return brandAsset; |
| | | } |
| | | |
| | | // Returns the requested item or null if not found |
| | | public AssetCatalogItem GetChild(string name) |
| | | { |
| | | foreach (var item in m_Items) |
| | | { |
| | | if (item.name == name) |
| | | return item; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | void WriteJson() |
| | | { |
| | | if (!providesNamespace) |
| | | return; // json is optional when namespace is not provided |
| | | |
| | | var doc = new JsonDocument(); |
| | | |
| | | WriteInfoToJson(doc); |
| | | |
| | | var props = doc.root.CreateDict("properties"); |
| | | props.SetBoolean("provides-namespace", providesNamespace); |
| | | doc.WriteToFile(Path.Combine(m_Path, "Contents.json")); |
| | | } |
| | | |
| | | public override void Write(List<string> warnings) |
| | | { |
| | | if (Directory.Exists(m_Path)) |
| | | Directory.Delete(m_Path, true); // ensure we start from clean state |
| | | Directory.CreateDirectory(m_Path); |
| | | WriteJson(); |
| | | |
| | | foreach (var item in m_Items) |
| | | item.Write(warnings); |
| | | } |
| | | } |
| | | |
| | | abstract class AssetCatalogItemWithVariants : AssetCatalogItem |
| | | { |
| | | protected List<VariantData> m_Variants = new List<VariantData>(); |
| | | protected List<string> m_ODRTags = new List<string>(); |
| | | |
| | | protected AssetCatalogItemWithVariants(string name, string authorId) : |
| | | base(name, authorId) |
| | | { |
| | | } |
| | | |
| | | protected class VariantData |
| | | { |
| | | public DeviceRequirement requirement; |
| | | public string path; |
| | | |
| | | public VariantData(DeviceRequirement requirement, string path) |
| | | { |
| | | this.requirement = requirement; |
| | | this.path = path; |
| | | } |
| | | } |
| | | |
| | | public bool HasVariant(DeviceRequirement requirement) |
| | | { |
| | | foreach (var item in m_Variants) |
| | | { |
| | | if (item.requirement.values == requirement.values) |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | public void AddOnDemandResourceTag(string tag) |
| | | { |
| | | if (!m_ODRTags.Contains(tag)) |
| | | m_ODRTags.Add(tag); |
| | | } |
| | | |
| | | protected void AddVariant(VariantData newItem) |
| | | { |
| | | foreach (var item in m_Variants) |
| | | { |
| | | if (item.requirement.values == newItem.requirement.values) |
| | | throw new Exception("The given requirement has been already added"); |
| | | if (Path.GetFileName(item.path) == Path.GetFileName(path)) |
| | | throw new Exception("Two items within the same set must not have the same file name"); |
| | | } |
| | | if (Path.GetFileName(newItem.path) == "Contents.json") |
| | | throw new Exception("The file name must not be equal to Contents.json"); |
| | | m_Variants.Add(newItem); |
| | | } |
| | | |
| | | protected void WriteODRTagsToJson(JsonElementDict info) |
| | | { |
| | | if (m_ODRTags.Count > 0) |
| | | { |
| | | var tags = info.CreateArray("on-demand-resource-tags"); |
| | | foreach (var tag in m_ODRTags) |
| | | tags.AddString(tag); |
| | | } |
| | | } |
| | | |
| | | protected void WriteRequirementsToJson(JsonElementDict item, DeviceRequirement req) |
| | | { |
| | | foreach (var kv in req.values) |
| | | { |
| | | if (kv.Value != null && kv.Value != "") |
| | | item.SetString(kv.Key, kv.Value); |
| | | } |
| | | } |
| | | |
| | | // Returns the filename of the resulting file |
| | | protected string CopyFileToSet(string path, HashSet<string> existingFilenames, List<string> warnings) |
| | | { |
| | | var filename = Path.GetFileName(path); |
| | | if (!File.Exists(path)) |
| | | { |
| | | if (warnings != null) |
| | | warnings.Add("File not found: " + path); |
| | | } |
| | | else |
| | | { |
| | | // ensure that we don't create duplicate filenames |
| | | int index = 1; |
| | | string filenameBase = Path.GetFileNameWithoutExtension(filename); |
| | | string extension = Path.GetExtension(filename); |
| | | |
| | | while (existingFilenames.Contains(filename)) |
| | | { |
| | | filename = String.Format("{0}-{1}{2}", filenameBase, index, extension); |
| | | index++; |
| | | } |
| | | existingFilenames.Add(filename); |
| | | File.Copy(path, Path.Combine(m_Path, filename)); |
| | | } |
| | | return filename; |
| | | } |
| | | } |
| | | |
| | | internal class AssetDataSet : AssetCatalogItemWithVariants |
| | | { |
| | | class DataSetVariant : VariantData |
| | | { |
| | | public string id; |
| | | |
| | | public DataSetVariant(DeviceRequirement requirement, string path, string id) : base(requirement, path) |
| | | { |
| | | this.id = id; |
| | | } |
| | | } |
| | | |
| | | internal AssetDataSet(string parentPath, string name, string authorId) : base(name, authorId) |
| | | { |
| | | m_Path = Path.Combine(parentPath, name + ".dataset"); |
| | | } |
| | | |
| | | // an exception is thrown is two equivalent requirements are added. |
| | | // The same asset dataset must not have paths with equivalent filenames. |
| | | // The identifier allows to identify which data variant is actually loaded (use |
| | | // the typeIdentifer property of the NSDataAsset that was created from the data set) |
| | | public void AddVariant(DeviceRequirement requirement, string path, string typeIdentifier) |
| | | { |
| | | foreach (DataSetVariant item in m_Variants) |
| | | { |
| | | if (item.id != null && typeIdentifier != null && item.id == typeIdentifier) |
| | | throw new Exception("Two items within the same dataset must not have the same id"); |
| | | } |
| | | AddVariant(new DataSetVariant(requirement, path, typeIdentifier)); |
| | | } |
| | | |
| | | public override void Write(List<string> warnings) |
| | | { |
| | | Directory.CreateDirectory(m_Path); |
| | | |
| | | var doc = new JsonDocument(); |
| | | |
| | | var info = WriteInfoToJson(doc); |
| | | WriteODRTagsToJson(info); |
| | | |
| | | var data = doc.root.CreateArray("data"); |
| | | |
| | | var existingFilenames = new HashSet<string>(); |
| | | |
| | | foreach (DataSetVariant item in m_Variants) |
| | | { |
| | | var filename = CopyFileToSet(item.path, existingFilenames, warnings); |
| | | |
| | | var docItem = data.AddDict(); |
| | | docItem.SetString("filename", filename); |
| | | WriteRequirementsToJson(docItem, item.requirement); |
| | | if (item.id != null) |
| | | docItem.SetString("universal-type-identifier", item.id); |
| | | } |
| | | doc.WriteToFile(Path.Combine(m_Path, "Contents.json")); |
| | | } |
| | | } |
| | | |
| | | internal class ImageAlignment |
| | | { |
| | | public int left = 0, right = 0, top = 0, bottom = 0; |
| | | } |
| | | |
| | | internal class ImageResizing |
| | | { |
| | | public enum SlicingType |
| | | { |
| | | Horizontal, |
| | | Vertical, |
| | | HorizontalAndVertical |
| | | } |
| | | |
| | | public enum ResizeMode |
| | | { |
| | | Stretch, |
| | | Tile |
| | | } |
| | | |
| | | public SlicingType type = SlicingType.HorizontalAndVertical; |
| | | public int left = 0; // only valid for horizontal slicing |
| | | public int right = 0; // only valid for horizontal slicing |
| | | public int top = 0; // only valid for vertical slicing |
| | | public int bottom = 0; // only valid for vertical slicing |
| | | public ResizeMode centerResizeMode = ResizeMode.Stretch; |
| | | public int centerWidth = 0; // only valid for vertical slicing |
| | | public int centerHeight = 0; // only valid for horizontal slicing |
| | | } |
| | | |
| | | // TODO: rendering intent property |
| | | internal class AssetImageSet : AssetCatalogItemWithVariants |
| | | { |
| | | internal AssetImageSet(string assetCatalogPath, string name, string authorId) : base(name, authorId) |
| | | { |
| | | m_Path = Path.Combine(assetCatalogPath, name + ".imageset"); |
| | | } |
| | | |
| | | class ImageSetVariant : VariantData |
| | | { |
| | | public ImageAlignment alignment = null; |
| | | public ImageResizing resizing = null; |
| | | |
| | | public ImageSetVariant(DeviceRequirement requirement, string path) : base(requirement, path) |
| | | { |
| | | } |
| | | } |
| | | |
| | | public void AddVariant(DeviceRequirement requirement, string path) |
| | | { |
| | | AddVariant(new ImageSetVariant(requirement, path)); |
| | | } |
| | | |
| | | public void AddVariant(DeviceRequirement requirement, string path, ImageAlignment alignment, ImageResizing resizing) |
| | | { |
| | | var imageset = new ImageSetVariant(requirement, path); |
| | | imageset.alignment = alignment; |
| | | imageset.resizing = resizing; |
| | | AddVariant(imageset); |
| | | } |
| | | |
| | | void WriteAlignmentToJson(JsonElementDict item, ImageAlignment alignment) |
| | | { |
| | | var docAlignment = item.CreateDict("alignment-insets"); |
| | | docAlignment.SetInteger("top", alignment.top); |
| | | docAlignment.SetInteger("bottom", alignment.bottom); |
| | | docAlignment.SetInteger("left", alignment.left); |
| | | docAlignment.SetInteger("right", alignment.right); |
| | | } |
| | | |
| | | static string GetSlicingMode(ImageResizing.SlicingType mode) |
| | | { |
| | | switch (mode) |
| | | { |
| | | case ImageResizing.SlicingType.Horizontal: return "3-part-horizontal"; |
| | | case ImageResizing.SlicingType.Vertical: return "3-part-vertical"; |
| | | case ImageResizing.SlicingType.HorizontalAndVertical: return "9-part"; |
| | | } |
| | | return ""; |
| | | } |
| | | |
| | | static string GetCenterResizeMode(ImageResizing.ResizeMode mode) |
| | | { |
| | | switch (mode) |
| | | { |
| | | case ImageResizing.ResizeMode.Stretch: return "stretch"; |
| | | case ImageResizing.ResizeMode.Tile: return "tile"; |
| | | } |
| | | return ""; |
| | | } |
| | | |
| | | void WriteResizingToJson(JsonElementDict item, ImageResizing resizing) |
| | | { |
| | | var docResizing = item.CreateDict("resizing"); |
| | | docResizing.SetString("mode", GetSlicingMode(resizing.type)); |
| | | |
| | | var docCenter = docResizing.CreateDict("center"); |
| | | docCenter.SetString("mode", GetCenterResizeMode(resizing.centerResizeMode)); |
| | | docCenter.SetInteger("width", resizing.centerWidth); |
| | | docCenter.SetInteger("height", resizing.centerHeight); |
| | | |
| | | var docInsets = docResizing.CreateDict("cap-insets"); |
| | | docInsets.SetInteger("top", resizing.top); |
| | | docInsets.SetInteger("bottom", resizing.bottom); |
| | | docInsets.SetInteger("left", resizing.left); |
| | | docInsets.SetInteger("right", resizing.right); |
| | | } |
| | | |
| | | public override void Write(List<string> warnings) |
| | | { |
| | | Directory.CreateDirectory(m_Path); |
| | | var doc = new JsonDocument(); |
| | | var info = WriteInfoToJson(doc); |
| | | WriteODRTagsToJson(info); |
| | | |
| | | var images = doc.root.CreateArray("images"); |
| | | |
| | | var existingFilenames = new HashSet<string>(); |
| | | |
| | | foreach (ImageSetVariant item in m_Variants) |
| | | { |
| | | var filename = CopyFileToSet(item.path, existingFilenames, warnings); |
| | | |
| | | var docItem = images.AddDict(); |
| | | docItem.SetString("filename", filename); |
| | | WriteRequirementsToJson(docItem, item.requirement); |
| | | if (item.alignment != null) |
| | | WriteAlignmentToJson(docItem, item.alignment); |
| | | if (item.resizing != null) |
| | | WriteResizingToJson(docItem, item.resizing); |
| | | } |
| | | doc.WriteToFile(Path.Combine(m_Path, "Contents.json")); |
| | | } |
| | | } |
| | | |
| | | /* A stack layer may either contain an image set or reference another imageset |
| | | */ |
| | | class AssetImageStackLayer : AssetCatalogItem |
| | | { |
| | | internal AssetImageStackLayer(string assetCatalogPath, string name, string authorId) : base(name, authorId) |
| | | { |
| | | m_Path = Path.Combine(assetCatalogPath, name + ".imagestacklayer"); |
| | | m_Imageset = new AssetImageSet(m_Path, "Content", authorId); |
| | | } |
| | | |
| | | AssetImageSet m_Imageset = null; |
| | | string m_ReferencedName = null; |
| | | |
| | | public void SetReference(string name) |
| | | { |
| | | m_Imageset = null; |
| | | m_ReferencedName = name; |
| | | } |
| | | |
| | | public string ReferencedName() |
| | | { |
| | | return m_ReferencedName; |
| | | } |
| | | |
| | | public AssetImageSet GetImageSet() |
| | | { |
| | | return m_Imageset; |
| | | } |
| | | |
| | | public override void Write(List<string> warnings) |
| | | { |
| | | Directory.CreateDirectory(m_Path); |
| | | var doc = new JsonDocument(); |
| | | WriteInfoToJson(doc); |
| | | |
| | | if (m_ReferencedName != null) |
| | | { |
| | | var props = doc.root.CreateDict("properties"); |
| | | var reference = props.CreateDict("content-reference"); |
| | | reference.SetString("type", "image-set"); |
| | | reference.SetString("name", m_ReferencedName); |
| | | reference.SetString("matching-style", "fully-qualified-name"); |
| | | } |
| | | if (m_Imageset != null) |
| | | m_Imageset.Write(warnings); |
| | | |
| | | doc.WriteToFile(Path.Combine(m_Path, "Contents.json")); |
| | | } |
| | | } |
| | | |
| | | class AssetImageStack : AssetCatalogItem |
| | | { |
| | | List<AssetImageStackLayer> m_Layers = new List<AssetImageStackLayer>(); |
| | | |
| | | internal AssetImageStack(string assetCatalogPath, string name, string authorId) : base(name, authorId) |
| | | { |
| | | m_Path = Path.Combine(assetCatalogPath, name + ".imagestack"); |
| | | } |
| | | |
| | | public AssetImageStackLayer AddLayer(string name) |
| | | { |
| | | foreach (var layer in m_Layers) |
| | | { |
| | | if (layer.name == name) |
| | | throw new Exception("A layer with given name already exists"); |
| | | } |
| | | var newLayer = new AssetImageStackLayer(m_Path, name, authorId); |
| | | m_Layers.Add(newLayer); |
| | | return newLayer; |
| | | } |
| | | |
| | | public override void Write(List<string> warnings) |
| | | { |
| | | Directory.CreateDirectory(m_Path); |
| | | var doc = new JsonDocument(); |
| | | WriteInfoToJson(doc); |
| | | |
| | | var docLayers = doc.root.CreateArray("layers"); |
| | | foreach (var layer in m_Layers) |
| | | { |
| | | layer.Write(warnings); |
| | | |
| | | var docLayer = docLayers.AddDict(); |
| | | docLayer.SetString("filename", Path.GetFileName(layer.path)); |
| | | } |
| | | doc.WriteToFile(Path.Combine(m_Path, "Contents.json")); |
| | | } |
| | | } |
| | | |
| | | class AssetBrandAssetGroup : AssetCatalogItem |
| | | { |
| | | class AssetBrandAssetItem |
| | | { |
| | | internal string idiom = null; |
| | | internal string role = null; |
| | | internal int width, height; |
| | | internal AssetCatalogItem item = null; |
| | | |
| | | } |
| | | |
| | | List<AssetBrandAssetItem> m_Items = new List<AssetBrandAssetItem>(); |
| | | |
| | | internal AssetBrandAssetGroup(string assetCatalogPath, string name, string authorId) : base(name, authorId) |
| | | { |
| | | m_Path = Path.Combine(assetCatalogPath, name + ".brandassets"); |
| | | } |
| | | |
| | | void AddItem(AssetCatalogItem item, string idiom, string role, int width, int height) |
| | | { |
| | | foreach (var it in m_Items) |
| | | { |
| | | if (it.item.name == item.name) |
| | | throw new Exception("An item with given name already exists"); |
| | | } |
| | | var newItem = new AssetBrandAssetItem(); |
| | | newItem.item = item; |
| | | newItem.idiom = idiom; |
| | | newItem.role = role; |
| | | newItem.width = width; |
| | | newItem.height = height; |
| | | m_Items.Add(newItem); |
| | | } |
| | | |
| | | public AssetImageSet OpenImageSet(string name, string idiom, string role, int width, int height) |
| | | { |
| | | var newItem = new AssetImageSet(m_Path, name, authorId); |
| | | AddItem(newItem, idiom, role, width, height); |
| | | return newItem; |
| | | } |
| | | |
| | | public AssetImageStack OpenImageStack(string name, string idiom, string role, int width, int height) |
| | | { |
| | | var newItem = new AssetImageStack(m_Path, name, authorId); |
| | | AddItem(newItem, idiom, role, width, height); |
| | | return newItem; |
| | | } |
| | | |
| | | public override void Write(List<string> warnings) |
| | | { |
| | | Directory.CreateDirectory(m_Path); |
| | | var doc = new JsonDocument(); |
| | | WriteInfoToJson(doc); |
| | | |
| | | var docAssets = doc.root.CreateArray("assets"); |
| | | foreach (var item in m_Items) |
| | | { |
| | | var docAsset = docAssets.AddDict(); |
| | | docAsset.SetString("size", String.Format("{0}x{1}", item.width, item.height)); |
| | | docAsset.SetString("idiom", item.idiom); |
| | | docAsset.SetString("role", item.role); |
| | | docAsset.SetString("filename", Path.GetFileName(item.item.path)); |
| | | |
| | | item.item.Write(warnings); |
| | | } |
| | | doc.WriteToFile(Path.Combine(m_Path, "Contents.json")); |
| | | } |
| | | } |
| | | |
| | | } // namespace UnityEditor.iOS.Xcode |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 289542669aaa84c879c9038d33c31421 |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.IO; |
| | | using System.Text; |
| | | using System.Linq; |
| | | |
| | | #if UNITY_XCODE_API_BUILD |
| | | namespace UnityEditor.iOS.Xcode |
| | | #else |
| | | namespace UnityEditor.iOS.Xcode.Custom |
| | | #endif |
| | | { |
| | | internal class JsonElement |
| | | { |
| | | protected JsonElement() {} |
| | | |
| | | // convenience methods |
| | | public string AsString() { return ((JsonElementString)this).value; } |
| | | public int AsInteger() { return ((JsonElementInteger)this).value; } |
| | | public bool AsBoolean() { return ((JsonElementBoolean)this).value; } |
| | | public JsonElementArray AsArray() { return (JsonElementArray)this; } |
| | | public JsonElementDict AsDict() { return (JsonElementDict)this; } |
| | | |
| | | public JsonElement this[string key] |
| | | { |
| | | get { return AsDict()[key]; } |
| | | set { AsDict()[key] = value; } |
| | | } |
| | | } |
| | | |
| | | internal class JsonElementString : JsonElement |
| | | { |
| | | public JsonElementString(string v) { value = v; } |
| | | |
| | | public string value; |
| | | } |
| | | |
| | | internal class JsonElementInteger : JsonElement |
| | | { |
| | | public JsonElementInteger(int v) { value = v; } |
| | | |
| | | public int value; |
| | | } |
| | | |
| | | internal class JsonElementBoolean : JsonElement |
| | | { |
| | | public JsonElementBoolean(bool v) { value = v; } |
| | | |
| | | public bool value; |
| | | } |
| | | |
| | | internal class JsonElementDict : JsonElement |
| | | { |
| | | public JsonElementDict() : base() {} |
| | | |
| | | private SortedDictionary<string, JsonElement> m_PrivateValue = new SortedDictionary<string, JsonElement>(); |
| | | public IDictionary<string, JsonElement> values { get { return m_PrivateValue; }} |
| | | |
| | | new public JsonElement this[string key] |
| | | { |
| | | get { |
| | | if (values.ContainsKey(key)) |
| | | return values[key]; |
| | | return null; |
| | | } |
| | | set { this.values[key] = value; } |
| | | } |
| | | |
| | | public bool Contains(string key) |
| | | { |
| | | return values.ContainsKey(key); |
| | | } |
| | | |
| | | public void Remove(string key) |
| | | { |
| | | values.Remove(key); |
| | | } |
| | | |
| | | // convenience methods |
| | | public void SetInteger(string key, int val) |
| | | { |
| | | values[key] = new JsonElementInteger(val); |
| | | } |
| | | |
| | | public void SetString(string key, string val) |
| | | { |
| | | values[key] = new JsonElementString(val); |
| | | } |
| | | |
| | | public void SetBoolean(string key, bool val) |
| | | { |
| | | values[key] = new JsonElementBoolean(val); |
| | | } |
| | | |
| | | public JsonElementArray CreateArray(string key) |
| | | { |
| | | var v = new JsonElementArray(); |
| | | values[key] = v; |
| | | return v; |
| | | } |
| | | |
| | | public JsonElementDict CreateDict(string key) |
| | | { |
| | | var v = new JsonElementDict(); |
| | | values[key] = v; |
| | | return v; |
| | | } |
| | | } |
| | | |
| | | internal class JsonElementArray : JsonElement |
| | | { |
| | | public JsonElementArray() : base() {} |
| | | public List<JsonElement> values = new List<JsonElement>(); |
| | | |
| | | // convenience methods |
| | | public void AddString(string val) |
| | | { |
| | | values.Add(new JsonElementString(val)); |
| | | } |
| | | |
| | | public void AddInteger(int val) |
| | | { |
| | | values.Add(new JsonElementInteger(val)); |
| | | } |
| | | |
| | | public void AddBoolean(bool val) |
| | | { |
| | | values.Add(new JsonElementBoolean(val)); |
| | | } |
| | | |
| | | public JsonElementArray AddArray() |
| | | { |
| | | var v = new JsonElementArray(); |
| | | values.Add(v); |
| | | return v; |
| | | } |
| | | |
| | | public JsonElementDict AddDict() |
| | | { |
| | | var v = new JsonElementDict(); |
| | | values.Add(v); |
| | | return v; |
| | | } |
| | | } |
| | | |
| | | internal class JsonDocument |
| | | { |
| | | public JsonElementDict root; |
| | | public string indentString = " "; |
| | | |
| | | public JsonDocument() |
| | | { |
| | | root = new JsonElementDict(); |
| | | } |
| | | |
| | | void AppendIndent(StringBuilder sb, int indent) |
| | | { |
| | | for (int i = 0; i < indent; ++i) |
| | | sb.Append(indentString); |
| | | } |
| | | |
| | | void WriteString(StringBuilder sb, string str) |
| | | { |
| | | // TODO: escape |
| | | sb.Append('"'); |
| | | sb.Append(str); |
| | | sb.Append('"'); |
| | | } |
| | | |
| | | void WriteBoolean(StringBuilder sb, bool value) |
| | | { |
| | | sb.Append(value ? "true" : "false"); |
| | | } |
| | | |
| | | void WriteInteger(StringBuilder sb, int value) |
| | | { |
| | | sb.Append(value.ToString()); |
| | | } |
| | | |
| | | void WriteDictKeyValue(StringBuilder sb, string key, JsonElement value, int indent) |
| | | { |
| | | sb.Append("\n"); |
| | | AppendIndent(sb, indent); |
| | | WriteString(sb, key); |
| | | sb.Append(" : "); |
| | | if (value is JsonElementString) |
| | | WriteString(sb, value.AsString()); |
| | | else if (value is JsonElementInteger) |
| | | WriteInteger(sb, value.AsInteger()); |
| | | else if (value is JsonElementBoolean) |
| | | WriteBoolean(sb, value.AsBoolean()); |
| | | else if (value is JsonElementDict) |
| | | WriteDict(sb, value.AsDict(), indent); |
| | | else if (value is JsonElementArray) |
| | | WriteArray(sb, value.AsArray(), indent); |
| | | } |
| | | |
| | | void WriteDict(StringBuilder sb, JsonElementDict el, int indent) |
| | | { |
| | | sb.Append("{"); |
| | | bool hasElement = false; |
| | | foreach (var key in el.values.Keys) |
| | | { |
| | | if (hasElement) |
| | | sb.Append(","); // trailing commas not supported |
| | | WriteDictKeyValue(sb, key, el[key], indent+1); |
| | | hasElement = true; |
| | | } |
| | | sb.Append("\n"); |
| | | AppendIndent(sb, indent); |
| | | sb.Append("}"); |
| | | } |
| | | |
| | | void WriteArray(StringBuilder sb, JsonElementArray el, int indent) |
| | | { |
| | | sb.Append("["); |
| | | bool hasElement = false; |
| | | foreach (var value in el.values) |
| | | { |
| | | if (hasElement) |
| | | sb.Append(","); // trailing commas not supported |
| | | sb.Append("\n"); |
| | | AppendIndent(sb, indent+1); |
| | | |
| | | if (value is JsonElementString) |
| | | WriteString(sb, value.AsString()); |
| | | else if (value is JsonElementInteger) |
| | | WriteInteger(sb, value.AsInteger()); |
| | | else if (value is JsonElementBoolean) |
| | | WriteBoolean(sb, value.AsBoolean()); |
| | | else if (value is JsonElementDict) |
| | | WriteDict(sb, value.AsDict(), indent+1); |
| | | else if (value is JsonElementArray) |
| | | WriteArray(sb, value.AsArray(), indent+1); |
| | | hasElement = true; |
| | | } |
| | | sb.Append("\n"); |
| | | AppendIndent(sb, indent); |
| | | sb.Append("]"); |
| | | } |
| | | |
| | | public void WriteToFile(string path) |
| | | { |
| | | File.WriteAllText(path, WriteToString()); |
| | | } |
| | | |
| | | public void WriteToStream(TextWriter tw) |
| | | { |
| | | tw.Write(WriteToString()); |
| | | } |
| | | |
| | | public string WriteToString() |
| | | { |
| | | var sb = new StringBuilder(); |
| | | WriteDict(sb, root, 0); |
| | | return sb.ToString(); |
| | | } |
| | | } |
| | | |
| | | |
| | | } // namespace UnityEditor.iOS.Xcode |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 8b01b8f01ef4145c59d40e635319bc61 |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: eb4e0a32726f245999ab29a8988993f7 |
| | | folderAsset: yes |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | DefaultImporter: |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System.Collections.Generic; |
| | | using System.Collections; |
| | | using System; |
| | | |
| | | #if UNITY_XCODE_API_BUILD |
| | | namespace UnityEditor.iOS.Xcode.PBX |
| | | #else |
| | | namespace UnityEditor.iOS.Xcode.Custom.PBX |
| | | #endif |
| | | { |
| | | |
| | | class PBXElement |
| | | { |
| | | protected PBXElement() {} |
| | | |
| | | // convenience methods |
| | | public string AsString() { return ((PBXElementString)this).value; } |
| | | public PBXElementArray AsArray() { return (PBXElementArray)this; } |
| | | public PBXElementDict AsDict() { return (PBXElementDict)this; } |
| | | |
| | | public PBXElement this[string key] |
| | | { |
| | | get { return AsDict()[key]; } |
| | | set { AsDict()[key] = value; } |
| | | } |
| | | } |
| | | |
| | | class PBXElementString : PBXElement |
| | | { |
| | | public PBXElementString(string v) { value = v; } |
| | | |
| | | public string value; |
| | | } |
| | | |
| | | class PBXElementDict : PBXElement |
| | | { |
| | | public PBXElementDict() : base() {} |
| | | |
| | | private Dictionary<string, PBXElement> m_PrivateValue = new Dictionary<string, PBXElement>(); |
| | | public IDictionary<string, PBXElement> values { get { return m_PrivateValue; }} |
| | | |
| | | new public PBXElement this[string key] |
| | | { |
| | | get { |
| | | if (values.ContainsKey(key)) |
| | | return values[key]; |
| | | return null; |
| | | } |
| | | set { this.values[key] = value; } |
| | | } |
| | | |
| | | public bool Contains(string key) |
| | | { |
| | | return values.ContainsKey(key); |
| | | } |
| | | |
| | | public void Remove(string key) |
| | | { |
| | | values.Remove(key); |
| | | } |
| | | |
| | | public void SetString(string key, string val) |
| | | { |
| | | values[key] = new PBXElementString(val); |
| | | } |
| | | |
| | | public PBXElementArray CreateArray(string key) |
| | | { |
| | | var v = new PBXElementArray(); |
| | | values[key] = v; |
| | | return v; |
| | | } |
| | | |
| | | public PBXElementDict CreateDict(string key) |
| | | { |
| | | var v = new PBXElementDict(); |
| | | values[key] = v; |
| | | return v; |
| | | } |
| | | } |
| | | |
| | | class PBXElementArray : PBXElement |
| | | { |
| | | public PBXElementArray() : base() {} |
| | | public List<PBXElement> values = new List<PBXElement>(); |
| | | |
| | | // convenience methods |
| | | public void AddString(string val) |
| | | { |
| | | values.Add(new PBXElementString(val)); |
| | | } |
| | | |
| | | public PBXElementArray AddArray() |
| | | { |
| | | var v = new PBXElementArray(); |
| | | values.Add(v); |
| | | return v; |
| | | } |
| | | |
| | | public PBXElementDict AddDict() |
| | | { |
| | | var v = new PBXElementDict(); |
| | | values.Add(v); |
| | | return v; |
| | | } |
| | | } |
| | | |
| | | } // namespace UnityEditor.iOS.Xcode |
| | | |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 1f71bea45bdd54cf5ab4817a71172d92 |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System.Collections.Generic; |
| | | using System.Text.RegularExpressions; |
| | | using System.IO; |
| | | using System.Linq; |
| | | using System; |
| | | |
| | | #if UNITY_XCODE_API_BUILD |
| | | namespace UnityEditor.iOS.Xcode.PBX |
| | | #else |
| | | namespace UnityEditor.iOS.Xcode.Custom.PBX |
| | | #endif |
| | | { |
| | | enum TokenType |
| | | { |
| | | EOF, |
| | | Invalid, |
| | | String, |
| | | QuotedString, |
| | | Comment, |
| | | |
| | | Semicolon, // ; |
| | | Comma, // , |
| | | Eq, // = |
| | | LParen, // ( |
| | | RParen, // ) |
| | | LBrace, // { |
| | | RBrace, // } |
| | | } |
| | | |
| | | class Token |
| | | { |
| | | public TokenType type; |
| | | |
| | | // the line of the input stream the token starts in (0-based) |
| | | public int line; |
| | | |
| | | // start and past-the-end positions of the token in the input stream |
| | | public int begin, end; |
| | | } |
| | | |
| | | class TokenList : List<Token> |
| | | { |
| | | } |
| | | |
| | | class Lexer |
| | | { |
| | | string text; |
| | | int pos; |
| | | int length; |
| | | int line; |
| | | |
| | | public static TokenList Tokenize(string text) |
| | | { |
| | | var lexer = new Lexer(); |
| | | lexer.SetText(text); |
| | | return lexer.ScanAll(); |
| | | } |
| | | |
| | | public void SetText(string text) |
| | | { |
| | | this.text = text + " "; // to prevent out-of-bounds access during look ahead |
| | | pos = 0; |
| | | length = text.Length; |
| | | line = 0; |
| | | } |
| | | |
| | | public TokenList ScanAll() |
| | | { |
| | | var tokens = new TokenList(); |
| | | |
| | | while (true) |
| | | { |
| | | var tok = new Token(); |
| | | ScanOne(tok); |
| | | tokens.Add(tok); |
| | | if (tok.type == TokenType.EOF) |
| | | break; |
| | | } |
| | | return tokens; |
| | | } |
| | | |
| | | void UpdateNewlineStats(char ch) |
| | | { |
| | | if (ch == '\n') |
| | | line++; |
| | | } |
| | | |
| | | // tokens list is modified in the case when we add BrokenLine token and need to remove already |
| | | // added tokens for the current line |
| | | void ScanOne(Token tok) |
| | | { |
| | | while (true) |
| | | { |
| | | while (pos < length && Char.IsWhiteSpace(text[pos])) |
| | | { |
| | | UpdateNewlineStats(text[pos]); |
| | | pos++; |
| | | } |
| | | |
| | | if (pos >= length) |
| | | { |
| | | tok.type = TokenType.EOF; |
| | | break; |
| | | } |
| | | |
| | | char ch = text[pos]; |
| | | char ch2 = text[pos+1]; |
| | | |
| | | if (ch == '\"') |
| | | ScanQuotedString(tok); |
| | | else if (ch == '/' && ch2 == '*') |
| | | ScanMultilineComment(tok); |
| | | else if (ch == '/' && ch2 == '/') |
| | | ScanComment(tok); |
| | | else if (IsOperator(ch)) |
| | | ScanOperator(tok); |
| | | else |
| | | ScanString(tok); // be more robust and accept whatever is left |
| | | return; |
| | | } |
| | | } |
| | | |
| | | void ScanString(Token tok) |
| | | { |
| | | tok.type = TokenType.String; |
| | | tok.begin = pos; |
| | | while (pos < length) |
| | | { |
| | | char ch = text[pos]; |
| | | char ch2 = text[pos+1]; |
| | | |
| | | if (Char.IsWhiteSpace(ch)) |
| | | break; |
| | | else if (ch == '\"') |
| | | break; |
| | | else if (ch == '/' && ch2 == '*') |
| | | break; |
| | | else if (ch == '/' && ch2 == '/') |
| | | break; |
| | | else if (IsOperator(ch)) |
| | | break; |
| | | pos++; |
| | | } |
| | | tok.end = pos; |
| | | tok.line = line; |
| | | } |
| | | |
| | | void ScanQuotedString(Token tok) |
| | | { |
| | | tok.type = TokenType.QuotedString; |
| | | tok.begin = pos; |
| | | pos++; |
| | | |
| | | while (pos < length) |
| | | { |
| | | // ignore escaped quotes |
| | | if (text[pos] == '\\' && text[pos+1] == '\"') |
| | | { |
| | | pos += 2; |
| | | continue; |
| | | } |
| | | |
| | | // note that we close unclosed quotes |
| | | if (text[pos] == '\"') |
| | | break; |
| | | |
| | | UpdateNewlineStats(text[pos]); |
| | | pos++; |
| | | } |
| | | pos++; |
| | | tok.end = pos; |
| | | tok.line = line; |
| | | } |
| | | |
| | | void ScanMultilineComment(Token tok) |
| | | { |
| | | tok.type = TokenType.Comment; |
| | | tok.begin = pos; |
| | | pos += 2; |
| | | |
| | | while (pos < length) |
| | | { |
| | | if (text[pos] == '*' && text[pos+1] == '/') |
| | | break; |
| | | |
| | | // we support multiline comments |
| | | UpdateNewlineStats(text[pos]); |
| | | pos++; |
| | | } |
| | | pos += 2; |
| | | tok.end = pos; |
| | | tok.line = line; |
| | | } |
| | | |
| | | void ScanComment(Token tok) |
| | | { |
| | | tok.type = TokenType.Comment; |
| | | tok.begin = pos; |
| | | pos += 2; |
| | | |
| | | while (pos < length) |
| | | { |
| | | if (text[pos] == '\n') |
| | | break; |
| | | pos++; |
| | | } |
| | | UpdateNewlineStats(text[pos]); |
| | | pos++; |
| | | tok.end = pos; |
| | | tok.line = line; |
| | | } |
| | | |
| | | bool IsOperator(char ch) |
| | | { |
| | | if (ch == ';' || ch == ',' || ch == '=' || ch == '(' || ch == ')' || ch == '{' || ch == '}') |
| | | return true; |
| | | return false; |
| | | } |
| | | |
| | | void ScanOperator(Token tok) |
| | | { |
| | | switch (text[pos]) |
| | | { |
| | | case ';': ScanOperatorSpecific(tok, TokenType.Semicolon); return; |
| | | case ',': ScanOperatorSpecific(tok, TokenType.Comma); return; |
| | | case '=': ScanOperatorSpecific(tok, TokenType.Eq); return; |
| | | case '(': ScanOperatorSpecific(tok, TokenType.LParen); return; |
| | | case ')': ScanOperatorSpecific(tok, TokenType.RParen); return; |
| | | case '{': ScanOperatorSpecific(tok, TokenType.LBrace); return; |
| | | case '}': ScanOperatorSpecific(tok, TokenType.RBrace); return; |
| | | default: return; |
| | | } |
| | | } |
| | | |
| | | void ScanOperatorSpecific(Token tok, TokenType type) |
| | | { |
| | | tok.type = type; |
| | | tok.begin = pos; |
| | | pos++; |
| | | tok.end = pos; |
| | | tok.line = line; |
| | | } |
| | | } |
| | | |
| | | |
| | | } // namespace UnityEditor.iOS.Xcode |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 7b36df8cd70f54a35b10d501fc2d9e4d |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System.Collections.Generic; |
| | | using System.Collections; |
| | | using System.Text.RegularExpressions; |
| | | using System.IO; |
| | | using System.Linq; |
| | | using System; |
| | | |
| | | #if UNITY_XCODE_API_BUILD |
| | | namespace UnityEditor.iOS.Xcode.PBX |
| | | #else |
| | | namespace UnityEditor.iOS.Xcode.Custom.PBX |
| | | #endif |
| | | { |
| | | internal class PBXObjectData |
| | | { |
| | | public string guid; |
| | | protected PBXElementDict m_Properties = new PBXElementDict(); |
| | | |
| | | internal void SetPropertiesWhenSerializing(PBXElementDict props) |
| | | { |
| | | m_Properties = props; |
| | | } |
| | | |
| | | internal PBXElementDict GetPropertiesWhenSerializing() |
| | | { |
| | | return m_Properties; |
| | | } |
| | | |
| | | /* Returns the internal properties dictionary which the user may manipulate directly. |
| | | If any of the properties are modified, UpdateVars() must be called before any other |
| | | operation affecting the given Xcode document is executed. |
| | | */ |
| | | internal PBXElementDict GetPropertiesRaw() |
| | | { |
| | | UpdateProps(); |
| | | return m_Properties; |
| | | } |
| | | |
| | | // returns null if it does not exist |
| | | protected string GetPropertyString(string name) |
| | | { |
| | | var prop = m_Properties[name]; |
| | | if (prop == null) |
| | | return null; |
| | | |
| | | return prop.AsString(); |
| | | } |
| | | |
| | | protected void SetPropertyString(string name, string value) |
| | | { |
| | | if (value == null) |
| | | m_Properties.Remove(name); |
| | | else |
| | | m_Properties.SetString(name, value); |
| | | } |
| | | |
| | | protected List<string> GetPropertyList(string name) |
| | | { |
| | | var prop = m_Properties[name]; |
| | | if (prop == null) |
| | | return null; |
| | | |
| | | var list = new List<string>(); |
| | | foreach (var el in prop.AsArray().values) |
| | | list.Add(el.AsString()); |
| | | return list; |
| | | } |
| | | |
| | | protected void SetPropertyList(string name, List<string> value) |
| | | { |
| | | if (value == null) |
| | | m_Properties.Remove(name); |
| | | else |
| | | { |
| | | var array = m_Properties.CreateArray(name); |
| | | foreach (string val in value) |
| | | array.AddString(val); |
| | | } |
| | | } |
| | | |
| | | private static PropertyCommentChecker checkerData = new PropertyCommentChecker(); |
| | | internal virtual PropertyCommentChecker checker { get { return checkerData; } } |
| | | internal virtual bool shouldCompact { get { return false; } } |
| | | |
| | | public virtual void UpdateProps() {} // Updates the props from cached variables |
| | | public virtual void UpdateVars() {} // Updates the cached variables from underlying props |
| | | } |
| | | |
| | | internal class PBXBuildFileData : PBXObjectData |
| | | { |
| | | public string fileRef; |
| | | public string compileFlags; |
| | | public bool weak; |
| | | public bool codeSignOnCopy; |
| | | public bool removeHeadersOnCopy; |
| | | public List<string> assetTags; |
| | | |
| | | private static PropertyCommentChecker checkerData = new PropertyCommentChecker(new string[]{ |
| | | "fileRef/*" |
| | | }); |
| | | internal override PropertyCommentChecker checker { get { return checkerData; } } |
| | | internal override bool shouldCompact { get { return true; } } |
| | | |
| | | public static PBXBuildFileData CreateFromFile(string fileRefGUID, bool weak, |
| | | string compileFlags) |
| | | { |
| | | PBXBuildFileData buildFile = new PBXBuildFileData(); |
| | | buildFile.guid = PBXGUID.Generate(); |
| | | buildFile.SetPropertyString("isa", "PBXBuildFile"); |
| | | buildFile.fileRef = fileRefGUID; |
| | | buildFile.compileFlags = compileFlags; |
| | | buildFile.weak = weak; |
| | | buildFile.codeSignOnCopy = false; |
| | | buildFile.removeHeadersOnCopy = false; |
| | | buildFile.assetTags = new List<string>(); |
| | | return buildFile; |
| | | } |
| | | |
| | | PBXElementDict UpdatePropsAttribute(PBXElementDict settings, bool value, string attributeName) |
| | | { |
| | | PBXElementArray attrs = null; |
| | | if (value) |
| | | { |
| | | if (settings == null) |
| | | settings = m_Properties.CreateDict("settings"); |
| | | } |
| | | if (settings != null && settings.Contains("ATTRIBUTES")) |
| | | attrs = settings["ATTRIBUTES"].AsArray(); |
| | | |
| | | if (value) |
| | | { |
| | | if (attrs == null) |
| | | attrs = settings.CreateArray("ATTRIBUTES"); |
| | | |
| | | bool exists = attrs.values.Any(attr => |
| | | { |
| | | return attr is PBXElementString && attr.AsString() == attributeName; |
| | | }); |
| | | |
| | | if (!exists) |
| | | attrs.AddString(attributeName); |
| | | } |
| | | else |
| | | { |
| | | if (attrs != null) |
| | | { |
| | | attrs.values.RemoveAll(el => (el is PBXElementString && el.AsString() == attributeName)); |
| | | if (attrs.values.Count == 0) |
| | | settings.Remove("ATTRIBUTES"); |
| | | } |
| | | } |
| | | return settings; |
| | | } |
| | | |
| | | public override void UpdateProps() |
| | | { |
| | | SetPropertyString("fileRef", fileRef); |
| | | |
| | | PBXElementDict settings = null; |
| | | if (m_Properties.Contains("settings")) |
| | | settings = m_Properties["settings"].AsDict(); |
| | | |
| | | if (compileFlags != null && compileFlags != "") |
| | | { |
| | | if (settings == null) |
| | | settings = m_Properties.CreateDict("settings"); |
| | | settings.SetString("COMPILER_FLAGS", compileFlags); |
| | | } |
| | | else |
| | | { |
| | | if (settings != null) |
| | | settings.Remove("COMPILER_FLAGS"); |
| | | } |
| | | |
| | | settings = UpdatePropsAttribute(settings, weak, "Weak"); |
| | | settings = UpdatePropsAttribute(settings, codeSignOnCopy, "CodeSignOnCopy"); |
| | | settings = UpdatePropsAttribute(settings, removeHeadersOnCopy, "RemoveHeadersOnCopy"); |
| | | |
| | | if (assetTags.Count > 0) |
| | | { |
| | | if (settings == null) |
| | | settings = m_Properties.CreateDict("settings"); |
| | | var tagsArray = settings.CreateArray("ASSET_TAGS"); |
| | | foreach (string tag in assetTags) |
| | | tagsArray.AddString(tag); |
| | | } |
| | | else |
| | | { |
| | | if (settings != null) |
| | | settings.Remove("ASSET_TAGS"); |
| | | } |
| | | |
| | | if (settings != null && settings.values.Count == 0) |
| | | m_Properties.Remove("settings"); |
| | | } |
| | | |
| | | public override void UpdateVars() |
| | | { |
| | | fileRef = GetPropertyString("fileRef"); |
| | | compileFlags = null; |
| | | weak = false; |
| | | assetTags = new List<string>(); |
| | | if (m_Properties.Contains("settings")) |
| | | { |
| | | var dict = m_Properties["settings"].AsDict(); |
| | | if (dict.Contains("COMPILER_FLAGS")) |
| | | compileFlags = dict["COMPILER_FLAGS"].AsString(); |
| | | |
| | | if (dict.Contains("ATTRIBUTES")) |
| | | { |
| | | var attrs = dict["ATTRIBUTES"].AsArray(); |
| | | foreach (var value in attrs.values) |
| | | { |
| | | if (value is PBXElementString && value.AsString() == "Weak") |
| | | weak = true; |
| | | if (value is PBXElementString && value.AsString() == "CodeSignOnCopy") |
| | | codeSignOnCopy = true; |
| | | if (value is PBXElementString && value.AsString() == "RemoveHeadersOnCopy") |
| | | removeHeadersOnCopy = true; |
| | | } |
| | | } |
| | | if (dict.Contains("ASSET_TAGS")) |
| | | { |
| | | var tags = dict["ASSET_TAGS"].AsArray(); |
| | | foreach (var value in tags.values) |
| | | assetTags.Add(value.AsString()); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | internal class PBXFileReferenceData : PBXObjectData |
| | | { |
| | | string m_Path = null; |
| | | string m_ExplicitFileType = null; |
| | | string m_LastKnownFileType = null; |
| | | |
| | | public string path |
| | | { |
| | | get { return m_Path; } |
| | | set { m_ExplicitFileType = null; m_LastKnownFileType = null; m_Path = value; } |
| | | } |
| | | |
| | | public string name; |
| | | public PBXSourceTree tree; |
| | | public bool isFolderReference |
| | | { |
| | | get { return m_LastKnownFileType != null && m_LastKnownFileType == "folder"; } |
| | | } |
| | | |
| | | internal override bool shouldCompact { get { return true; } } |
| | | |
| | | public static PBXFileReferenceData CreateFromFile(string path, string projectFileName, |
| | | PBXSourceTree tree) |
| | | { |
| | | string guid = PBXGUID.Generate(); |
| | | |
| | | PBXFileReferenceData fileRef = new PBXFileReferenceData(); |
| | | fileRef.SetPropertyString("isa", "PBXFileReference"); |
| | | fileRef.guid = guid; |
| | | fileRef.path = path; |
| | | fileRef.name = projectFileName; |
| | | fileRef.tree = tree; |
| | | return fileRef; |
| | | } |
| | | |
| | | public static PBXFileReferenceData CreateFromFolderReference(string path, string projectFileName, |
| | | PBXSourceTree tree) |
| | | { |
| | | var fileRef = CreateFromFile(path, projectFileName, tree); |
| | | fileRef.m_LastKnownFileType = "folder"; |
| | | return fileRef; |
| | | } |
| | | |
| | | public override void UpdateProps() |
| | | { |
| | | string ext = null; |
| | | if (m_ExplicitFileType != null) |
| | | SetPropertyString("explicitFileType", m_ExplicitFileType); |
| | | else if (m_LastKnownFileType != null) |
| | | SetPropertyString("lastKnownFileType", m_LastKnownFileType); |
| | | else |
| | | { |
| | | if (name != null) |
| | | ext = Path.GetExtension(name); |
| | | else if (m_Path != null) |
| | | ext = Path.GetExtension(m_Path); |
| | | if (ext != null) |
| | | { |
| | | if (FileTypeUtils.IsFileTypeExplicit(ext)) |
| | | SetPropertyString("explicitFileType", FileTypeUtils.GetTypeName(ext)); |
| | | else |
| | | SetPropertyString("lastKnownFileType", FileTypeUtils.GetTypeName(ext)); |
| | | } |
| | | } |
| | | if (m_Path == name) |
| | | SetPropertyString("name", null); |
| | | else |
| | | SetPropertyString("name", name); |
| | | if (m_Path == null) |
| | | SetPropertyString("path", ""); |
| | | else |
| | | SetPropertyString("path", m_Path); |
| | | SetPropertyString("sourceTree", FileTypeUtils.SourceTreeDesc(tree)); |
| | | } |
| | | |
| | | public override void UpdateVars() |
| | | { |
| | | name = GetPropertyString("name"); |
| | | m_Path = GetPropertyString("path"); |
| | | if (name == null) |
| | | name = m_Path; |
| | | if (m_Path == null) |
| | | m_Path = ""; |
| | | tree = FileTypeUtils.ParseSourceTree(GetPropertyString("sourceTree")); |
| | | m_ExplicitFileType = GetPropertyString("explicitFileType"); |
| | | m_LastKnownFileType = GetPropertyString("lastKnownFileType"); |
| | | } |
| | | } |
| | | |
| | | class GUIDList : IEnumerable<string> |
| | | { |
| | | private List<string> m_List = new List<string>(); |
| | | |
| | | public GUIDList() {} |
| | | public GUIDList(List<string> data) |
| | | { |
| | | m_List = data; |
| | | } |
| | | |
| | | public static implicit operator List<string>(GUIDList list) { return list.m_List; } |
| | | public static implicit operator GUIDList(List<string> data) { return new GUIDList(data); } |
| | | |
| | | public void AddGUID(string guid) { m_List.Add(guid); } |
| | | public void RemoveGUID(string guid) { m_List.RemoveAll(x => x == guid); } |
| | | public bool Contains(string guid) { return m_List.Contains(guid); } |
| | | public int Count { get { return m_List.Count; } } |
| | | public void Clear() { m_List.Clear(); } |
| | | IEnumerator<string> IEnumerable<string>.GetEnumerator() { return m_List.GetEnumerator(); } |
| | | IEnumerator IEnumerable.GetEnumerator() { return m_List.GetEnumerator(); } |
| | | } |
| | | |
| | | internal class XCConfigurationListData : PBXObjectData |
| | | { |
| | | public GUIDList buildConfigs; |
| | | |
| | | private static PropertyCommentChecker checkerData = new PropertyCommentChecker(new string[]{ |
| | | "buildConfigurations/*" |
| | | }); |
| | | internal override PropertyCommentChecker checker { get { return checkerData; } } |
| | | |
| | | public static XCConfigurationListData Create() |
| | | { |
| | | var res = new XCConfigurationListData(); |
| | | res.guid = PBXGUID.Generate(); |
| | | |
| | | res.SetPropertyString("isa", "XCConfigurationList"); |
| | | res.buildConfigs = new GUIDList(); |
| | | res.SetPropertyString("defaultConfigurationIsVisible", "0"); |
| | | |
| | | return res; |
| | | } |
| | | |
| | | public override void UpdateProps() |
| | | { |
| | | SetPropertyList("buildConfigurations", buildConfigs); |
| | | } |
| | | public override void UpdateVars() |
| | | { |
| | | buildConfigs = GetPropertyList("buildConfigurations"); |
| | | } |
| | | } |
| | | |
| | | internal class PBXGroupData : PBXObjectData |
| | | { |
| | | public GUIDList children; |
| | | public PBXSourceTree tree; |
| | | public string name, path; |
| | | |
| | | private static PropertyCommentChecker checkerData = new PropertyCommentChecker(new string[]{ |
| | | "children/*" |
| | | }); |
| | | internal override PropertyCommentChecker checker { get { return checkerData; } } |
| | | |
| | | // name must not contain '/' |
| | | public static PBXGroupData Create(string name, string path, PBXSourceTree tree) |
| | | { |
| | | if (name.Contains("/")) |
| | | throw new Exception("Group name must not contain '/'"); |
| | | |
| | | PBXGroupData gr = new PBXGroupData(); |
| | | gr.guid = PBXGUID.Generate(); |
| | | gr.SetPropertyString("isa", "PBXGroup"); |
| | | gr.name = name; |
| | | gr.path = path; |
| | | gr.tree = PBXSourceTree.Group; |
| | | gr.children = new GUIDList(); |
| | | |
| | | return gr; |
| | | } |
| | | |
| | | public static PBXGroupData CreateRelative(string name) |
| | | { |
| | | return Create(name, name, PBXSourceTree.Group); |
| | | } |
| | | |
| | | public override void UpdateProps() |
| | | { |
| | | // The name property is set only if it is different from the path property |
| | | SetPropertyList("children", children); |
| | | if (name == path) |
| | | SetPropertyString("name", null); |
| | | else |
| | | SetPropertyString("name", name); |
| | | if (path == "") |
| | | SetPropertyString("path", null); |
| | | else |
| | | SetPropertyString("path", path); |
| | | SetPropertyString("sourceTree", FileTypeUtils.SourceTreeDesc(tree)); |
| | | } |
| | | public override void UpdateVars() |
| | | { |
| | | children = GetPropertyList("children"); |
| | | path = GetPropertyString("path"); |
| | | name = GetPropertyString("name"); |
| | | if (name == null) |
| | | name = path; |
| | | if (path == null) |
| | | path = ""; |
| | | tree = FileTypeUtils.ParseSourceTree(GetPropertyString("sourceTree")); |
| | | } |
| | | } |
| | | |
| | | internal class PBXVariantGroupData : PBXGroupData |
| | | { |
| | | } |
| | | |
| | | internal class PBXNativeTargetData : PBXObjectData |
| | | { |
| | | public GUIDList phases; |
| | | |
| | | public string buildConfigList; // guid |
| | | public string name; |
| | | public GUIDList dependencies; |
| | | public string productReference; // guid |
| | | |
| | | private static PropertyCommentChecker checkerData = new PropertyCommentChecker(new string[]{ |
| | | "buildPhases/*", |
| | | "buildRules/*", |
| | | "dependencies/*", |
| | | "productReference/*", |
| | | "buildConfigurationList/*" |
| | | }); |
| | | |
| | | internal override PropertyCommentChecker checker { get { return checkerData; } } |
| | | |
| | | public static PBXNativeTargetData Create(string name, string productRef, |
| | | string productType, string buildConfigList) |
| | | { |
| | | var res = new PBXNativeTargetData(); |
| | | res.guid = PBXGUID.Generate(); |
| | | res.SetPropertyString("isa", "PBXNativeTarget"); |
| | | res.buildConfigList = buildConfigList; |
| | | res.phases = new GUIDList(); |
| | | res.SetPropertyList("buildRules", new List<string>()); |
| | | res.dependencies = new GUIDList(); |
| | | res.name = name; |
| | | res.productReference = productRef; |
| | | res.SetPropertyString("productName", name); |
| | | res.SetPropertyString("productReference", productRef); |
| | | res.SetPropertyString("productType", productType); |
| | | return res; |
| | | } |
| | | |
| | | public override void UpdateProps() |
| | | { |
| | | SetPropertyString("buildConfigurationList", buildConfigList); |
| | | SetPropertyString("name", name); |
| | | SetPropertyString("productReference", productReference); |
| | | SetPropertyList("buildPhases", phases); |
| | | SetPropertyList("dependencies", dependencies); |
| | | } |
| | | public override void UpdateVars() |
| | | { |
| | | buildConfigList = GetPropertyString("buildConfigurationList"); |
| | | name = GetPropertyString("name"); |
| | | productReference = GetPropertyString("productReference"); |
| | | phases = GetPropertyList("buildPhases"); |
| | | dependencies = GetPropertyList("dependencies"); |
| | | } |
| | | } |
| | | |
| | | |
| | | internal class FileGUIDListBase : PBXObjectData |
| | | { |
| | | public GUIDList files; |
| | | |
| | | private static PropertyCommentChecker checkerData = new PropertyCommentChecker(new string[]{ |
| | | "files/*", |
| | | }); |
| | | |
| | | internal override PropertyCommentChecker checker { get { return checkerData; } } |
| | | |
| | | public override void UpdateProps() |
| | | { |
| | | SetPropertyList("files", files); |
| | | } |
| | | public override void UpdateVars() |
| | | { |
| | | files = GetPropertyList("files"); |
| | | } |
| | | } |
| | | |
| | | internal class PBXSourcesBuildPhaseData : FileGUIDListBase |
| | | { |
| | | public static PBXSourcesBuildPhaseData Create() |
| | | { |
| | | var res = new PBXSourcesBuildPhaseData(); |
| | | res.guid = PBXGUID.Generate(); |
| | | res.SetPropertyString("isa", "PBXSourcesBuildPhase"); |
| | | res.SetPropertyString("buildActionMask", "2147483647"); |
| | | res.files = new List<string>(); |
| | | res.SetPropertyString("runOnlyForDeploymentPostprocessing", "0"); |
| | | return res; |
| | | } |
| | | } |
| | | |
| | | internal class PBXFrameworksBuildPhaseData : FileGUIDListBase |
| | | { |
| | | public static PBXFrameworksBuildPhaseData Create() |
| | | { |
| | | var res = new PBXFrameworksBuildPhaseData(); |
| | | res.guid = PBXGUID.Generate(); |
| | | res.SetPropertyString("isa", "PBXFrameworksBuildPhase"); |
| | | res.SetPropertyString("buildActionMask", "2147483647"); |
| | | res.files = new List<string>(); |
| | | res.SetPropertyString("runOnlyForDeploymentPostprocessing", "0"); |
| | | return res; |
| | | } |
| | | } |
| | | |
| | | internal class PBXResourcesBuildPhaseData : FileGUIDListBase |
| | | { |
| | | public static PBXResourcesBuildPhaseData Create() |
| | | { |
| | | var res = new PBXResourcesBuildPhaseData(); |
| | | res.guid = PBXGUID.Generate(); |
| | | res.SetPropertyString("isa", "PBXResourcesBuildPhase"); |
| | | res.SetPropertyString("buildActionMask", "2147483647"); |
| | | res.files = new List<string>(); |
| | | res.SetPropertyString("runOnlyForDeploymentPostprocessing", "0"); |
| | | return res; |
| | | } |
| | | } |
| | | |
| | | internal class PBXCopyFilesBuildPhaseData : FileGUIDListBase |
| | | { |
| | | private static PropertyCommentChecker checkerData = new PropertyCommentChecker(new string[]{ |
| | | "files/*", |
| | | }); |
| | | |
| | | internal override PropertyCommentChecker checker { get { return checkerData; } } |
| | | |
| | | public string name; |
| | | public string dstPath; |
| | | public string dstSubfolderSpec; |
| | | |
| | | // name may be null |
| | | public static PBXCopyFilesBuildPhaseData Create(string name, string dstPath, string subfolderSpec) |
| | | { |
| | | var res = new PBXCopyFilesBuildPhaseData(); |
| | | res.guid = PBXGUID.Generate(); |
| | | res.SetPropertyString("isa", "PBXCopyFilesBuildPhase"); |
| | | res.SetPropertyString("buildActionMask", "2147483647"); |
| | | res.dstPath = dstPath; |
| | | res.dstSubfolderSpec = subfolderSpec; |
| | | res.files = new List<string>(); |
| | | res.SetPropertyString("runOnlyForDeploymentPostprocessing", "0"); |
| | | res.name = name; |
| | | return res; |
| | | } |
| | | |
| | | public override void UpdateProps() |
| | | { |
| | | SetPropertyList("files", files); |
| | | SetPropertyString("name", name); |
| | | SetPropertyString("dstPath", dstPath); |
| | | SetPropertyString("dstSubfolderSpec", dstSubfolderSpec); |
| | | } |
| | | |
| | | public override void UpdateVars() |
| | | { |
| | | files = GetPropertyList("files"); |
| | | name = GetPropertyString("name"); |
| | | dstPath = GetPropertyString("dstPath"); |
| | | dstSubfolderSpec = GetPropertyString("dstSubfolderSpec"); |
| | | } |
| | | } |
| | | |
| | | internal class PBXShellScriptBuildPhaseData : FileGUIDListBase |
| | | { |
| | | public string name; |
| | | public string shellPath; |
| | | public string shellScript; |
| | | |
| | | public static PBXShellScriptBuildPhaseData Create(string name, string shellPath, string shellScript) |
| | | { |
| | | var res = new PBXShellScriptBuildPhaseData(); |
| | | res.guid = PBXGUID.Generate(); |
| | | res.SetPropertyString("isa", "PBXShellScriptBuildPhase"); |
| | | res.SetPropertyString("buildActionMask", "2147483647"); |
| | | res.files = new List<string>(); |
| | | res.SetPropertyString("runOnlyForDeploymentPostprocessing", "0"); |
| | | res.name = name; |
| | | res.shellPath = shellPath; |
| | | res.shellScript = shellScript; |
| | | return res; |
| | | } |
| | | |
| | | public override void UpdateProps() |
| | | { |
| | | base.UpdateProps(); |
| | | SetPropertyString("name", name); |
| | | SetPropertyString("shellPath", shellPath); |
| | | SetPropertyString("shellScript", shellScript); |
| | | } |
| | | public override void UpdateVars() |
| | | { |
| | | base.UpdateVars(); |
| | | name = GetPropertyString("name"); |
| | | shellPath = GetPropertyString("shellPath"); |
| | | shellScript = GetPropertyString("shellScript"); |
| | | } |
| | | } |
| | | |
| | | internal class BuildConfigEntryData |
| | | { |
| | | public string name; |
| | | public List<string> val = new List<string>(); |
| | | |
| | | public static string ExtractValue(string src) |
| | | { |
| | | return PBXStream.UnquoteString(src.Trim().TrimEnd(',')); |
| | | } |
| | | |
| | | public void AddValue(string value) |
| | | { |
| | | if (!val.Contains(value)) |
| | | val.Add(value); |
| | | } |
| | | |
| | | public void RemoveValue(string value) |
| | | { |
| | | val.RemoveAll(v => v == value); |
| | | } |
| | | |
| | | public void RemoveValueList(IEnumerable<string> values) |
| | | { |
| | | List<string> valueList = new List<string>(values); |
| | | if (valueList.Count == 0) |
| | | return; |
| | | for (int i = 0; i < val.Count - valueList.Count; i++) |
| | | { |
| | | bool match = true; |
| | | for (int j = 0; j < valueList.Count; j++) |
| | | { |
| | | if (val[i + j] != valueList[j]) |
| | | { |
| | | match = false; |
| | | break; |
| | | } |
| | | } |
| | | if (match) |
| | | { |
| | | val.RemoveRange(i, valueList.Count); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | |
| | | public static BuildConfigEntryData FromNameValue(string name, string value) |
| | | { |
| | | BuildConfigEntryData ret = new BuildConfigEntryData(); |
| | | ret.name = name; |
| | | ret.AddValue(value); |
| | | return ret; |
| | | } |
| | | } |
| | | |
| | | internal class XCBuildConfigurationData : PBXObjectData |
| | | { |
| | | protected SortedDictionary<string, BuildConfigEntryData> entries = new SortedDictionary<string, BuildConfigEntryData>(); |
| | | public string name { get { return GetPropertyString("name"); } } |
| | | public string baseConfigurationReference; // may be null |
| | | |
| | | // Note that QuoteStringIfNeeded does its own escaping. Double-escaping with quotes is |
| | | // required to please Xcode that does not handle paths with spaces if they are not |
| | | // enclosed in quotes. |
| | | static string EscapeWithQuotesIfNeeded(string name, string value) |
| | | { |
| | | if (name != "LIBRARY_SEARCH_PATHS" && name != "FRAMEWORK_SEARCH_PATHS") |
| | | return value; |
| | | if (!value.Contains(" ")) |
| | | return value; |
| | | if (value.First() == '\"' && value.Last() == '\"') |
| | | return value; |
| | | return "\"" + value + "\""; |
| | | } |
| | | |
| | | public void SetProperty(string name, string value) |
| | | { |
| | | entries[name] = BuildConfigEntryData.FromNameValue(name, EscapeWithQuotesIfNeeded(name, value)); |
| | | } |
| | | |
| | | public void AddProperty(string name, string value) |
| | | { |
| | | if (entries.ContainsKey(name)) |
| | | entries[name].AddValue(EscapeWithQuotesIfNeeded(name, value)); |
| | | else |
| | | SetProperty(name, value); |
| | | } |
| | | |
| | | public void RemoveProperty(string name) |
| | | { |
| | | if (entries.ContainsKey(name)) |
| | | entries.Remove(name); |
| | | } |
| | | |
| | | public void RemovePropertyValue(string name, string value) |
| | | { |
| | | if (entries.ContainsKey(name)) |
| | | entries[name].RemoveValue(EscapeWithQuotesIfNeeded(name, value)); |
| | | } |
| | | |
| | | public void RemovePropertyValueList(string name, IEnumerable<string> valueList) |
| | | { |
| | | if (entries.ContainsKey(name)) |
| | | entries[name].RemoveValueList(valueList); |
| | | } |
| | | |
| | | // name should be either release or debug |
| | | public static XCBuildConfigurationData Create(string name) |
| | | { |
| | | var res = new XCBuildConfigurationData(); |
| | | res.guid = PBXGUID.Generate(); |
| | | res.SetPropertyString("isa", "XCBuildConfiguration"); |
| | | res.SetPropertyString("name", name); |
| | | return res; |
| | | } |
| | | |
| | | public override void UpdateProps() |
| | | { |
| | | SetPropertyString("baseConfigurationReference", baseConfigurationReference); |
| | | |
| | | var dict = m_Properties.CreateDict("buildSettings"); |
| | | foreach (var kv in entries) |
| | | { |
| | | if (kv.Value.val.Count == 0) |
| | | continue; |
| | | else if (kv.Value.val.Count == 1) |
| | | dict.SetString(kv.Key, kv.Value.val[0]); |
| | | else // kv.Value.val.Count > 1 |
| | | { |
| | | var array = dict.CreateArray(kv.Key); |
| | | foreach (var value in kv.Value.val) |
| | | array.AddString(value); |
| | | } |
| | | } |
| | | } |
| | | public override void UpdateVars() |
| | | { |
| | | baseConfigurationReference = GetPropertyString("baseConfigurationReference"); |
| | | |
| | | entries = new SortedDictionary<string, BuildConfigEntryData>(); |
| | | if (m_Properties.Contains("buildSettings")) |
| | | { |
| | | var dict = m_Properties["buildSettings"].AsDict(); |
| | | foreach (var key in dict.values.Keys) |
| | | { |
| | | var value = dict[key]; |
| | | if (value is PBXElementString) |
| | | { |
| | | if (entries.ContainsKey(key)) |
| | | entries[key].val.Add(value.AsString()); |
| | | else |
| | | entries.Add(key, BuildConfigEntryData.FromNameValue(key, value.AsString())); |
| | | } |
| | | else if (value is PBXElementArray) |
| | | { |
| | | foreach (var pvalue in value.AsArray().values) |
| | | { |
| | | if (pvalue is PBXElementString) |
| | | { |
| | | if (entries.ContainsKey(key)) |
| | | entries[key].val.Add(pvalue.AsString()); |
| | | else |
| | | entries.Add(key, BuildConfigEntryData.FromNameValue(key, pvalue.AsString())); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | internal class PBXContainerItemProxyData : PBXObjectData |
| | | { |
| | | private static PropertyCommentChecker checkerData = new PropertyCommentChecker(new string[]{ |
| | | "containerPortal/*" |
| | | }); |
| | | |
| | | internal override PropertyCommentChecker checker { get { return checkerData; } } |
| | | |
| | | public static PBXContainerItemProxyData Create(string containerRef, string proxyType, |
| | | string remoteGlobalGUID, string remoteInfo) |
| | | { |
| | | var res = new PBXContainerItemProxyData(); |
| | | res.guid = PBXGUID.Generate(); |
| | | res.SetPropertyString("isa", "PBXContainerItemProxy"); |
| | | res.SetPropertyString("containerPortal", containerRef); // guid |
| | | res.SetPropertyString("proxyType", proxyType); |
| | | res.SetPropertyString("remoteGlobalIDString", remoteGlobalGUID); // guid |
| | | res.SetPropertyString("remoteInfo", remoteInfo); |
| | | return res; |
| | | } |
| | | } |
| | | |
| | | internal class PBXReferenceProxyData : PBXObjectData |
| | | { |
| | | private static PropertyCommentChecker checkerData = new PropertyCommentChecker(new string[]{ |
| | | "remoteRef/*" |
| | | }); |
| | | |
| | | internal override PropertyCommentChecker checker { get { return checkerData; } } |
| | | |
| | | public string path { get { return GetPropertyString("path"); } } |
| | | |
| | | public static PBXReferenceProxyData Create(string path, string fileType, |
| | | string remoteRef, string sourceTree) |
| | | { |
| | | var res = new PBXReferenceProxyData(); |
| | | res.guid = PBXGUID.Generate(); |
| | | res.SetPropertyString("isa", "PBXReferenceProxy"); |
| | | res.SetPropertyString("path", path); |
| | | res.SetPropertyString("fileType", fileType); |
| | | res.SetPropertyString("remoteRef", remoteRef); |
| | | res.SetPropertyString("sourceTree", sourceTree); |
| | | return res; |
| | | } |
| | | } |
| | | |
| | | internal class PBXTargetDependencyData : PBXObjectData |
| | | { |
| | | private static PropertyCommentChecker checkerData = new PropertyCommentChecker(new string[]{ |
| | | "target/*", |
| | | "targetProxy/*" |
| | | }); |
| | | |
| | | internal override PropertyCommentChecker checker { get { return checkerData; } } |
| | | |
| | | public static PBXTargetDependencyData Create(string target, string targetProxy) |
| | | { |
| | | var res = new PBXTargetDependencyData(); |
| | | res.guid = PBXGUID.Generate(); |
| | | res.SetPropertyString("isa", "PBXTargetDependency"); |
| | | res.SetPropertyString("target", target); |
| | | res.SetPropertyString("targetProxy", targetProxy); |
| | | return res; |
| | | } |
| | | } |
| | | |
| | | internal class ProjectReference |
| | | { |
| | | public string group; // guid |
| | | public string projectRef; // guid |
| | | |
| | | public static ProjectReference Create(string group, string projectRef) |
| | | { |
| | | var res = new ProjectReference(); |
| | | res.group = group; |
| | | res.projectRef = projectRef; |
| | | return res; |
| | | } |
| | | } |
| | | |
| | | internal class PBXProjectObjectData : PBXObjectData |
| | | { |
| | | private static PropertyCommentChecker checkerData = new PropertyCommentChecker(new string[]{ |
| | | "buildConfigurationList/*", |
| | | "mainGroup/*", |
| | | "projectReferences/*/ProductGroup/*", |
| | | "projectReferences/*/ProjectRef/*", |
| | | "targets/*" |
| | | }); |
| | | |
| | | internal override PropertyCommentChecker checker { get { return checkerData; } } |
| | | |
| | | public List<ProjectReference> projectReferences = new List<ProjectReference>(); |
| | | public string mainGroup { get { return GetPropertyString("mainGroup"); } } |
| | | public List<string> targets = new List<string>(); |
| | | public List<string> knownAssetTags = new List<string>(); |
| | | public string buildConfigList; |
| | | // the name of the entitlements file required for some capabilities. |
| | | public string entitlementsFile; |
| | | public List<PBXCapabilityType.TargetCapabilityPair> capabilities = new List<PBXCapabilityType.TargetCapabilityPair>(); |
| | | public Dictionary<string, string> teamIDs = new Dictionary<string, string>(); |
| | | |
| | | |
| | | public void AddReference(string productGroup, string projectRef) |
| | | { |
| | | projectReferences.Add(ProjectReference.Create(productGroup, projectRef)); |
| | | } |
| | | |
| | | public override void UpdateProps() |
| | | { |
| | | m_Properties.values.Remove("projectReferences"); |
| | | if (projectReferences.Count > 0) |
| | | { |
| | | var array = m_Properties.CreateArray("projectReferences"); |
| | | foreach (var value in projectReferences) |
| | | { |
| | | var dict = array.AddDict(); |
| | | dict.SetString("ProductGroup", value.group); |
| | | dict.SetString("ProjectRef", value.projectRef); |
| | | } |
| | | }; |
| | | SetPropertyList("targets", targets); |
| | | SetPropertyString("buildConfigurationList", buildConfigList); |
| | | if (knownAssetTags.Count > 0) |
| | | { |
| | | PBXElementDict attrs; |
| | | if (m_Properties.Contains("attributes")) |
| | | attrs = m_Properties["attributes"].AsDict(); |
| | | else |
| | | attrs = m_Properties.CreateDict("attributes"); |
| | | var tags = attrs.CreateArray("knownAssetTags"); |
| | | foreach (var tag in knownAssetTags) |
| | | tags.AddString(tag); |
| | | } |
| | | |
| | | // Enable the capabilities. |
| | | foreach (var cap in capabilities) |
| | | { |
| | | var attrs = m_Properties.Contains("attributes") ? m_Properties["attributes"].AsDict() : m_Properties.CreateDict("attributes"); |
| | | var targAttr = attrs.Contains("TargetAttributes") ? attrs["TargetAttributes"].AsDict() : attrs.CreateDict("TargetAttributes"); |
| | | var target = targAttr.Contains(cap.targetGuid) ? targAttr[cap.targetGuid].AsDict() : targAttr.CreateDict(cap.targetGuid); |
| | | var sysCap = target.Contains("SystemCapabilities") ? target["SystemCapabilities"].AsDict() : target.CreateDict("SystemCapabilities"); |
| | | |
| | | var capabilityId = cap.capability.id; |
| | | var currentCapability = sysCap.Contains(capabilityId) ? sysCap[capabilityId].AsDict() : sysCap.CreateDict(capabilityId); |
| | | currentCapability.SetString("enabled", "1"); |
| | | } |
| | | |
| | | // Set the team id |
| | | foreach (KeyValuePair<string, string> teamID in teamIDs) |
| | | { |
| | | var attrs = m_Properties.Contains("attributes") ? m_Properties["attributes"].AsDict() : m_Properties.CreateDict("attributes"); |
| | | var targAttr = attrs.Contains("TargetAttributes") ? attrs["TargetAttributes"].AsDict() : attrs.CreateDict("TargetAttributes"); |
| | | var target = targAttr.Contains(teamID.Key) ? targAttr[teamID.Key].AsDict() : targAttr.CreateDict(teamID.Key); |
| | | target.SetString("DevelopmentTeam", teamID.Value); |
| | | } |
| | | } |
| | | |
| | | public override void UpdateVars() |
| | | { |
| | | projectReferences = new List<ProjectReference>(); |
| | | if (m_Properties.Contains("projectReferences")) |
| | | { |
| | | var el = m_Properties["projectReferences"].AsArray(); |
| | | foreach (var value in el.values) |
| | | { |
| | | PBXElementDict dict = value.AsDict(); |
| | | if (dict.Contains("ProductGroup") && dict.Contains("ProjectRef")) |
| | | { |
| | | string group = dict["ProductGroup"].AsString(); |
| | | string projectRef = dict["ProjectRef"].AsString(); |
| | | projectReferences.Add(ProjectReference.Create(group, projectRef)); |
| | | } |
| | | } |
| | | } |
| | | targets = GetPropertyList("targets"); |
| | | buildConfigList = GetPropertyString("buildConfigurationList"); |
| | | |
| | | // update knownAssetTags |
| | | knownAssetTags = new List<string>(); |
| | | if (m_Properties.Contains("attributes")) |
| | | { |
| | | var el = m_Properties["attributes"].AsDict(); |
| | | if (el.Contains("knownAssetTags")) |
| | | { |
| | | var tags = el["knownAssetTags"].AsArray(); |
| | | foreach (var tag in tags.values) |
| | | knownAssetTags.Add(tag.AsString()); |
| | | } |
| | | |
| | | capabilities = new List<PBXCapabilityType.TargetCapabilityPair>(); |
| | | teamIDs = new Dictionary<string, string>(); |
| | | |
| | | if (el.Contains("TargetAttributes")) |
| | | { |
| | | var targetAttr = el["TargetAttributes"].AsDict(); |
| | | foreach (var attr in targetAttr.values) |
| | | { |
| | | if (attr.Key == "DevelopmentTeam") |
| | | { |
| | | teamIDs.Add(attr.Key, attr.Value.AsString()); |
| | | } |
| | | |
| | | if (attr.Key == "SystemCapabilities") |
| | | { |
| | | var caps = el["SystemCapabilities"].AsDict(); |
| | | foreach (var cap in caps.values) |
| | | capabilities.Add(new PBXCapabilityType.TargetCapabilityPair(attr.Key, PBXCapabilityType.StringToPBXCapabilityType(cap.Value.AsString()))); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | } // namespace UnityEditor.iOS.Xcode |
| | | |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: e5cb654dcba3547a8aa444c3a651a631 |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System.Collections.Generic; |
| | | using System.Text.RegularExpressions; |
| | | using System.IO; |
| | | using System.Linq; |
| | | using System; |
| | | |
| | | #if UNITY_XCODE_API_BUILD |
| | | namespace UnityEditor.iOS.Xcode.PBX |
| | | #else |
| | | namespace UnityEditor.iOS.Xcode.Custom.PBX |
| | | #endif |
| | | { |
| | | class ValueAST {} |
| | | |
| | | // IdentifierAST := <quoted string> \ <string> |
| | | class IdentifierAST : ValueAST |
| | | { |
| | | public int value = 0; // token id |
| | | } |
| | | |
| | | // TreeAST := '{' KeyValuePairList '}' |
| | | // KeyValuePairList := KeyValuePair ',' KeyValuePairList |
| | | // KeyValuePair ',' |
| | | // (empty) |
| | | class TreeAST : ValueAST |
| | | { |
| | | public List<KeyValueAST> values = new List<KeyValueAST>(); |
| | | } |
| | | |
| | | // ListAST := '(' ValueList ')' |
| | | // ValueList := ValueAST ',' ValueList |
| | | // ValueAST ',' |
| | | // (empty) |
| | | class ArrayAST : ValueAST |
| | | { |
| | | public List<ValueAST> values = new List<ValueAST>(); |
| | | } |
| | | |
| | | // KeyValueAST := IdentifierAST '=' ValueAST ';' |
| | | // ValueAST := IdentifierAST | TreeAST | ListAST |
| | | class KeyValueAST |
| | | { |
| | | public IdentifierAST key = null; |
| | | public ValueAST value = null; // either IdentifierAST, TreeAST or ListAST |
| | | } |
| | | |
| | | class Parser |
| | | { |
| | | TokenList tokens; |
| | | int currPos; |
| | | |
| | | public Parser(TokenList tokens) |
| | | { |
| | | this.tokens = tokens; |
| | | currPos = SkipComments(0); |
| | | } |
| | | |
| | | int SkipComments(int pos) |
| | | { |
| | | while (pos < tokens.Count && tokens[pos].type == TokenType.Comment) |
| | | { |
| | | pos++; |
| | | } |
| | | return pos; |
| | | } |
| | | |
| | | // returns new position |
| | | int IncInternal(int pos) |
| | | { |
| | | if (pos >= tokens.Count) |
| | | return pos; |
| | | pos++; |
| | | |
| | | return SkipComments(pos); |
| | | } |
| | | |
| | | // Increments current pointer if not past the end, returns previous pos |
| | | int Inc() |
| | | { |
| | | int prev = currPos; |
| | | currPos = IncInternal(currPos); |
| | | return prev; |
| | | } |
| | | |
| | | // Returns the token type of the current token |
| | | TokenType Tok() |
| | | { |
| | | if (currPos >= tokens.Count) |
| | | return TokenType.EOF; |
| | | return tokens[currPos].type; |
| | | } |
| | | |
| | | void SkipIf(TokenType type) |
| | | { |
| | | if (Tok() == type) |
| | | Inc(); |
| | | } |
| | | |
| | | string GetErrorMsg() |
| | | { |
| | | return "Invalid PBX project (parsing line " + tokens[currPos].line + ")"; |
| | | } |
| | | |
| | | public IdentifierAST ParseIdentifier() |
| | | { |
| | | if (Tok() != TokenType.String && Tok() != TokenType.QuotedString) |
| | | throw new Exception(GetErrorMsg()); |
| | | var ast = new IdentifierAST(); |
| | | ast.value = Inc(); |
| | | return ast; |
| | | } |
| | | |
| | | public TreeAST ParseTree() |
| | | { |
| | | if (Tok() != TokenType.LBrace) |
| | | throw new Exception(GetErrorMsg()); |
| | | Inc(); |
| | | |
| | | var ast = new TreeAST(); |
| | | while (Tok() != TokenType.RBrace && Tok() != TokenType.EOF) |
| | | { |
| | | ast.values.Add(ParseKeyValue()); |
| | | } |
| | | SkipIf(TokenType.RBrace); |
| | | return ast; |
| | | } |
| | | |
| | | public ArrayAST ParseList() |
| | | { |
| | | if (Tok() != TokenType.LParen) |
| | | throw new Exception(GetErrorMsg()); |
| | | Inc(); |
| | | |
| | | var ast = new ArrayAST(); |
| | | while (Tok() != TokenType.RParen && Tok() != TokenType.EOF) |
| | | { |
| | | ast.values.Add(ParseValue()); |
| | | SkipIf(TokenType.Comma); |
| | | } |
| | | SkipIf(TokenType.RParen); |
| | | return ast; |
| | | } |
| | | |
| | | // throws on error |
| | | public KeyValueAST ParseKeyValue() |
| | | { |
| | | var ast = new KeyValueAST(); |
| | | ast.key = ParseIdentifier(); |
| | | |
| | | if (Tok() != TokenType.Eq) |
| | | throw new Exception(GetErrorMsg()); |
| | | Inc(); // skip '=' |
| | | |
| | | ast.value = ParseValue(); |
| | | SkipIf(TokenType.Semicolon); |
| | | |
| | | return ast; |
| | | } |
| | | |
| | | // throws on error |
| | | public ValueAST ParseValue() |
| | | { |
| | | if (Tok() == TokenType.String || Tok() == TokenType.QuotedString) |
| | | return ParseIdentifier(); |
| | | else if (Tok() == TokenType.LBrace) |
| | | return ParseTree(); |
| | | else if (Tok() == TokenType.LParen) |
| | | return ParseList(); |
| | | throw new Exception(GetErrorMsg()); |
| | | } |
| | | } |
| | | |
| | | } // namespace UnityEditor.iOS.Xcode |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 0c57f96ccca694ed3b4f40636df4871a |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System; |
| | | using System.Collections; |
| | | using System.Collections.Generic; |
| | | using System.Text; |
| | | using System.Text.RegularExpressions; |
| | | using System.IO; |
| | | |
| | | // Base classes for section handling |
| | | |
| | | #if UNITY_XCODE_API_BUILD |
| | | namespace UnityEditor.iOS.Xcode.PBX |
| | | #else |
| | | namespace UnityEditor.iOS.Xcode.Custom.PBX |
| | | #endif |
| | | { |
| | | |
| | | // common base |
| | | internal abstract class SectionBase |
| | | { |
| | | public abstract void AddObject(string key, PBXElementDict value); |
| | | public abstract void WriteSection(StringBuilder sb, GUIDToCommentMap comments); |
| | | } |
| | | |
| | | // known section: contains objects that we care about |
| | | internal class KnownSectionBase<T> : SectionBase where T : PBXObjectData, new() |
| | | { |
| | | private Dictionary<string, T> m_Entries = new Dictionary<string, T>(); |
| | | |
| | | private string m_Name; |
| | | |
| | | public KnownSectionBase(string sectionName) |
| | | { |
| | | m_Name = sectionName; |
| | | } |
| | | |
| | | public IEnumerable<KeyValuePair<string, T>> GetEntries() |
| | | { |
| | | return m_Entries; |
| | | } |
| | | |
| | | public IEnumerable<string> GetGuids() |
| | | { |
| | | return m_Entries.Keys; |
| | | } |
| | | |
| | | public IEnumerable<T> GetObjects() |
| | | { |
| | | return m_Entries.Values; |
| | | } |
| | | |
| | | public override void AddObject(string key, PBXElementDict value) |
| | | { |
| | | T obj = new T(); |
| | | obj.guid = key; |
| | | obj.SetPropertiesWhenSerializing(value); |
| | | obj.UpdateVars(); |
| | | m_Entries[obj.guid] = obj; |
| | | } |
| | | |
| | | public override void WriteSection(StringBuilder sb, GUIDToCommentMap comments) |
| | | { |
| | | if (m_Entries.Count == 0) |
| | | return; // do not write empty sections |
| | | |
| | | sb.AppendFormat("\n\n/* Begin {0} section */", m_Name); |
| | | var keys = new List<string>(m_Entries.Keys); |
| | | keys.Sort(StringComparer.Ordinal); |
| | | foreach (string key in keys) |
| | | { |
| | | T obj = m_Entries[key]; |
| | | obj.UpdateProps(); |
| | | sb.Append("\n\t\t"); |
| | | comments.WriteStringBuilder(sb, obj.guid); |
| | | sb.Append(" = "); |
| | | Serializer.WriteDict(sb, obj.GetPropertiesWhenSerializing(), 2, |
| | | obj.shouldCompact, obj.checker, comments); |
| | | sb.Append(";"); |
| | | } |
| | | sb.AppendFormat("\n/* End {0} section */", m_Name); |
| | | } |
| | | |
| | | // returns null if not found |
| | | public T this[string guid] |
| | | { |
| | | get { |
| | | if (m_Entries.ContainsKey(guid)) |
| | | return m_Entries[guid]; |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | public bool HasEntry(string guid) |
| | | { |
| | | return m_Entries.ContainsKey(guid); |
| | | } |
| | | |
| | | public void AddEntry(T obj) |
| | | { |
| | | m_Entries[obj.guid] = obj; |
| | | } |
| | | |
| | | public void RemoveEntry(string guid) |
| | | { |
| | | if (m_Entries.ContainsKey(guid)) |
| | | m_Entries.Remove(guid); |
| | | } |
| | | } |
| | | |
| | | // we assume there is only one PBXProject entry |
| | | internal class PBXProjectSection : KnownSectionBase<PBXProjectObjectData> |
| | | { |
| | | public PBXProjectSection() : base("PBXProject") |
| | | { |
| | | } |
| | | |
| | | public PBXProjectObjectData project |
| | | { |
| | | get { |
| | | foreach (var kv in GetEntries()) |
| | | return kv.Value; |
| | | return null; |
| | | } |
| | | } |
| | | } |
| | | |
| | | } // UnityEditor.iOS.Xcode |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 7a069fc7321244656b0ca1f0f77aac95 |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Text; |
| | | using System.Text.RegularExpressions; |
| | | using System.IO; |
| | | using System.Linq; |
| | | |
| | | #if UNITY_XCODE_API_BUILD |
| | | namespace UnityEditor.iOS.Xcode.PBX |
| | | #else |
| | | namespace UnityEditor.iOS.Xcode.Custom.PBX |
| | | #endif |
| | | { |
| | | class PropertyCommentChecker |
| | | { |
| | | private int m_Level; |
| | | private bool m_All; |
| | | private List<List<string>> m_Props; |
| | | |
| | | /* The argument is an array of matcher strings each of which determine |
| | | whether a property with a certain path needs to be decorated with a |
| | | comment. |
| | | |
| | | A path is a number of path elements concatenated by '/'. The last path |
| | | element is either: |
| | | the key if we're referring to a dict key |
| | | the value if we're referring to a value in an array |
| | | the value if we're referring to a value in a dict |
| | | All other path elements are either: |
| | | the key if the container is dict |
| | | '*' if the container is array |
| | | |
| | | Path matcher has the same structure as a path, except that any of the |
| | | elements may be '*'. Matcher matches a path if both have the same number |
| | | of elements and for each pair matcher element is the same as path element |
| | | or is '*'. |
| | | |
| | | a/b/c matches a/b/c but not a/b nor a/b/c/d |
| | | a/* /c matches a/d/c but not a/b nor a/b/c/d |
| | | * /* /* matches any path from three elements |
| | | */ |
| | | protected PropertyCommentChecker(int level, List<List<string>> props) |
| | | { |
| | | m_Level = level; |
| | | m_All = false; |
| | | m_Props = props; |
| | | } |
| | | |
| | | public PropertyCommentChecker() |
| | | { |
| | | m_Level = 0; |
| | | m_All = false; |
| | | m_Props = new List<List<string>>(); |
| | | } |
| | | |
| | | public PropertyCommentChecker(IEnumerable<string> props) |
| | | { |
| | | m_Level = 0; |
| | | m_All = false; |
| | | m_Props = new List<List<string>>(); |
| | | foreach (var prop in props) |
| | | { |
| | | m_Props.Add(new List<string>(prop.Split('/'))); |
| | | } |
| | | } |
| | | |
| | | bool CheckContained(string prop) |
| | | { |
| | | if (m_All) |
| | | return true; |
| | | foreach (var list in m_Props) |
| | | { |
| | | if (list.Count == m_Level+1) |
| | | { |
| | | if (list[m_Level] == prop) |
| | | return true; |
| | | if (list[m_Level] == "*") |
| | | { |
| | | m_All = true; // short-circuit all at this level |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | public bool CheckStringValueInArray(string value) { return CheckContained(value); } |
| | | public bool CheckKeyInDict(string key) { return CheckContained(key); } |
| | | |
| | | public bool CheckStringValueInDict(string key, string value) |
| | | { |
| | | foreach (var list in m_Props) |
| | | { |
| | | if (list.Count == m_Level + 2) |
| | | { |
| | | if ((list[m_Level] == "*" || list[m_Level] == key) && |
| | | list[m_Level+1] == "*" || list[m_Level+1] == value) |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | public PropertyCommentChecker NextLevel(string prop) |
| | | { |
| | | var newList = new List<List<string>>(); |
| | | foreach (var list in m_Props) |
| | | { |
| | | if (list.Count <= m_Level+1) |
| | | continue; |
| | | if (list[m_Level] == "*" || list[m_Level] == prop) |
| | | newList.Add(list); |
| | | } |
| | | return new PropertyCommentChecker(m_Level + 1, newList); |
| | | } |
| | | } |
| | | |
| | | class Serializer |
| | | { |
| | | public static PBXElementDict ParseTreeAST(TreeAST ast, TokenList tokens, string text) |
| | | { |
| | | var el = new PBXElementDict(); |
| | | foreach (var kv in ast.values) |
| | | { |
| | | PBXElementString key = ParseIdentifierAST(kv.key, tokens, text); |
| | | PBXElement value = ParseValueAST(kv.value, tokens, text); |
| | | el[key.value] = value; |
| | | } |
| | | return el; |
| | | } |
| | | |
| | | public static PBXElementArray ParseArrayAST(ArrayAST ast, TokenList tokens, string text) |
| | | { |
| | | var el = new PBXElementArray(); |
| | | foreach (var v in ast.values) |
| | | { |
| | | el.values.Add(ParseValueAST(v, tokens, text)); |
| | | } |
| | | return el; |
| | | } |
| | | |
| | | public static PBXElement ParseValueAST(ValueAST ast, TokenList tokens, string text) |
| | | { |
| | | if (ast is TreeAST) |
| | | return ParseTreeAST((TreeAST)ast, tokens, text); |
| | | if (ast is ArrayAST) |
| | | return ParseArrayAST((ArrayAST)ast, tokens, text); |
| | | if (ast is IdentifierAST) |
| | | return ParseIdentifierAST((IdentifierAST)ast, tokens, text); |
| | | return null; |
| | | } |
| | | |
| | | public static PBXElementString ParseIdentifierAST(IdentifierAST ast, TokenList tokens, string text) |
| | | { |
| | | Token tok = tokens[ast.value]; |
| | | string value; |
| | | switch (tok.type) |
| | | { |
| | | case TokenType.String: |
| | | value = text.Substring(tok.begin, tok.end - tok.begin); |
| | | return new PBXElementString(value); |
| | | case TokenType.QuotedString: |
| | | value = text.Substring(tok.begin, tok.end - tok.begin); |
| | | value = PBXStream.UnquoteString(value); |
| | | return new PBXElementString(value); |
| | | default: |
| | | throw new Exception("Internal parser error"); |
| | | } |
| | | } |
| | | |
| | | static string k_Indent = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; |
| | | |
| | | static string GetIndent(int indent) |
| | | { |
| | | return k_Indent.Substring(0, indent); |
| | | } |
| | | |
| | | static void WriteStringImpl(StringBuilder sb, string s, bool comment, GUIDToCommentMap comments) |
| | | { |
| | | if (comment) |
| | | comments.WriteStringBuilder(sb, s); |
| | | else |
| | | sb.Append(PBXStream.QuoteStringIfNeeded(s)); |
| | | } |
| | | |
| | | public static void WriteDictKeyValue(StringBuilder sb, string key, PBXElement value, int indent, bool compact, |
| | | PropertyCommentChecker checker, GUIDToCommentMap comments) |
| | | { |
| | | if (!compact) |
| | | { |
| | | sb.Append("\n"); |
| | | sb.Append(GetIndent(indent)); |
| | | } |
| | | WriteStringImpl(sb, key, checker.CheckKeyInDict(key), comments); |
| | | sb.Append(" = "); |
| | | |
| | | if (value is PBXElementString) |
| | | WriteStringImpl(sb, value.AsString(), checker.CheckStringValueInDict(key, value.AsString()), comments); |
| | | else if (value is PBXElementDict) |
| | | WriteDict(sb, value.AsDict(), indent, compact, checker.NextLevel(key), comments); |
| | | else if (value is PBXElementArray) |
| | | WriteArray(sb, value.AsArray(), indent, compact, checker.NextLevel(key), comments); |
| | | sb.Append(";"); |
| | | if (compact) |
| | | sb.Append(" "); |
| | | } |
| | | |
| | | public static void WriteDict(StringBuilder sb, PBXElementDict el, int indent, bool compact, |
| | | PropertyCommentChecker checker, GUIDToCommentMap comments) |
| | | { |
| | | sb.Append("{"); |
| | | |
| | | if (el.Contains("isa")) |
| | | WriteDictKeyValue(sb, "isa", el["isa"], indent+1, compact, checker, comments); |
| | | var keys = new List<string>(el.values.Keys); |
| | | keys.Sort(StringComparer.Ordinal); |
| | | foreach (var key in keys) |
| | | { |
| | | if (key != "isa") |
| | | WriteDictKeyValue(sb, key, el[key], indent+1, compact, checker, comments); |
| | | } |
| | | if (!compact) |
| | | { |
| | | sb.Append("\n"); |
| | | sb.Append(GetIndent(indent)); |
| | | } |
| | | sb.Append("}"); |
| | | } |
| | | |
| | | public static void WriteArray(StringBuilder sb, PBXElementArray el, int indent, bool compact, |
| | | PropertyCommentChecker checker, GUIDToCommentMap comments) |
| | | { |
| | | sb.Append("("); |
| | | foreach (var value in el.values) |
| | | { |
| | | if (!compact) |
| | | { |
| | | sb.Append("\n"); |
| | | sb.Append(GetIndent(indent+1)); |
| | | } |
| | | |
| | | if (value is PBXElementString) |
| | | WriteStringImpl(sb, value.AsString(), checker.CheckStringValueInArray(value.AsString()), comments); |
| | | else if (value is PBXElementDict) |
| | | WriteDict(sb, value.AsDict(), indent+1, compact, checker.NextLevel("*"), comments); |
| | | else if (value is PBXElementArray) |
| | | WriteArray(sb, value.AsArray(), indent+1, compact, checker.NextLevel("*"), comments); |
| | | sb.Append(","); |
| | | if (compact) |
| | | sb.Append(" "); |
| | | } |
| | | |
| | | if (!compact) |
| | | { |
| | | sb.Append("\n"); |
| | | sb.Append(GetIndent(indent)); |
| | | } |
| | | sb.Append(")"); |
| | | } |
| | | } |
| | | |
| | | } // namespace UnityEditor.iOS.Xcode |
| | | |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 029907646ae924238af446911774e82d |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Text; |
| | | using System.Text.RegularExpressions; |
| | | using System.IO; |
| | | |
| | | #if UNITY_XCODE_API_BUILD |
| | | namespace UnityEditor.iOS.Xcode.PBX |
| | | #else |
| | | namespace UnityEditor.iOS.Xcode.Custom.PBX |
| | | #endif |
| | | { |
| | | internal class GUIDToCommentMap |
| | | { |
| | | private Dictionary<string, string> m_Dict = new Dictionary<string, string>(); |
| | | |
| | | public string this[string guid] |
| | | { |
| | | get { |
| | | if (m_Dict.ContainsKey(guid)) |
| | | return m_Dict[guid]; |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | public void Add(string guid, string comment) |
| | | { |
| | | if (m_Dict.ContainsKey(guid)) |
| | | return; |
| | | m_Dict.Add(guid, comment); |
| | | } |
| | | |
| | | public void Remove(string guid) |
| | | { |
| | | m_Dict.Remove(guid); |
| | | } |
| | | |
| | | public string Write(string guid) |
| | | { |
| | | string comment = this[guid]; |
| | | if (comment == null) |
| | | return guid; |
| | | return String.Format("{0} /* {1} */", guid, comment); |
| | | } |
| | | |
| | | public void WriteStringBuilder(StringBuilder sb, string guid) |
| | | { |
| | | string comment = this[guid]; |
| | | if (comment == null) |
| | | sb.Append(guid); |
| | | else |
| | | { |
| | | // {0} /* {1} */ |
| | | sb.Append(guid).Append(" /* ").Append(comment).Append(" */"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | internal class PBXGUID |
| | | { |
| | | internal delegate string GuidGenerator(); |
| | | |
| | | // We allow changing Guid generator to make testing of PBXProject possible |
| | | private static GuidGenerator guidGenerator = DefaultGuidGenerator; |
| | | |
| | | internal static string DefaultGuidGenerator() |
| | | { |
| | | return Guid.NewGuid().ToString("N").Substring(8).ToUpper(); |
| | | } |
| | | |
| | | internal static void SetGuidGenerator(GuidGenerator generator) |
| | | { |
| | | guidGenerator = generator; |
| | | } |
| | | |
| | | // Generates a GUID. |
| | | public static string Generate() |
| | | { |
| | | return guidGenerator(); |
| | | } |
| | | } |
| | | |
| | | internal class PBXRegex |
| | | { |
| | | public static string GuidRegexString = "[A-Fa-f0-9]{24}"; |
| | | } |
| | | |
| | | internal class PBXStream |
| | | { |
| | | static bool DontNeedQuotes(string src) |
| | | { |
| | | // using a regex instead of explicit matching slows down common cases by 40% |
| | | if (src.Length == 0) |
| | | return false; |
| | | |
| | | bool hasSlash = false; |
| | | for (int i = 0; i < src.Length; ++i) |
| | | { |
| | | char c = src[i]; |
| | | if (Char.IsLetterOrDigit(c) || c == '.' || c == '*' || c == '_') |
| | | continue; |
| | | if (c == '/') |
| | | { |
| | | hasSlash = true; |
| | | continue; |
| | | } |
| | | return false; |
| | | } |
| | | if (hasSlash) |
| | | { |
| | | if (src.Contains("//") || src.Contains("/*") || src.Contains("*/")) |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | // Quotes the given string if it contains special characters. Note: if the string already |
| | | // contains quotes, then they are escaped and the entire string quoted again |
| | | public static string QuoteStringIfNeeded(string src) |
| | | { |
| | | if (DontNeedQuotes(src)) |
| | | return src; |
| | | return "\"" + src.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n") + "\""; |
| | | } |
| | | |
| | | // If the given string is quoted, removes the quotes and unescapes any quotes within the string |
| | | public static string UnquoteString(string src) |
| | | { |
| | | if (!src.StartsWith("\"") || !src.EndsWith("\"")) |
| | | return src; |
| | | return src.Substring(1, src.Length - 2).Replace("\\\\", "\u569f").Replace("\\\"", "\"") |
| | | .Replace("\\n", "\n").Replace("\u569f", "\\"); // U+569f is a rarely used Chinese character |
| | | } |
| | | } |
| | | |
| | | internal enum PBXFileType |
| | | { |
| | | NotBuildable, |
| | | Framework, |
| | | Source, |
| | | Resource, |
| | | CopyFile, |
| | | ShellScript |
| | | } |
| | | |
| | | internal class FileTypeUtils |
| | | { |
| | | internal class FileTypeDesc |
| | | { |
| | | public FileTypeDesc(string typeName, PBXFileType type) |
| | | { |
| | | this.name = typeName; |
| | | this.type = type; |
| | | this.isExplicit = false; |
| | | } |
| | | |
| | | public FileTypeDesc(string typeName, PBXFileType type, bool isExplicit) |
| | | { |
| | | this.name = typeName; |
| | | this.type = type; |
| | | this.isExplicit = isExplicit; |
| | | } |
| | | |
| | | public string name; |
| | | public PBXFileType type; |
| | | public bool isExplicit; |
| | | } |
| | | |
| | | private static readonly Dictionary<string, FileTypeDesc> types = |
| | | new Dictionary<string, FileTypeDesc> |
| | | { |
| | | { "a", new FileTypeDesc("archive.ar", PBXFileType.Framework) }, |
| | | { "aif", new FileTypeDesc("sound.aif", PBXFileType.Resource) }, |
| | | { "app", new FileTypeDesc("wrapper.application", PBXFileType.NotBuildable, true) }, |
| | | { "appex", new FileTypeDesc("wrapper.app-extension", PBXFileType.CopyFile) }, |
| | | { "bin", new FileTypeDesc("archive.macbinary", PBXFileType.Resource) }, |
| | | { "s", new FileTypeDesc("sourcecode.asm", PBXFileType.Source) }, |
| | | { "c", new FileTypeDesc("sourcecode.c.c", PBXFileType.Source) }, |
| | | { "cc", new FileTypeDesc("sourcecode.cpp.cpp", PBXFileType.Source) }, |
| | | { "cpp", new FileTypeDesc("sourcecode.cpp.cpp", PBXFileType.Source) }, |
| | | { "swift", new FileTypeDesc("sourcecode.swift", PBXFileType.Source) }, |
| | | { "dll", new FileTypeDesc("file", PBXFileType.NotBuildable) }, |
| | | { "framework", new FileTypeDesc("wrapper.framework", PBXFileType.Framework) }, |
| | | { "h", new FileTypeDesc("sourcecode.c.h", PBXFileType.NotBuildable) }, |
| | | { "pch", new FileTypeDesc("sourcecode.c.h", PBXFileType.NotBuildable) }, |
| | | { "icns", new FileTypeDesc("image.icns", PBXFileType.Resource) }, |
| | | { "xcassets", new FileTypeDesc("folder.assetcatalog", PBXFileType.Resource) }, |
| | | { "inc", new FileTypeDesc("sourcecode.inc", PBXFileType.NotBuildable) }, |
| | | { "m", new FileTypeDesc("sourcecode.c.objc", PBXFileType.Source) }, |
| | | { "mm", new FileTypeDesc("sourcecode.cpp.objcpp", PBXFileType.Source ) }, |
| | | { "mp3", new FileTypeDesc("sound.mp3", PBXFileType.Resource) }, |
| | | { "nib", new FileTypeDesc("wrapper.nib", PBXFileType.Resource) }, |
| | | { "plist", new FileTypeDesc("text.plist.xml", PBXFileType.Resource) }, |
| | | { "png", new FileTypeDesc("image.png", PBXFileType.Resource) }, |
| | | { "rtf", new FileTypeDesc("text.rtf", PBXFileType.Resource) }, |
| | | { "tiff", new FileTypeDesc("image.tiff", PBXFileType.Resource) }, |
| | | { "txt", new FileTypeDesc("text", PBXFileType.Resource) }, |
| | | { "json", new FileTypeDesc("text.json", PBXFileType.Resource) }, |
| | | { "xcodeproj", new FileTypeDesc("wrapper.pb-project", PBXFileType.NotBuildable) }, |
| | | { "xib", new FileTypeDesc("file.xib", PBXFileType.Resource) }, |
| | | { "strings", new FileTypeDesc("text.plist.strings", PBXFileType.Resource) }, |
| | | { "storyboard",new FileTypeDesc("file.storyboard", PBXFileType.Resource) }, |
| | | { "bundle", new FileTypeDesc("wrapper.plug-in", PBXFileType.Resource) }, |
| | | { "dylib", new FileTypeDesc("compiled.mach-o.dylib", PBXFileType.Framework) }, |
| | | { "tbd", new FileTypeDesc("sourcecode.text-based-dylib-definition", PBXFileType.Framework) }, |
| | | { "wav", new FileTypeDesc("sound.wav", PBXFileType.Resource) } |
| | | }; |
| | | |
| | | public static string TrimExtension(string ext) |
| | | { |
| | | return ext.TrimStart('.'); |
| | | } |
| | | |
| | | public static bool IsKnownExtension(string ext) |
| | | { |
| | | ext = TrimExtension(ext); |
| | | return types.ContainsKey(ext); |
| | | } |
| | | |
| | | internal static bool IsFileTypeExplicit(string ext) |
| | | { |
| | | ext = TrimExtension(ext); |
| | | if (types.ContainsKey(ext)) |
| | | return types[ext].isExplicit; |
| | | return false; |
| | | } |
| | | |
| | | public static PBXFileType GetFileType(string ext, bool isFolderRef) |
| | | { |
| | | ext = TrimExtension(ext); |
| | | if (isFolderRef) |
| | | return PBXFileType.Resource; |
| | | if (!types.ContainsKey(ext)) |
| | | return PBXFileType.Resource; |
| | | return types[ext].type; |
| | | } |
| | | |
| | | public static string GetTypeName(string ext) |
| | | { |
| | | ext = TrimExtension(ext); |
| | | if (types.ContainsKey(ext)) |
| | | return types[ext].name; |
| | | // Xcode actually checks the file contents to determine the file type. |
| | | // Text files have "text" type and all other files have "file" type. |
| | | // Since we can't reasonably determine whether the file in question is |
| | | // a text file, we just take the safe route and return "file" type. |
| | | return "file"; |
| | | } |
| | | |
| | | public static bool IsBuildableFile(string ext) |
| | | { |
| | | ext = TrimExtension(ext); |
| | | if (!types.ContainsKey(ext)) |
| | | return true; |
| | | if (types[ext].type != PBXFileType.NotBuildable) |
| | | return true; |
| | | return false; |
| | | } |
| | | |
| | | public static bool IsBuildable(string ext, bool isFolderReference) |
| | | { |
| | | ext = TrimExtension(ext); |
| | | if (isFolderReference) |
| | | return true; |
| | | return IsBuildableFile(ext); |
| | | } |
| | | |
| | | private static readonly Dictionary<PBXSourceTree, string> sourceTree = new Dictionary<PBXSourceTree, string> |
| | | { |
| | | { PBXSourceTree.Absolute, "<absolute>" }, |
| | | { PBXSourceTree.Group, "<group>" }, |
| | | { PBXSourceTree.Build, "BUILT_PRODUCTS_DIR" }, |
| | | { PBXSourceTree.Developer, "DEVELOPER_DIR" }, |
| | | { PBXSourceTree.Sdk, "SDKROOT" }, |
| | | { PBXSourceTree.Source, "SOURCE_ROOT" }, |
| | | }; |
| | | |
| | | private static readonly Dictionary<string, PBXSourceTree> stringToSourceTreeMap = new Dictionary<string, PBXSourceTree> |
| | | { |
| | | { "<absolute>", PBXSourceTree.Absolute }, |
| | | { "<group>", PBXSourceTree.Group }, |
| | | { "BUILT_PRODUCTS_DIR", PBXSourceTree.Build }, |
| | | { "DEVELOPER_DIR", PBXSourceTree.Developer }, |
| | | { "SDKROOT", PBXSourceTree.Sdk }, |
| | | { "SOURCE_ROOT", PBXSourceTree.Source }, |
| | | }; |
| | | |
| | | internal static string SourceTreeDesc(PBXSourceTree tree) |
| | | { |
| | | return sourceTree[tree]; |
| | | } |
| | | |
| | | // returns PBXSourceTree.Source on error |
| | | internal static PBXSourceTree ParseSourceTree(string tree) |
| | | { |
| | | if (stringToSourceTreeMap.ContainsKey(tree)) |
| | | return stringToSourceTreeMap[tree]; |
| | | return PBXSourceTree.Source; |
| | | } |
| | | |
| | | internal static List<PBXSourceTree> AllAbsoluteSourceTrees() |
| | | { |
| | | return new List<PBXSourceTree>{PBXSourceTree.Absolute, PBXSourceTree.Build, |
| | | PBXSourceTree.Developer, PBXSourceTree.Sdk, PBXSourceTree.Source}; |
| | | } |
| | | } |
| | | |
| | | } // UnityEditor.iOS.Xcode |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: d480bd1a541b24e418cbe771bdc81686 |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | #if UNITY_XCODE_API_BUILD |
| | | namespace UnityEditor.iOS.Xcode |
| | | #else |
| | | namespace UnityEditor.iOS.Xcode.Custom |
| | | #endif |
| | | { |
| | | /// <summary> |
| | | /// List of all the capabilities available. |
| | | /// </summary> |
| | | public sealed class PBXCapabilityType |
| | | { |
| | | public static readonly PBXCapabilityType ApplePay = new PBXCapabilityType ("com.apple.ApplePay", true); |
| | | public static readonly PBXCapabilityType AppGroups = new PBXCapabilityType ("com.apple.ApplicationGroups.iOS", true); |
| | | public static readonly PBXCapabilityType AssociatedDomains = new PBXCapabilityType ("com.apple.SafariKeychain", true); |
| | | public static readonly PBXCapabilityType BackgroundModes = new PBXCapabilityType ("com.apple.BackgroundModes", false); |
| | | public static readonly PBXCapabilityType DataProtection = new PBXCapabilityType ("com.apple.DataProtection", true); |
| | | public static readonly PBXCapabilityType GameCenter = new PBXCapabilityType ("com.apple.GameCenter", false, "GameKit.framework"); |
| | | public static readonly PBXCapabilityType HealthKit = new PBXCapabilityType ("com.apple.HealthKit", true, "HealthKit.framework"); |
| | | public static readonly PBXCapabilityType HomeKit = new PBXCapabilityType ("com.apple.HomeKit", true, "HomeKit.framework"); |
| | | public static readonly PBXCapabilityType iCloud = new PBXCapabilityType("com.apple.iCloud", true, "CloudKit.framework", true); |
| | | public static readonly PBXCapabilityType InAppPurchase = new PBXCapabilityType ("com.apple.InAppPurchase", false); |
| | | public static readonly PBXCapabilityType InterAppAudio = new PBXCapabilityType ("com.apple.InterAppAudio", true, "AudioToolbox.framework"); |
| | | public static readonly PBXCapabilityType KeychainSharing = new PBXCapabilityType ("com.apple.KeychainSharing", true); |
| | | public static readonly PBXCapabilityType Maps = new PBXCapabilityType("com.apple.Maps.iOS", false, "MapKit.framework"); |
| | | public static readonly PBXCapabilityType PersonalVPN = new PBXCapabilityType("com.apple.VPNLite", true, "NetworkExtension.framework"); |
| | | public static readonly PBXCapabilityType PushNotifications = new PBXCapabilityType ("com.apple.Push", true); |
| | | public static readonly PBXCapabilityType Siri = new PBXCapabilityType ("com.apple.Siri", true); |
| | | public static readonly PBXCapabilityType Wallet = new PBXCapabilityType ("com.apple.Wallet", true, "PassKit.framework"); |
| | | public static readonly PBXCapabilityType WirelessAccessoryConfiguration = new PBXCapabilityType("com.apple.WAC", true, "ExternalAccessory.framework"); |
| | | |
| | | private readonly string m_ID; |
| | | private readonly bool m_RequiresEntitlements; |
| | | private readonly string m_Framework; |
| | | private readonly bool m_OptionalFramework; |
| | | |
| | | public bool optionalFramework |
| | | { |
| | | get { return m_OptionalFramework; } |
| | | } |
| | | |
| | | public string framework |
| | | { |
| | | get { return m_Framework; } |
| | | } |
| | | |
| | | public string id |
| | | { |
| | | get { return m_ID; } |
| | | } |
| | | |
| | | public bool requiresEntitlements |
| | | { |
| | | get { return m_RequiresEntitlements; } |
| | | } |
| | | |
| | | public struct TargetCapabilityPair |
| | | { |
| | | public string targetGuid; |
| | | public PBXCapabilityType capability; |
| | | |
| | | public TargetCapabilityPair(string guid, PBXCapabilityType type) |
| | | { |
| | | targetGuid = guid; |
| | | capability = type; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// This private object represents what a capability changes in the PBXProject file |
| | | /// </summary> |
| | | /// <param name="id">The string used in the PBXProject file to identify the capability and mark it as enabled.</param> |
| | | /// <param name="requiresEntitlements">This capability requires an entitlements file therefore we need to add this entitlements file to the code signing entitlement.</param> |
| | | /// <param name="framework">Specify which framework need to be added to the project for this capability, if "" no framework are added.</param> |
| | | /// <param name="optionalFramework">Some capability (right now only iCloud) adds a framework, not all the time but just when some option are checked |
| | | /// this parameter indicates if one of them is checked.</param> |
| | | private PBXCapabilityType(string _id, bool _requiresEntitlements, string _framework = "", bool _optionalFramework = false) |
| | | { |
| | | m_ID = _id; |
| | | m_RequiresEntitlements = _requiresEntitlements; |
| | | m_Framework = _framework; |
| | | m_OptionalFramework = _optionalFramework; |
| | | } |
| | | |
| | | public static PBXCapabilityType StringToPBXCapabilityType(string cap) |
| | | { |
| | | switch (cap) |
| | | { |
| | | case "com.apple.ApplePay": |
| | | return ApplePay; |
| | | case "com.apple.ApplicationGroups.iOS": |
| | | return AppGroups; |
| | | case "com.apple.SafariKeychain": |
| | | return AssociatedDomains; |
| | | case "com.apple.BackgroundModes": |
| | | return BackgroundModes; |
| | | case "com.apple.DataProtection": |
| | | return DataProtection; |
| | | case "com.apple.GameCenter": |
| | | return GameCenter; |
| | | case "com.apple.HealthKit": |
| | | return HealthKit; |
| | | case "com.apple.HomeKit": |
| | | return HomeKit; |
| | | case "com.apple.iCloud": |
| | | return iCloud; |
| | | case "com.apple.InAppPurchase": |
| | | return InAppPurchase; |
| | | case "com.apple.InterAppAudio": |
| | | return InterAppAudio; |
| | | case "com.apple.KeychainSharing": |
| | | return KeychainSharing; |
| | | case "com.apple.Maps.iOS": |
| | | return Maps; |
| | | case "com.apple.VPNLite": |
| | | return PersonalVPN; |
| | | case "com.apple.Push": |
| | | return PushNotifications; |
| | | case "com.apple.Siri": |
| | | return Siri; |
| | | case "com.apple.Wallet": |
| | | return Wallet; |
| | | case "WAC": |
| | | return WirelessAccessoryConfiguration; |
| | | default: |
| | | return null; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 14df1fd8e025b40dc9dafd42aa4bc7b3 |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Text; |
| | | using System.Text.RegularExpressions; |
| | | using System.IO; |
| | | |
| | | #if UNITY_XCODE_API_BUILD |
| | | namespace UnityEditor.iOS.Xcode |
| | | #else |
| | | namespace UnityEditor.iOS.Xcode.Custom |
| | | #endif |
| | | { |
| | | internal class PBXPath |
| | | { |
| | | /// Replaces '\' with '/'. We need to apply this function to all paths that come from the user |
| | | /// of the API because we store paths to pbxproj and on windows we may get path with '\' slashes |
| | | /// instead of '/' slashes |
| | | public static string FixSlashes(string path) |
| | | { |
| | | if (path == null) |
| | | return null; |
| | | return path.Replace('\\', '/'); |
| | | } |
| | | |
| | | public static void Combine(string path1, PBXSourceTree tree1, string path2, PBXSourceTree tree2, |
| | | out string resPath, out PBXSourceTree resTree) |
| | | { |
| | | if (tree2 == PBXSourceTree.Group) |
| | | { |
| | | resPath = Combine(path1, path2); |
| | | resTree = tree1; |
| | | return; |
| | | } |
| | | |
| | | resPath = path2; |
| | | resTree = tree2; |
| | | } |
| | | |
| | | // Combines two paths |
| | | public static string Combine(string path1, string path2) |
| | | { |
| | | if (path2.StartsWith("/")) |
| | | return path2; |
| | | if (path1.EndsWith("/")) |
| | | return path1 + path2; |
| | | if (path1 == "") |
| | | return path2; |
| | | if (path2 == "") |
| | | return path1; |
| | | return path1 + "/" + path2; |
| | | } |
| | | |
| | | public static string GetDirectory(string path) |
| | | { |
| | | path = path.TrimEnd('/'); |
| | | int pos = path.LastIndexOf('/'); |
| | | if (pos == -1) |
| | | return ""; |
| | | else |
| | | return path.Substring(0, pos); |
| | | } |
| | | |
| | | public static string GetCurrentDirectory() |
| | | { |
| | | if (Environment.OSVersion.Platform != PlatformID.MacOSX && |
| | | Environment.OSVersion.Platform != PlatformID.Unix) |
| | | { |
| | | throw new Exception("PBX project compatible current directory can only obtained on OSX"); |
| | | } |
| | | |
| | | string path = Directory.GetCurrentDirectory(); |
| | | path = FixSlashes(path); |
| | | if (!IsPathRooted(path)) |
| | | return "/" + path; |
| | | return path; |
| | | } |
| | | |
| | | public static string GetFilename(string path) |
| | | { |
| | | int pos = path.LastIndexOf('/'); |
| | | if (pos == -1) |
| | | return path; |
| | | else |
| | | return path.Substring(pos + 1); |
| | | } |
| | | |
| | | public static bool IsPathRooted(string path) |
| | | { |
| | | if (path == null || path.Length == 0) |
| | | return false; |
| | | return path[0] == '/'; |
| | | } |
| | | |
| | | public static string GetFullPath(string path) |
| | | { |
| | | if (IsPathRooted(path)) |
| | | return path; |
| | | else |
| | | return Combine(GetCurrentDirectory(), path); |
| | | } |
| | | |
| | | public static string[] Split(string path) |
| | | { |
| | | if (string.IsNullOrEmpty(path)) |
| | | return new string[]{}; |
| | | return path.Split(new[]{'/'}, StringSplitOptions.RemoveEmptyEntries); |
| | | } |
| | | } |
| | | |
| | | } // UnityEditor.iOS.Xcode |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 6145845643c89442db964c33ae65f55b |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | 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<PBXBuildFileData>; |
| | | using PBXFileReferenceSection = KnownSectionBase<PBXFileReferenceData>; |
| | | using PBXGroupSection = KnownSectionBase<PBXGroupData>; |
| | | using PBXContainerItemProxySection = KnownSectionBase<PBXContainerItemProxyData>; |
| | | using PBXReferenceProxySection = KnownSectionBase<PBXReferenceProxyData>; |
| | | using PBXSourcesBuildPhaseSection = KnownSectionBase<PBXSourcesBuildPhaseData>; |
| | | using PBXFrameworksBuildPhaseSection= KnownSectionBase<PBXFrameworksBuildPhaseData>; |
| | | using PBXResourcesBuildPhaseSection = KnownSectionBase<PBXResourcesBuildPhaseData>; |
| | | using PBXCopyFilesBuildPhaseSection = KnownSectionBase<PBXCopyFilesBuildPhaseData>; |
| | | using PBXShellScriptBuildPhaseSection = KnownSectionBase<PBXShellScriptBuildPhaseData>; |
| | | using PBXVariantGroupSection = KnownSectionBase<PBXVariantGroupData>; |
| | | using PBXNativeTargetSection = KnownSectionBase<PBXNativeTargetData>; |
| | | using PBXTargetDependencySection = KnownSectionBase<PBXTargetDependencyData>; |
| | | using XCBuildConfigurationSection = KnownSectionBase<XCBuildConfigurationData>; |
| | | using XCConfigurationListSection = KnownSectionBase<XCConfigurationListData>; |
| | | using UnknownSection = KnownSectionBase<PBXObjectData>; |
| | | |
| | | // 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<PBXBuildFileData> 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); } |
| | | |
| | | /// <summary> |
| | | /// Returns the path to PBX project in the given Unity build path. This function can only |
| | | /// be used in Unity-generated projects |
| | | /// </summary> |
| | | /// <param name="buildPath">The project build path</param> |
| | | /// <returns>The path to the PBX project file that can later be opened via ReadFromFile function</returns> |
| | | public static string GetPBXProjectPath(string buildPath) |
| | | { |
| | | return PBXPath.Combine(buildPath, "Unity-iPhone.xcodeproj/project.pbxproj"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <returns>The default main target name.</returns> |
| | | public static string GetUnityTargetName() |
| | | { |
| | | return "Unity-iPhone"; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <returns>The default test target name.</returns> |
| | | public static string GetUnityTestTargetName() |
| | | { |
| | | return "Unity-iPhone Tests"; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <returns>The GUID of the project.</returns> |
| | | public string ProjectGuid() |
| | | { |
| | | return project.project.guid; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <returns>The name of the native target.</returns> |
| | | /// <param name="name">The GUID identifying the native target.</param> |
| | | public string TargetGuidByName(string name) |
| | | { |
| | | foreach (var entry in nativeTargets.GetEntries()) |
| | | if (entry.Value.name == name) |
| | | return entry.Key; |
| | | return null; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Checks if files with the given extension are known to PBXProject. |
| | | /// </summary> |
| | | /// <returns>Returns <c>true</c> if is the extension is known, <c>false</c> otherwise.</returns> |
| | | /// <param name="ext">The file extension (leading dot is not necessary, but accepted).</param> |
| | | public static bool IsKnownExtension(string ext) |
| | | { |
| | | return FileTypeUtils.IsKnownExtension(ext); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Checks if files with the given extension are known to PBXProject. |
| | | /// Returns <c>true</c> if the extension is not known by PBXProject. |
| | | /// </summary> |
| | | /// <returns>Returns <c>true</c> if is the extension is known, <c>false</c> otherwise.</returns> |
| | | /// <param name="ext">The file extension (leading dot is not necessary, but accepted).</param> |
| | | 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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]]. |
| | | /// </summary> |
| | | /// <returns>The GUID of the added file. It can later be used to add the file for building to targets, etc.</returns> |
| | | /// <param name="path">The physical path to the file on the filesystem.</param> |
| | | /// <param name="projectPath">The project path to the file.</param> |
| | | /// <param name="sourceTree">The source tree the path is relative to. By default it's [[PBXSourceTree.Source]]. |
| | | /// The [[PBXSourceTree.Group]] tree is not supported.</param> |
| | | 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); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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]]. |
| | | /// </summary> |
| | | /// <returns>The GUID of the added folder reference. It can later be used to add the file for building to targets, etc.</returns> |
| | | /// <param name="path">The physical path to the folder on the filesystem.</param> |
| | | /// <param name="projectPath">The project path to the folder.</param> |
| | | /// <param name="sourceTree">The source tree the path is relative to. By default it's [[PBXSourceTree.Source]]. |
| | | /// The [[PBXSourceTree.Group]] tree is not supported.</param> |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="fileGuid">The file guid returned by [[AddFile]] or [[AddFolderReference]].</param> |
| | | public void AddFileToBuild(string targetGuid, string fileGuid) |
| | | { |
| | | AddBuildFileImpl(targetGuid, fileGuid, false, null); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="fileGuid">The file guid returned by [[AddFile]] or [[AddFolderReference]].</param> |
| | | /// <param name="compileFlags">Compile flags to use.</param> |
| | | public void AddFileToBuildWithFlags(string targetGuid, string fileGuid, string compileFlags) |
| | | { |
| | | AddBuildFileImpl(targetGuid, fileGuid, false, compileFlags); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="sectionGuid">The GUID of the section to add the file to.</param> |
| | | /// <param name="fileGuid">The file guid returned by [[AddFile]] or [[AddFolderReference]].</param> |
| | | 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); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <returns>The compile flags for the specified file.</returns> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="fileGuid">The GUID of the file.</param> |
| | | public List<string> GetCompileFlagsForFile(string targetGuid, string fileGuid) |
| | | { |
| | | var buildFile = BuildFilesGetForSourceFile(targetGuid, fileGuid); |
| | | if (buildFile == null) |
| | | return null; |
| | | if (buildFile.compileFlags == null) |
| | | return new List<string>(); |
| | | return new List<string>(buildFile.compileFlags.Split(new char[]{' '}, StringSplitOptions.RemoveEmptyEntries)); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Sets the compilation flags for the given file in the given target. |
| | | /// </summary> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="fileGuid">The GUID of the file.</param> |
| | | /// <param name="compileFlags">The list of compile flags or null if the flags should be unset.</param> |
| | | public void SetCompileFlagsForFile(string targetGuid, string fileGuid, List<string> compileFlags) |
| | | { |
| | | var buildFile = BuildFilesGetForSourceFile(targetGuid, fileGuid); |
| | | if (buildFile == null) |
| | | return; |
| | | if (compileFlags == null) |
| | | buildFile.compileFlags = null; |
| | | else |
| | | buildFile.compileFlags = string.Join(" ", compileFlags.ToArray()); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="fileGuid">The GUID of the file.</param> |
| | | /// <param name="tag">The name of the asset tag.</param> |
| | | 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); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="fileGuid">The GUID of the file.</param> |
| | | /// <param name="tag">The name of the asset tag.</param> |
| | | 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); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="tag">The name of the asset tag.</param> |
| | | public void AddAssetTagToDefaultInstall(string targetGuid, string tag) |
| | | { |
| | | if (!project.project.knownAssetTags.Contains(tag)) |
| | | return; |
| | | AddBuildProperty(targetGuid, "ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS", tag); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="tag">The name of the asset tag.</param> |
| | | public void RemoveAssetTagFromDefaultInstall(string targetGuid, string tag) |
| | | { |
| | | UpdateBuildProperty(targetGuid, "ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS", null, new[]{tag}); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="tag">The name of the asset tag.</param> |
| | | 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); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Checks if the project contains a file with the given physical path. |
| | | /// The search is performed across all absolute source trees. |
| | | /// </summary> |
| | | /// <returns>Returns <c>true</c> if the project contains the file, <c>false</c> otherwise.</returns> |
| | | /// <param name="path">The physical path of the file.</param> |
| | | public bool ContainsFileByRealPath(string path) |
| | | { |
| | | return FindFileGuidByRealPath(path) != null; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Checks if the project contains a file with the given physical path. |
| | | /// </summary> |
| | | /// <returns>Returns <c>true</c> if the project contains the file, <c>false</c> otherwise.</returns> |
| | | /// <param name="path">The physical path of the file.</param> |
| | | /// <param name="sourceTree">The source tree path is relative to. The [[PBXSourceTree.Group]] tree is not supported.</param> |
| | | 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Checks if the project contains a file with the given project path. |
| | | /// </summary> |
| | | /// <returns>Returns <c>true</c> if the project contains the file, <c>false</c> otherwise.</returns> |
| | | /// <param name="path">The project path of the file.</param> |
| | | public bool ContainsFileByProjectPath(string path) |
| | | { |
| | | return FindFileGuidByProjectPath(path) != null; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <returns>Returns <c>true</c> if the given framework is a dependency of the given target, |
| | | /// <c>false</c> otherwise.</returns> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="framework">The name of the framework. The extension of the filename must be ".framework".</param> |
| | | 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); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="framework">The name of the framework. The extension of the filename must be ".framework".</param> |
| | | /// <param name="weak"><c>true</c> if the framework is optional (i.e. weakly linked) required, |
| | | /// <c>false</c> if the framework is required.</param> |
| | | 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); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="framework">The name of the framework. The extension of the filename must be ".framework".</param> |
| | | 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); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Finds a file with the given physical path in the project, if any. |
| | | /// </summary> |
| | | /// <returns>The GUID of the file if the search succeeded, null otherwise.</returns> |
| | | /// <param name="path">The physical path of the file.</param> |
| | | /// <param name="sourceTree">The source tree path is relative to. The [[PBXSourceTree.Group]] tree is not supported.</param> |
| | | 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Finds a file with the given physical path in the project, if any. |
| | | /// The search is performed across all absolute source trees. |
| | | /// </summary> |
| | | /// <returns>The GUID of the file if the search succeeded, null otherwise.</returns> |
| | | /// <param name="path">The physical path of the file.</param> |
| | | 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Finds a file with the given project path in the project, if any. |
| | | /// </summary> |
| | | /// <returns>The GUID of the file if the search succeeded, null otherwise.</returns> |
| | | /// <param name="path">The project path of the file.</param> |
| | | public string FindFileGuidByProjectPath(string path) |
| | | { |
| | | path = PBXPath.FixSlashes(path); |
| | | var fileRef = FileRefsGetByProjectPath(path); |
| | | if (fileRef != null) |
| | | return fileRef.guid; |
| | | return null; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Removes given file from the list of files to build for the given target. |
| | | /// </summary> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="fileGuid">The GUID of the file or folder reference.</param> |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="fileGuid">The GUID of the file or folder reference.</param> |
| | | 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<string> children = new List<string>(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<string> GetGroupChildrenFiles(string projectPath) |
| | | { |
| | | projectPath = PBXPath.FixSlashes(projectPath); |
| | | PBXGroupData gr = GroupsGetByProjectPath(projectPath); |
| | | if (gr == null) |
| | | return null; |
| | | var res = new List<string>(); |
| | | 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<string> GetGroupChildrenFilesRefs(string projectPath) |
| | | { |
| | | projectPath = PBXPath.FixSlashes(projectPath); |
| | | PBXGroupData gr = GroupsGetByProjectPath(projectPath); |
| | | if (gr == null) |
| | | return new HashSet<string>(); |
| | | HashSet<string> res = new HashSet<string>(); |
| | | foreach (var guid in gr.children) |
| | | { |
| | | PBXFileReferenceData fileRef = FileRefsGet(guid); |
| | | if (fileRef != null) |
| | | res.Add(fileRef.path); |
| | | } |
| | | return res == null ? new HashSet<string> () : res; |
| | | } |
| | | |
| | | internal HashSet<string> GetFileRefsByProjectPaths(IEnumerable<string> paths) |
| | | { |
| | | HashSet<string> ret = new HashSet<string>(); |
| | | 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <returns>The GUID of the new target.</returns> |
| | | /// <param name="name">The name of the new target.</param> |
| | | /// <param name="ext">The file extension of the target artifact (leading dot is not necessary, but accepted).</param> |
| | | /// <param name="type">The type of the target. For example: |
| | | /// "com.apple.product-type.app-extension" - App extension, |
| | | /// "com.apple.product-type.application.watchapp2" - WatchKit 2 application</param> |
| | | 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<string> GetAllTargetGuids() |
| | | { |
| | | var targets = new List<string>(); |
| | | |
| | | targets.Add(project.project.guid); |
| | | targets.AddRange(nativeTargets.GetGuids()); |
| | | |
| | | return targets; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Returns the file reference of the artifact created by building target. |
| | | /// </summary> |
| | | /// <returns>The file reference of the artifact created by building target.</returns> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | public string GetTargetProductFileRef(string targetGuid) |
| | | { |
| | | return nativeTargets[targetGuid].productReference; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Sets up a dependency between two targets. |
| | | /// </summary> |
| | | /// <param name="targetGuid">The GUID of the target that is depending on the dependency.</param> |
| | | /// <param name="targetDependencyGuid">The GUID of the dependency target</param> |
| | | 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); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Returns the GUID of build configuration with the given name for the specific target. |
| | | /// Null is returned if such configuration does not exist. |
| | | /// </summary> |
| | | /// <returns>The GUID of the build configuration or null if it does not exist.</returns> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="name">The name of the build configuration.</param> |
| | | 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <returns>An array of build config names.</returns> |
| | | public IEnumerable<string> BuildConfigNames() |
| | | { |
| | | var names = new List<string>(); |
| | | // 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="name">The name of the build configuration.</param> |
| | | public void AddBuildConfig(string name) |
| | | { |
| | | foreach (var targetGuid in GetAllTargetGuids()) |
| | | AddBuildConfigForTarget(targetGuid, name); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="name">The name of the build configuration.</param> |
| | | public void RemoveBuildConfig(string name) |
| | | { |
| | | foreach (var targetGuid in GetAllTargetGuids()) |
| | | RemoveBuildConfigForTarget(targetGuid, name); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Returns the GUID of sources build phase for the given target. |
| | | /// </summary> |
| | | /// <returns>Returns the GUID of the existing phase or null if it does not exist.</returns> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <returns>Returns the GUID of the new phase.</returns> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Returns the GUID of resources build phase for the given target. |
| | | /// </summary> |
| | | /// <returns>Returns the GUID of the existing phase or null if it does not exist.</returns> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <returns>Returns the GUID of the new phase.</returns> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Returns the GUID of frameworks build phase for the given target. |
| | | /// </summary> |
| | | /// <returns>Returns the GUID of the existing phase or null if it does not exist.</returns> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <returns>Returns the GUID of the new phase.</returns> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <returns>Returns the GUID of the matching phase or null if it does not exist.</returns> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="name">The name of the phase.</param> |
| | | /// <param name="dstPath">The destination path.</param> |
| | | /// <param name="subfolderSpec">The "subfolder spec". The following usages are known: |
| | | /// "10" for embedding frameworks; |
| | | /// "13" for embedding app extension content; |
| | | /// "16" for embedding watch content</param> |
| | | 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <returns>Returns the GUID of the new phase.</returns> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="name">The name of the phase.</param> |
| | | /// <param name="dstPath">The destination path.</param> |
| | | /// <param name="subfolderSpec">The "subfolder spec". The following usages are known: |
| | | /// "10" for embedding frameworks; |
| | | /// "13" for embedding app extension content; |
| | | /// "16" for embedding watch content</param> |
| | | 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="name">The name of the build property.</param> |
| | | /// <param name="value">The value of the build property.</param> |
| | | public void AddBuildProperty(string targetGuid, string name, string value) |
| | | { |
| | | foreach (string guid in buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs) |
| | | AddBuildPropertyForConfig(guid, name, value); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="targetGuids">The GUIDs of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="name">The name of the build property.</param> |
| | | /// <param name="value">The value of the build property.</param> |
| | | public void AddBuildProperty(IEnumerable<string> targetGuids, string name, string value) |
| | | { |
| | | foreach (string t in targetGuids) |
| | | AddBuildProperty(t, name, value); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="configGuid">The GUID of the build configuration as returned by [[BuildConfigByName()]].</param> |
| | | /// <param name="name">The name of the build property.</param> |
| | | /// <param name="value">The value of the build property.</param> |
| | | public void AddBuildPropertyForConfig(string configGuid, string name, string value) |
| | | { |
| | | buildConfigs[configGuid].AddProperty(name, value); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="configGuids">The GUIDs of the build configurations as returned by [[BuildConfigByName()]].</param> |
| | | /// <param name="name">The name of the build property.</param> |
| | | /// <param name="value">The value of the build property.</param> |
| | | public void AddBuildPropertyForConfig(IEnumerable<string> configGuids, string name, string value) |
| | | { |
| | | foreach (string guid in configGuids) |
| | | AddBuildPropertyForConfig(guid, name, value); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="name">The name of the build property.</param> |
| | | /// <param name="value">The value of the build property.</param> |
| | | public void SetBuildProperty(string targetGuid, string name, string value) |
| | | { |
| | | foreach (string guid in buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs) |
| | | SetBuildPropertyForConfig(guid, name, value); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="targetGuids">The GUIDs of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="name">The name of the build property.</param> |
| | | /// <param name="value">The value of the build property.</param> |
| | | public void SetBuildProperty(IEnumerable<string> 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<string> 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<string> 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<string> configGuids, string name) |
| | | { |
| | | foreach (string guid in configGuids) |
| | | RemoveBuildPropertyForConfig(guid, name); |
| | | } |
| | | |
| | | internal void RemoveBuildPropertyValueList(string targetGuid, string name, IEnumerable<string> valueList) |
| | | { |
| | | foreach (string guid in buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs) |
| | | RemoveBuildPropertyValueListForConfig(guid, name, valueList); |
| | | } |
| | | internal void RemoveBuildPropertyValueList(IEnumerable<string> targetGuids, string name, IEnumerable<string> valueList) |
| | | { |
| | | foreach (string t in targetGuids) |
| | | RemoveBuildPropertyValueList(t, name, valueList); |
| | | } |
| | | internal void RemoveBuildPropertyValueListForConfig(string configGuid, string name, IEnumerable<string> valueList) |
| | | { |
| | | buildConfigs[configGuid].RemovePropertyValueList(name, valueList); |
| | | } |
| | | internal void RemoveBuildPropertyValueListForConfig(IEnumerable<string> configGuids, string name, IEnumerable<string> 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<string> addValues, IEnumerable<string> removeValues) |
| | | { |
| | | foreach (string guid in buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs) |
| | | UpdateBuildPropertyForConfig(guid, name, addValues, removeValues); |
| | | } |
| | | public void UpdateBuildProperty(IEnumerable<string> targetGuids, string name, |
| | | IEnumerable<string> addValues, IEnumerable<string> removeValues) |
| | | { |
| | | foreach (string t in targetGuids) |
| | | UpdateBuildProperty(t, name, addValues, removeValues); |
| | | } |
| | | public void UpdateBuildPropertyForConfig(string configGuid, string name, |
| | | IEnumerable<string> addValues, IEnumerable<string> 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<string> configGuids, string name, |
| | | IEnumerable<string> addValues, IEnumerable<string> 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<string> 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<string, PBXNativeTargetData> 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 |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 259f8edae64294e05b179fb43b2e950f |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System.Collections.Generic; |
| | | using System.Text; |
| | | using System.Text.RegularExpressions; |
| | | using System.IO; |
| | | using System; |
| | | #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<PBXBuildFileData>; |
| | | using PBXFileReferenceSection = KnownSectionBase<PBXFileReferenceData>; |
| | | using PBXGroupSection = KnownSectionBase<PBXGroupData>; |
| | | using PBXContainerItemProxySection = KnownSectionBase<PBXContainerItemProxyData>; |
| | | using PBXReferenceProxySection = KnownSectionBase<PBXReferenceProxyData>; |
| | | using PBXSourcesBuildPhaseSection = KnownSectionBase<PBXSourcesBuildPhaseData>; |
| | | using PBXFrameworksBuildPhaseSection= KnownSectionBase<PBXFrameworksBuildPhaseData>; |
| | | using PBXResourcesBuildPhaseSection = KnownSectionBase<PBXResourcesBuildPhaseData>; |
| | | using PBXCopyFilesBuildPhaseSection = KnownSectionBase<PBXCopyFilesBuildPhaseData>; |
| | | using PBXShellScriptBuildPhaseSection = KnownSectionBase<PBXShellScriptBuildPhaseData>; |
| | | using PBXVariantGroupSection = KnownSectionBase<PBXVariantGroupData>; |
| | | using PBXNativeTargetSection = KnownSectionBase<PBXNativeTargetData>; |
| | | using PBXTargetDependencySection = KnownSectionBase<PBXTargetDependencyData>; |
| | | using XCBuildConfigurationSection = KnownSectionBase<XCBuildConfigurationData>; |
| | | using XCConfigurationListSection = KnownSectionBase<XCConfigurationListData>; |
| | | using UnknownSection = KnownSectionBase<PBXObjectData>; |
| | | |
| | | internal class PBXProjectData |
| | | { |
| | | private Dictionary<string, SectionBase> m_Section = null; |
| | | private PBXElementDict m_RootElements = null; |
| | | private PBXElementDict m_UnknownObjects = null; |
| | | private string m_ObjectVersion = null; |
| | | private List<string> m_SectionOrder = null; |
| | | |
| | | private Dictionary<string, UnknownSection> m_UnknownSections; |
| | | private PBXBuildFileSection buildFiles = null; // use BuildFiles* methods instead of manipulating directly |
| | | private PBXFileReferenceSection fileRefs = null; // use FileRefs* methods instead of manipulating directly |
| | | private PBXGroupSection groups = null; // use Groups* methods instead of manipulating directly |
| | | public PBXContainerItemProxySection containerItems = null; |
| | | public PBXReferenceProxySection references = null; |
| | | public PBXSourcesBuildPhaseSection sources = null; |
| | | public PBXFrameworksBuildPhaseSection frameworks = null; |
| | | public PBXResourcesBuildPhaseSection resources = null; |
| | | public PBXCopyFilesBuildPhaseSection copyFiles = null; |
| | | public PBXShellScriptBuildPhaseSection shellScripts = null; |
| | | public PBXNativeTargetSection nativeTargets = null; |
| | | public PBXTargetDependencySection targetDependencies = null; |
| | | public PBXVariantGroupSection variantGroups = null; |
| | | public XCBuildConfigurationSection buildConfigs = null; |
| | | public XCConfigurationListSection buildConfigLists = null; |
| | | public PBXProjectSection project = null; |
| | | |
| | | // FIXME: create a separate PBXObject tree to represent these relationships |
| | | |
| | | // A build file can be represented only once in all *BuildPhaseSection sections, thus |
| | | // we can simplify the cache by not caring about the file extension |
| | | private Dictionary<string, Dictionary<string, PBXBuildFileData>> m_FileGuidToBuildFileMap = null; |
| | | private Dictionary<string, PBXFileReferenceData> m_ProjectPathToFileRefMap = null; |
| | | private Dictionary<string, string> m_FileRefGuidToProjectPathMap = null; |
| | | private Dictionary<PBXSourceTree, Dictionary<string, PBXFileReferenceData>> m_RealPathToFileRefMap = null; |
| | | private Dictionary<string, PBXGroupData> m_ProjectPathToGroupMap = null; |
| | | private Dictionary<string, string> m_GroupGuidToProjectPathMap = null; |
| | | private Dictionary<string, PBXGroupData> m_GuidToParentGroupMap = null; |
| | | |
| | | public PBXBuildFileData BuildFilesGet(string guid) |
| | | { |
| | | return buildFiles[guid]; |
| | | } |
| | | |
| | | // targetGuid is the guid of the target that contains the section that contains the buildFile |
| | | public void BuildFilesAdd(string targetGuid, PBXBuildFileData buildFile) |
| | | { |
| | | if (!m_FileGuidToBuildFileMap.ContainsKey(targetGuid)) |
| | | m_FileGuidToBuildFileMap[targetGuid] = new Dictionary<string, PBXBuildFileData>(); |
| | | m_FileGuidToBuildFileMap[targetGuid][buildFile.fileRef] = buildFile; |
| | | buildFiles.AddEntry(buildFile); |
| | | } |
| | | |
| | | public void BuildFilesRemove(string targetGuid, string fileGuid) |
| | | { |
| | | var buildFile = BuildFilesGetForSourceFile(targetGuid, fileGuid); |
| | | if (buildFile != null) |
| | | { |
| | | m_FileGuidToBuildFileMap[targetGuid].Remove(buildFile.fileRef); |
| | | buildFiles.RemoveEntry(buildFile.guid); |
| | | } |
| | | } |
| | | |
| | | public PBXBuildFileData BuildFilesGetForSourceFile(string targetGuid, string fileGuid) |
| | | { |
| | | if (!m_FileGuidToBuildFileMap.ContainsKey(targetGuid)) |
| | | return null; |
| | | if (!m_FileGuidToBuildFileMap[targetGuid].ContainsKey(fileGuid)) |
| | | return null; |
| | | return m_FileGuidToBuildFileMap[targetGuid][fileGuid]; |
| | | } |
| | | |
| | | public IEnumerable<PBXBuildFileData> BuildFilesGetAll() |
| | | { |
| | | return buildFiles.GetObjects(); |
| | | } |
| | | |
| | | public void FileRefsAdd(string realPath, string projectPath, PBXGroupData parent, PBXFileReferenceData fileRef) |
| | | { |
| | | fileRefs.AddEntry(fileRef); |
| | | m_ProjectPathToFileRefMap.Add(projectPath, fileRef); |
| | | m_FileRefGuidToProjectPathMap.Add(fileRef.guid, projectPath); |
| | | m_RealPathToFileRefMap[fileRef.tree].Add(realPath, fileRef); // FIXME |
| | | m_GuidToParentGroupMap.Add(fileRef.guid, parent); |
| | | } |
| | | |
| | | public PBXFileReferenceData FileRefsGet(string guid) |
| | | { |
| | | return fileRefs[guid]; |
| | | } |
| | | |
| | | public PBXFileReferenceData FileRefsGetByRealPath(string path, PBXSourceTree sourceTree) |
| | | { |
| | | if (m_RealPathToFileRefMap[sourceTree].ContainsKey(path)) |
| | | return m_RealPathToFileRefMap[sourceTree][path]; |
| | | return null; |
| | | } |
| | | |
| | | public PBXFileReferenceData FileRefsGetByProjectPath(string path) |
| | | { |
| | | if (m_ProjectPathToFileRefMap.ContainsKey(path)) |
| | | return m_ProjectPathToFileRefMap[path]; |
| | | return null; |
| | | } |
| | | |
| | | public void FileRefsRemove(string guid) |
| | | { |
| | | PBXFileReferenceData fileRef = fileRefs[guid]; |
| | | fileRefs.RemoveEntry(guid); |
| | | m_ProjectPathToFileRefMap.Remove(m_FileRefGuidToProjectPathMap[guid]); |
| | | m_FileRefGuidToProjectPathMap.Remove(guid); |
| | | foreach (var tree in FileTypeUtils.AllAbsoluteSourceTrees()) |
| | | m_RealPathToFileRefMap[tree].Remove(fileRef.path); |
| | | m_GuidToParentGroupMap.Remove(guid); |
| | | } |
| | | |
| | | public PBXGroupData GroupsGet(string guid) |
| | | { |
| | | return groups[guid]; |
| | | } |
| | | |
| | | public PBXGroupData GroupsGetByChild(string childGuid) |
| | | { |
| | | return m_GuidToParentGroupMap[childGuid]; |
| | | } |
| | | |
| | | public PBXGroupData GroupsGetMainGroup() |
| | | { |
| | | return groups[project.project.mainGroup]; |
| | | } |
| | | |
| | | /// Returns the source group identified by sourceGroup. If sourceGroup is empty or null, |
| | | /// root group is returned. If no group is found, null is returned. |
| | | public PBXGroupData GroupsGetByProjectPath(string sourceGroup) |
| | | { |
| | | if (m_ProjectPathToGroupMap.ContainsKey(sourceGroup)) |
| | | return m_ProjectPathToGroupMap[sourceGroup]; |
| | | return null; |
| | | } |
| | | |
| | | public void GroupsAdd(string projectPath, PBXGroupData parent, PBXGroupData gr) |
| | | { |
| | | m_ProjectPathToGroupMap.Add(projectPath, gr); |
| | | m_GroupGuidToProjectPathMap.Add(gr.guid, projectPath); |
| | | m_GuidToParentGroupMap.Add(gr.guid, parent); |
| | | groups.AddEntry(gr); |
| | | } |
| | | |
| | | public void GroupsAddDuplicate(PBXGroupData gr) |
| | | { |
| | | groups.AddEntry(gr); |
| | | } |
| | | |
| | | public void GroupsRemove(string guid) |
| | | { |
| | | m_ProjectPathToGroupMap.Remove(m_GroupGuidToProjectPathMap[guid]); |
| | | m_GroupGuidToProjectPathMap.Remove(guid); |
| | | m_GuidToParentGroupMap.Remove(guid); |
| | | groups.RemoveEntry(guid); |
| | | } |
| | | |
| | | // This function returns a build section that a particular file should be automatically added to. |
| | | // Returns null for non-buildable file types. |
| | | // Throws an exception if the file should be added to a section, but that particular section does not exist for given target |
| | | // Note that for unknown file types we add them to resource build sections |
| | | public FileGUIDListBase BuildSectionAny(PBXNativeTargetData target, string path, bool isFolderRef) |
| | | { |
| | | string ext = Path.GetExtension(path); |
| | | var phase = FileTypeUtils.GetFileType(ext, isFolderRef); |
| | | switch (phase) { |
| | | case PBXFileType.Framework: |
| | | foreach (var guid in target.phases) |
| | | if (frameworks.HasEntry(guid)) |
| | | return frameworks[guid]; |
| | | break; |
| | | case PBXFileType.Resource: |
| | | foreach (var guid in target.phases) |
| | | if (resources.HasEntry(guid)) |
| | | return resources[guid]; |
| | | break; |
| | | case PBXFileType.Source: |
| | | foreach (var guid in target.phases) |
| | | if (sources.HasEntry(guid)) |
| | | return sources[guid]; |
| | | break; |
| | | case PBXFileType.CopyFile: |
| | | foreach (var guid in target.phases) |
| | | if (copyFiles.HasEntry(guid)) |
| | | return copyFiles[guid]; |
| | | break; |
| | | case PBXFileType.ShellScript: |
| | | foreach (var guid in target.phases) |
| | | if (shellScripts.HasEntry(guid)) |
| | | return shellScripts[guid]; |
| | | break; |
| | | case PBXFileType.NotBuildable: |
| | | return null; |
| | | } |
| | | throw new Exception(String.Format("The given path {0} does not refer to a file in a known build section", path)); |
| | | } |
| | | |
| | | public FileGUIDListBase BuildSectionAny(string sectionGuid) |
| | | { |
| | | if (frameworks.HasEntry(sectionGuid)) |
| | | return frameworks[sectionGuid]; |
| | | if (resources.HasEntry(sectionGuid)) |
| | | return resources[sectionGuid]; |
| | | if (sources.HasEntry(sectionGuid)) |
| | | return sources[sectionGuid]; |
| | | if (copyFiles.HasEntry(sectionGuid)) |
| | | return copyFiles[sectionGuid]; |
| | | if (shellScripts.HasEntry(sectionGuid)) |
| | | return shellScripts[sectionGuid]; |
| | | return null; |
| | | } |
| | | |
| | | void RefreshBuildFilesMapForBuildFileGuidList(Dictionary<string, PBXBuildFileData> mapForTarget, |
| | | FileGUIDListBase list) |
| | | { |
| | | foreach (string guid in list.files) |
| | | { |
| | | var buildFile = buildFiles[guid]; |
| | | mapForTarget[buildFile.fileRef] = buildFile; |
| | | } |
| | | } |
| | | |
| | | void RefreshMapsForGroupChildren(string projectPath, string realPath, PBXSourceTree realPathTree, PBXGroupData parent) |
| | | { |
| | | var children = new List<string>(parent.children); |
| | | foreach (string guid in children) |
| | | { |
| | | PBXFileReferenceData fileRef = fileRefs[guid]; |
| | | string pPath; |
| | | string rPath; |
| | | PBXSourceTree rTree; |
| | | |
| | | if (fileRef != null) |
| | | { |
| | | pPath = PBXPath.Combine(projectPath, fileRef.name); |
| | | PBXPath.Combine(realPath, realPathTree, fileRef.path, fileRef.tree, out rPath, out rTree); |
| | | |
| | | if (!m_ProjectPathToFileRefMap.ContainsKey(pPath)) |
| | | { |
| | | m_ProjectPathToFileRefMap.Add(pPath, fileRef); |
| | | } |
| | | if (!m_FileRefGuidToProjectPathMap.ContainsKey(fileRef.guid)) |
| | | { |
| | | m_FileRefGuidToProjectPathMap.Add(fileRef.guid, pPath); |
| | | } |
| | | if (!m_RealPathToFileRefMap[rTree].ContainsKey(rPath)) |
| | | { |
| | | m_RealPathToFileRefMap[rTree].Add(rPath, fileRef); |
| | | } |
| | | if (!m_GuidToParentGroupMap.ContainsKey(guid)) |
| | | { |
| | | m_GuidToParentGroupMap.Add(guid, parent); |
| | | } |
| | | |
| | | continue; |
| | | } |
| | | |
| | | PBXGroupData gr = groups[guid]; |
| | | if (gr != null) |
| | | { |
| | | pPath = PBXPath.Combine(projectPath, gr.name); |
| | | PBXPath.Combine(realPath, realPathTree, gr.path, gr.tree, out rPath, out rTree); |
| | | |
| | | if (!m_ProjectPathToGroupMap.ContainsKey(pPath)) |
| | | { |
| | | m_ProjectPathToGroupMap.Add(pPath, gr); |
| | | } |
| | | if (!m_GroupGuidToProjectPathMap.ContainsKey(gr.guid)) |
| | | { |
| | | m_GroupGuidToProjectPathMap.Add(gr.guid, pPath); |
| | | } |
| | | if (!m_GuidToParentGroupMap.ContainsKey(guid)) |
| | | { |
| | | m_GuidToParentGroupMap.Add(guid, parent); |
| | | } |
| | | |
| | | RefreshMapsForGroupChildren(pPath, rPath, rTree, gr); |
| | | } |
| | | } |
| | | } |
| | | |
| | | void RefreshAuxMaps() |
| | | { |
| | | foreach (var targetEntry in nativeTargets.GetEntries()) |
| | | { |
| | | var map = new Dictionary<string, PBXBuildFileData>(); |
| | | foreach (string phaseGuid in targetEntry.Value.phases) |
| | | { |
| | | if (frameworks.HasEntry(phaseGuid)) |
| | | RefreshBuildFilesMapForBuildFileGuidList(map, frameworks[phaseGuid]); |
| | | if (resources.HasEntry(phaseGuid)) |
| | | RefreshBuildFilesMapForBuildFileGuidList(map, resources[phaseGuid]); |
| | | if (sources.HasEntry(phaseGuid)) |
| | | RefreshBuildFilesMapForBuildFileGuidList(map, sources[phaseGuid]); |
| | | if (copyFiles.HasEntry(phaseGuid)) |
| | | RefreshBuildFilesMapForBuildFileGuidList(map, copyFiles[phaseGuid]); |
| | | } |
| | | m_FileGuidToBuildFileMap[targetEntry.Key] = map; |
| | | } |
| | | RefreshMapsForGroupChildren("", "", PBXSourceTree.Source, GroupsGetMainGroup()); |
| | | } |
| | | |
| | | public void Clear() |
| | | { |
| | | buildFiles = new PBXBuildFileSection("PBXBuildFile"); |
| | | fileRefs = new PBXFileReferenceSection("PBXFileReference"); |
| | | groups = new PBXGroupSection("PBXGroup"); |
| | | containerItems = new PBXContainerItemProxySection("PBXContainerItemProxy"); |
| | | references = new PBXReferenceProxySection("PBXReferenceProxy"); |
| | | sources = new PBXSourcesBuildPhaseSection("PBXSourcesBuildPhase"); |
| | | frameworks = new PBXFrameworksBuildPhaseSection("PBXFrameworksBuildPhase"); |
| | | resources = new PBXResourcesBuildPhaseSection("PBXResourcesBuildPhase"); |
| | | copyFiles = new PBXCopyFilesBuildPhaseSection("PBXCopyFilesBuildPhase"); |
| | | shellScripts = new PBXShellScriptBuildPhaseSection("PBXShellScriptBuildPhase"); |
| | | nativeTargets = new PBXNativeTargetSection("PBXNativeTarget"); |
| | | targetDependencies = new PBXTargetDependencySection("PBXTargetDependency"); |
| | | variantGroups = new PBXVariantGroupSection("PBXVariantGroup"); |
| | | buildConfigs = new XCBuildConfigurationSection("XCBuildConfiguration"); |
| | | buildConfigLists = new XCConfigurationListSection("XCConfigurationList"); |
| | | project = new PBXProjectSection(); |
| | | m_UnknownSections = new Dictionary<string, UnknownSection>(); |
| | | |
| | | m_Section = new Dictionary<string, SectionBase> |
| | | { |
| | | { "PBXBuildFile", buildFiles }, |
| | | { "PBXFileReference", fileRefs }, |
| | | { "PBXGroup", groups }, |
| | | { "PBXContainerItemProxy", containerItems }, |
| | | { "PBXReferenceProxy", references }, |
| | | { "PBXSourcesBuildPhase", sources }, |
| | | { "PBXFrameworksBuildPhase", frameworks }, |
| | | { "PBXResourcesBuildPhase", resources }, |
| | | { "PBXCopyFilesBuildPhase", copyFiles }, |
| | | { "PBXShellScriptBuildPhase", shellScripts }, |
| | | { "PBXNativeTarget", nativeTargets }, |
| | | { "PBXTargetDependency", targetDependencies }, |
| | | { "PBXVariantGroup", variantGroups }, |
| | | { "XCBuildConfiguration", buildConfigs }, |
| | | { "XCConfigurationList", buildConfigLists }, |
| | | |
| | | { "PBXProject", project }, |
| | | }; |
| | | m_RootElements = new PBXElementDict(); |
| | | m_UnknownObjects = new PBXElementDict(); |
| | | m_ObjectVersion = null; |
| | | m_SectionOrder = new List<string>{ |
| | | "PBXBuildFile", "PBXContainerItemProxy", "PBXCopyFilesBuildPhase", "PBXFileReference", |
| | | "PBXFrameworksBuildPhase", "PBXGroup", "PBXNativeTarget", "PBXProject", "PBXReferenceProxy", |
| | | "PBXResourcesBuildPhase", "PBXShellScriptBuildPhase", "PBXSourcesBuildPhase", "PBXTargetDependency", |
| | | "PBXVariantGroup", "XCBuildConfiguration", "XCConfigurationList" |
| | | }; |
| | | m_FileGuidToBuildFileMap = new Dictionary<string, Dictionary<string, PBXBuildFileData>>(); |
| | | m_ProjectPathToFileRefMap = new Dictionary<string, PBXFileReferenceData>(); |
| | | m_FileRefGuidToProjectPathMap = new Dictionary<string, string>(); |
| | | m_RealPathToFileRefMap = new Dictionary<PBXSourceTree, Dictionary<string, PBXFileReferenceData>>(); |
| | | foreach (var tree in FileTypeUtils.AllAbsoluteSourceTrees()) |
| | | m_RealPathToFileRefMap.Add(tree, new Dictionary<string, PBXFileReferenceData>()); |
| | | m_ProjectPathToGroupMap = new Dictionary<string, PBXGroupData>(); |
| | | m_GroupGuidToProjectPathMap = new Dictionary<string, string>(); |
| | | m_GuidToParentGroupMap = new Dictionary<string, PBXGroupData>(); |
| | | } |
| | | |
| | | private void BuildCommentMapForBuildFiles(GUIDToCommentMap comments, List<string> guids, string sectName) |
| | | { |
| | | foreach (var guid in guids) |
| | | { |
| | | var buildFile = BuildFilesGet(guid); |
| | | if (buildFile != null) |
| | | { |
| | | var fileRef = FileRefsGet(buildFile.fileRef); |
| | | if (fileRef != null) |
| | | comments.Add(guid, String.Format("{0} in {1}", fileRef.name, sectName)); |
| | | else |
| | | { |
| | | var reference = references[buildFile.fileRef]; |
| | | if (reference != null) |
| | | comments.Add(guid, String.Format("{0} in {1}", reference.path, sectName)); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private GUIDToCommentMap BuildCommentMap() |
| | | { |
| | | GUIDToCommentMap comments = new GUIDToCommentMap(); |
| | | |
| | | // buildFiles are handled below |
| | | // filerefs are handled below |
| | | foreach (var e in groups.GetObjects()) |
| | | comments.Add(e.guid, e.name); |
| | | foreach (var e in containerItems.GetObjects()) |
| | | comments.Add(e.guid, "PBXContainerItemProxy"); |
| | | foreach (var e in references.GetObjects()) |
| | | comments.Add(e.guid, e.path); |
| | | foreach (var e in sources.GetObjects()) |
| | | { |
| | | comments.Add(e.guid, "Sources"); |
| | | BuildCommentMapForBuildFiles(comments, e.files, "Sources"); |
| | | } |
| | | foreach (var e in resources.GetObjects()) |
| | | { |
| | | comments.Add(e.guid, "Resources"); |
| | | BuildCommentMapForBuildFiles(comments, e.files, "Resources"); |
| | | } |
| | | foreach (var e in frameworks.GetObjects()) |
| | | { |
| | | comments.Add(e.guid, "Frameworks"); |
| | | BuildCommentMapForBuildFiles(comments, e.files, "Frameworks"); |
| | | } |
| | | foreach (var e in copyFiles.GetObjects()) |
| | | { |
| | | string sectName = e.name; |
| | | if (sectName == null) |
| | | sectName = "CopyFiles"; |
| | | comments.Add(e.guid, sectName); |
| | | BuildCommentMapForBuildFiles(comments, e.files, sectName); |
| | | } |
| | | foreach (var e in shellScripts.GetObjects()) |
| | | comments.Add(e.guid, "ShellScript"); |
| | | foreach (var e in targetDependencies.GetObjects()) |
| | | comments.Add(e.guid, "PBXTargetDependency"); |
| | | foreach (var e in nativeTargets.GetObjects()) |
| | | { |
| | | comments.Add(e.guid, e.name); |
| | | comments.Add(e.buildConfigList, String.Format("Build configuration list for PBXNativeTarget \"{0}\"", e.name)); |
| | | } |
| | | foreach (var e in variantGroups.GetObjects()) |
| | | comments.Add(e.guid, e.name); |
| | | foreach (var e in buildConfigs.GetObjects()) |
| | | comments.Add(e.guid, e.name); |
| | | foreach (var e in project.GetObjects()) |
| | | { |
| | | comments.Add(e.guid, "Project object"); |
| | | comments.Add(e.buildConfigList, "Build configuration list for PBXProject \"Unity-iPhone\""); // FIXME: project name is hardcoded |
| | | } |
| | | foreach (var e in fileRefs.GetObjects()) |
| | | comments.Add(e.guid, e.name); |
| | | if (m_RootElements.Contains("rootObject") && m_RootElements["rootObject"] is PBXElementString) |
| | | comments.Add(m_RootElements["rootObject"].AsString(), "Project object"); |
| | | |
| | | return comments; |
| | | } |
| | | |
| | | private static PBXElementDict ParseContent(string content) |
| | | { |
| | | TokenList tokens = Lexer.Tokenize(content); |
| | | var parser = new Parser(tokens); |
| | | TreeAST ast = parser.ParseTree(); |
| | | return Serializer.ParseTreeAST(ast, tokens, content); |
| | | } |
| | | |
| | | public void ReadFromStream(TextReader sr) |
| | | { |
| | | Clear(); |
| | | m_RootElements = ParseContent(sr.ReadToEnd()); |
| | | |
| | | if (!m_RootElements.Contains("objects")) |
| | | throw new Exception("Invalid PBX project file: no objects element"); |
| | | |
| | | var objects = m_RootElements["objects"].AsDict(); |
| | | m_RootElements.Remove("objects"); |
| | | m_RootElements.SetString("objects", "OBJMARKER"); |
| | | |
| | | if (m_RootElements.Contains("objectVersion")) |
| | | { |
| | | m_ObjectVersion = m_RootElements["objectVersion"].AsString(); |
| | | m_RootElements.Remove("objectVersion"); |
| | | } |
| | | |
| | | var allGuids = new List<string>(); |
| | | string prevSectionName = null; |
| | | foreach (var kv in objects.values) |
| | | { |
| | | allGuids.Add(kv.Key); |
| | | var el = kv.Value; |
| | | |
| | | if (!(el is PBXElementDict) || !el.AsDict().Contains("isa")) |
| | | { |
| | | m_UnknownObjects.values.Add(kv.Key, el); |
| | | continue; |
| | | } |
| | | var dict = el.AsDict(); |
| | | var sectionName = dict["isa"].AsString(); |
| | | |
| | | if (m_Section.ContainsKey(sectionName)) |
| | | { |
| | | var section = m_Section[sectionName]; |
| | | section.AddObject(kv.Key, dict); |
| | | } |
| | | else |
| | | { |
| | | UnknownSection section; |
| | | if (m_UnknownSections.ContainsKey(sectionName)) |
| | | section = m_UnknownSections[sectionName]; |
| | | else |
| | | { |
| | | section = new UnknownSection(sectionName); |
| | | m_UnknownSections.Add(sectionName, section); |
| | | } |
| | | section.AddObject(kv.Key, dict); |
| | | |
| | | // update section order |
| | | if (!m_SectionOrder.Contains(sectionName)) |
| | | { |
| | | int pos = 0; |
| | | if (prevSectionName != null) |
| | | { |
| | | // this never fails, because we already added any previous unknown sections |
| | | // to m_SectionOrder |
| | | pos = m_SectionOrder.FindIndex(x => x == prevSectionName); |
| | | pos += 1; |
| | | } |
| | | m_SectionOrder.Insert(pos, sectionName); |
| | | } |
| | | } |
| | | prevSectionName = sectionName; |
| | | } |
| | | RepairStructure(allGuids); |
| | | RefreshAuxMaps(); |
| | | } |
| | | |
| | | |
| | | public string WriteToString() |
| | | { |
| | | var commentMap = BuildCommentMap(); |
| | | var emptyChecker = new PropertyCommentChecker(); |
| | | var emptyCommentMap = new GUIDToCommentMap(); |
| | | |
| | | // since we need to add custom comments, the serialization is much more complex |
| | | StringBuilder objectsSb = new StringBuilder(); |
| | | if (m_ObjectVersion != null) // objectVersion comes right before objects |
| | | objectsSb.AppendFormat("objectVersion = {0};\n\t", m_ObjectVersion); |
| | | objectsSb.Append("objects = {"); |
| | | foreach (string sectionName in m_SectionOrder) |
| | | { |
| | | if (m_Section.ContainsKey(sectionName)) |
| | | m_Section[sectionName].WriteSection(objectsSb, commentMap); |
| | | else if (m_UnknownSections.ContainsKey(sectionName)) |
| | | m_UnknownSections[sectionName].WriteSection(objectsSb, commentMap); |
| | | } |
| | | foreach (var kv in m_UnknownObjects.values) |
| | | Serializer.WriteDictKeyValue(objectsSb, kv.Key, kv.Value, 2, false, emptyChecker, emptyCommentMap); |
| | | objectsSb.Append("\n\t};"); |
| | | |
| | | StringBuilder contentSb = new StringBuilder(); |
| | | contentSb.Append("// !$*UTF8*$!\n"); |
| | | Serializer.WriteDict(contentSb, m_RootElements, 0, false, |
| | | new PropertyCommentChecker(new string[]{"rootObject/*"}), commentMap); |
| | | contentSb.Append("\n"); |
| | | string content = contentSb.ToString(); |
| | | |
| | | content = content.Replace("objects = OBJMARKER;", objectsSb.ToString()); |
| | | return content; |
| | | } |
| | | |
| | | // This method walks the project structure and removes invalid entries. |
| | | void RepairStructure(List<string> allGuids) |
| | | { |
| | | var guidSet = new Dictionary<string, bool>(); // emulate HashSet on .Net 2.0 |
| | | foreach (var guid in allGuids) |
| | | guidSet.Add(guid, false); |
| | | |
| | | while (RepairStructureImpl(guidSet) == true) |
| | | ; |
| | | } |
| | | |
| | | /* Iterates the given guid list and removes all guids that are not in allGuids dictionary. |
| | | */ |
| | | static void RemoveMissingGuidsFromGuidList(PBX.GUIDList guidList, Dictionary<string, bool> allGuids) |
| | | { |
| | | List<string> guidsToRemove = null; |
| | | foreach (var guid in guidList) |
| | | { |
| | | if (!allGuids.ContainsKey(guid)) |
| | | { |
| | | if (guidsToRemove == null) |
| | | guidsToRemove = new List<string>(); |
| | | guidsToRemove.Add(guid); |
| | | } |
| | | } |
| | | if (guidsToRemove != null) |
| | | { |
| | | foreach (var guid in guidsToRemove) |
| | | guidList.RemoveGUID(guid); |
| | | } |
| | | } |
| | | |
| | | /* Removes objects from the given @a section for which @a checker returns true. |
| | | Also removes the guids of the removed elements from allGuids dictionary. |
| | | Returns true if any objects were removed. |
| | | */ |
| | | static bool RemoveObjectsFromSection<T>(KnownSectionBase<T> section, |
| | | Dictionary<string, bool> allGuids, |
| | | Func<T, bool> checker) where T : PBXObjectData, new() |
| | | { |
| | | List<string> guidsToRemove = null; |
| | | foreach (var kv in section.GetEntries()) |
| | | { |
| | | if (checker(kv.Value)) |
| | | { |
| | | if (guidsToRemove == null) |
| | | guidsToRemove = new List<string>(); |
| | | guidsToRemove.Add(kv.Key); |
| | | } |
| | | } |
| | | if (guidsToRemove != null) |
| | | { |
| | | foreach (var guid in guidsToRemove) |
| | | { |
| | | section.RemoveEntry(guid); |
| | | allGuids.Remove(guid); |
| | | } |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | // Returns true if changes were done and one should call RepairStructureImpl again |
| | | bool RepairStructureImpl(Dictionary<string, bool> allGuids) |
| | | { |
| | | bool changed = false; |
| | | |
| | | // PBXBuildFile |
| | | changed |= RemoveObjectsFromSection(buildFiles, allGuids, |
| | | o => (o.fileRef == null || !allGuids.ContainsKey(o.fileRef))); |
| | | // PBXFileReference / fileRefs not cleaned |
| | | |
| | | // PBXGroup |
| | | changed |= RemoveObjectsFromSection(groups, allGuids, o => o.children == null); |
| | | foreach (var o in groups.GetObjects()) |
| | | RemoveMissingGuidsFromGuidList(o.children, allGuids); |
| | | |
| | | // PBXContainerItem / containerItems not cleaned |
| | | // PBXReferenceProxy / references not cleaned |
| | | |
| | | // PBXSourcesBuildPhase |
| | | changed |= RemoveObjectsFromSection(sources, allGuids, o => o.files == null); |
| | | foreach (var o in sources.GetObjects()) |
| | | RemoveMissingGuidsFromGuidList(o.files, allGuids); |
| | | // PBXFrameworksBuildPhase |
| | | changed |= RemoveObjectsFromSection(frameworks, allGuids, o => o.files == null); |
| | | foreach (var o in frameworks.GetObjects()) |
| | | RemoveMissingGuidsFromGuidList(o.files, allGuids); |
| | | // PBXResourcesBuildPhase |
| | | changed |= RemoveObjectsFromSection(resources, allGuids, o => o.files == null); |
| | | foreach (var o in resources.GetObjects()) |
| | | RemoveMissingGuidsFromGuidList(o.files, allGuids); |
| | | // PBXCopyFilesBuildPhase |
| | | changed |= RemoveObjectsFromSection(copyFiles, allGuids, o => o.files == null); |
| | | foreach (var o in copyFiles.GetObjects()) |
| | | RemoveMissingGuidsFromGuidList(o.files, allGuids); |
| | | // PBXShellScriptsBuildPhase |
| | | changed |= RemoveObjectsFromSection(shellScripts, allGuids, o => o.files == null); |
| | | foreach (var o in shellScripts.GetObjects()) |
| | | RemoveMissingGuidsFromGuidList(o.files, allGuids); |
| | | |
| | | // PBXNativeTarget |
| | | changed |= RemoveObjectsFromSection(nativeTargets, allGuids, o => o.phases == null); |
| | | foreach (var o in nativeTargets.GetObjects()) |
| | | RemoveMissingGuidsFromGuidList(o.phases, allGuids); |
| | | |
| | | // PBXTargetDependency / targetDependencies not cleaned |
| | | |
| | | // PBXVariantGroup |
| | | changed |= RemoveObjectsFromSection(variantGroups, allGuids, o => o.children == null); |
| | | foreach (var o in variantGroups.GetObjects()) |
| | | RemoveMissingGuidsFromGuidList(o.children, allGuids); |
| | | |
| | | // XCBuildConfiguration / buildConfigs not cleaned |
| | | |
| | | // XCConfigurationList |
| | | changed |= RemoveObjectsFromSection(buildConfigLists, allGuids, o => o.buildConfigs == null); |
| | | foreach (var o in buildConfigLists.GetObjects()) |
| | | RemoveMissingGuidsFromGuidList(o.buildConfigs, allGuids); |
| | | |
| | | // PBXProject project not cleaned |
| | | return changed; |
| | | } |
| | | } |
| | | |
| | | } // namespace UnityEditor.iOS.Xcode |
| | | |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 7097deba75a7d4f299fc93ec23b54299 |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System.Collections.Generic; |
| | | using System.Text; |
| | | using System.Text.RegularExpressions; |
| | | using System.IO; |
| | | using System; |
| | | #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.Extensions |
| | | #else |
| | | namespace UnityEditor.iOS.Xcode.Custom.Extensions |
| | | #endif |
| | | { |
| | | /* This class implements a number of static methods for performing common tasks |
| | | on xcode projects. |
| | | TODO: Make sure enough stuff is exposed so that it's possible to perform the tasks |
| | | without using internal APIs |
| | | */ |
| | | public static class PBXProjectExtensions |
| | | { |
| | | // Create a wrapper class so that collection initializers work and we can have a |
| | | // compact notation. Note that we can't use Dictionary because the keys may be duplicate |
| | | internal class FlagList : List<KeyValuePair<string, string>> |
| | | { |
| | | public void Add(string flag, string value) |
| | | { |
| | | Add(new KeyValuePair<string, string>(flag, value)); |
| | | } |
| | | } |
| | | |
| | | internal static FlagList appExtensionReleaseBuildFlags = new FlagList |
| | | { |
| | | // { "INFOPLIST_FILE", <path/to/info.plist> }, |
| | | { "LD_RUNPATH_SEARCH_PATHS", "$(inherited)" }, |
| | | { "LD_RUNPATH_SEARCH_PATHS", "@executable_path/Frameworks" }, |
| | | { "LD_RUNPATH_SEARCH_PATHS", "@executable_path/../../Frameworks" }, |
| | | // { "PRODUCT_BUNDLE_IDENTIFIER", "<bundle id>" }, |
| | | { "PRODUCT_NAME", "$(TARGET_NAME)" }, |
| | | { "SKIP_INSTALL", "YES" }, |
| | | }; |
| | | |
| | | internal static FlagList appExtensionDebugBuildFlags = new FlagList |
| | | { |
| | | // { "INFOPLIST_FILE", <path/to/info.plist> }, |
| | | { "LD_RUNPATH_SEARCH_PATHS", "$(inherited)" }, |
| | | { "LD_RUNPATH_SEARCH_PATHS", "@executable_path/Frameworks" }, |
| | | { "LD_RUNPATH_SEARCH_PATHS", "@executable_path/../../Frameworks" }, |
| | | // { "PRODUCT_BUNDLE_IDENTIFIER", "<bundle id>" }, |
| | | { "PRODUCT_NAME", "$(TARGET_NAME)" }, |
| | | { "SKIP_INSTALL", "YES" }, |
| | | }; |
| | | |
| | | internal static FlagList watchExtensionReleaseBuildFlags = new FlagList |
| | | { |
| | | { "ASSETCATALOG_COMPILER_COMPLICATION_NAME", "Complication" }, |
| | | { "CLANG_ANALYZER_NONNULL", "YES" }, |
| | | { "CLANG_WARN_DOCUMENTATION_COMMENTS", "YES" }, |
| | | { "CLANG_WARN_INFINITE_RECURSION", "YES" }, |
| | | { "CLANG_WARN_SUSPICIOUS_MOVE", "YES" }, |
| | | { "DEBUG_INFORMATION_FORMAT", "dwarf-with-dsym" }, |
| | | { "GCC_NO_COMMON_BLOCKS", "YES" }, |
| | | //{ "INFOPLIST_FILE", "<path/to/Info.plist>" }, |
| | | { "LD_RUNPATH_SEARCH_PATHS", "$(inherited)" }, |
| | | { "LD_RUNPATH_SEARCH_PATHS", "@executable_path/Frameworks" }, |
| | | { "LD_RUNPATH_SEARCH_PATHS", "@executable_path/../../Frameworks" }, |
| | | // { "PRODUCT_BUNDLE_IDENTIFIER", "<bundle id>" }, |
| | | { "PRODUCT_NAME", "${TARGET_NAME}" }, |
| | | { "SDKROOT", "watchos" }, |
| | | { "SKIP_INSTALL", "YES" }, |
| | | { "TARGETED_DEVICE_FAMILY", "4" }, |
| | | { "WATCHOS_DEPLOYMENT_TARGET", "3.1" }, |
| | | // the following are needed to override project settings in Unity Xcode project |
| | | { "ARCHS", "$(ARCHS_STANDARD)" }, |
| | | { "SUPPORTED_PLATFORMS", "watchos" }, |
| | | { "SUPPORTED_PLATFORMS", "watchsimulator" }, |
| | | }; |
| | | |
| | | internal static FlagList watchExtensionDebugBuildFlags = new FlagList |
| | | { |
| | | { "ASSETCATALOG_COMPILER_COMPLICATION_NAME", "Complication" }, |
| | | { "CLANG_ANALYZER_NONNULL", "YES" }, |
| | | { "CLANG_WARN_DOCUMENTATION_COMMENTS", "YES" }, |
| | | { "CLANG_WARN_INFINITE_RECURSION", "YES" }, |
| | | { "CLANG_WARN_SUSPICIOUS_MOVE", "YES" }, |
| | | { "DEBUG_INFORMATION_FORMAT", "dwarf" }, |
| | | { "ENABLE_TESTABILITY", "YES" }, |
| | | { "GCC_NO_COMMON_BLOCKS", "YES" }, |
| | | // { "INFOPLIST_FILE", "<path/to/Info.plist>" }, |
| | | { "LD_RUNPATH_SEARCH_PATHS", "$(inherited)" }, |
| | | { "LD_RUNPATH_SEARCH_PATHS", "@executable_path/Frameworks" }, |
| | | { "LD_RUNPATH_SEARCH_PATHS", "@executable_path/../../Frameworks" }, |
| | | // { "PRODUCT_BUNDLE_IDENTIFIER", "<bundle id>" }, |
| | | { "PRODUCT_NAME", "${TARGET_NAME}" }, |
| | | { "SDKROOT", "watchos" }, |
| | | { "SKIP_INSTALL", "YES" }, |
| | | { "TARGETED_DEVICE_FAMILY", "4" }, |
| | | { "WATCHOS_DEPLOYMENT_TARGET", "3.1" }, |
| | | // the following are needed to override project settings in Unity Xcode project |
| | | { "ARCHS", "$(ARCHS_STANDARD)" }, |
| | | { "SUPPORTED_PLATFORMS", "watchos" }, |
| | | { "SUPPORTED_PLATFORMS", "watchsimulator" }, |
| | | }; |
| | | |
| | | internal static FlagList watchAppReleaseBuildFlags = new FlagList |
| | | { |
| | | { "ASSETCATALOG_COMPILER_APPICON_NAME", "AppIcon" }, |
| | | { "CLANG_ANALYZER_NONNULL", "YES" }, |
| | | { "CLANG_WARN_DOCUMENTATION_COMMENTS", "YES" }, |
| | | { "CLANG_WARN_INFINITE_RECURSION", "YES" }, |
| | | { "CLANG_WARN_SUSPICIOUS_MOVE", "YES" }, |
| | | { "DEBUG_INFORMATION_FORMAT", "dwarf-with-dsym" }, |
| | | { "GCC_NO_COMMON_BLOCKS", "YES" }, |
| | | //{ "IBSC_MODULE", "the extension target name with ' ' replaced with '_'" }, |
| | | //{ "INFOPLIST_FILE", "<path/to/Info.plist>" }, |
| | | //{ "PRODUCT_BUNDLE_IDENTIFIER", "<bundle id>" }, |
| | | { "PRODUCT_NAME", "$(TARGET_NAME)" }, |
| | | { "SDKROOT", "watchos" }, |
| | | { "SKIP_INSTALL", "YES" }, |
| | | { "TARGETED_DEVICE_FAMILY", "4" }, |
| | | { "WATCHOS_DEPLOYMENT_TARGET", "3.1" }, |
| | | // the following are needed to override project settings in Unity Xcode project |
| | | { "ARCHS", "$(ARCHS_STANDARD)" }, |
| | | { "SUPPORTED_PLATFORMS", "watchos" }, |
| | | { "SUPPORTED_PLATFORMS", "watchsimulator" }, |
| | | }; |
| | | |
| | | internal static FlagList watchAppDebugBuildFlags = new FlagList |
| | | { |
| | | { "ASSETCATALOG_COMPILER_APPICON_NAME", "AppIcon" }, |
| | | { "CLANG_ANALYZER_NONNULL", "YES" }, |
| | | { "CLANG_WARN_DOCUMENTATION_COMMENTS", "YES" }, |
| | | { "CLANG_WARN_INFINITE_RECURSION", "YES" }, |
| | | { "CLANG_WARN_SUSPICIOUS_MOVE", "YES" }, |
| | | { "DEBUG_INFORMATION_FORMAT", "dwarf" }, |
| | | { "ENABLE_TESTABILITY", "YES" }, |
| | | { "GCC_NO_COMMON_BLOCKS", "YES" }, |
| | | //{ "IBSC_MODULE", "the extension target name with ' ' replaced with '_'" }, |
| | | //{ "INFOPLIST_FILE", "<path/to/Info.plist>" }, |
| | | //{ "PRODUCT_BUNDLE_IDENTIFIER", "<bundle id>" }, |
| | | { "PRODUCT_NAME", "$(TARGET_NAME)" }, |
| | | { "SDKROOT", "watchos" }, |
| | | { "SKIP_INSTALL", "YES" }, |
| | | { "TARGETED_DEVICE_FAMILY", "4" }, |
| | | { "WATCHOS_DEPLOYMENT_TARGET", "3.1" }, |
| | | // the following are needed to override project settings in Unity Xcode project |
| | | { "ARCHS", "$(ARCHS_STANDARD)" }, |
| | | { "SUPPORTED_PLATFORMS", "watchos" }, |
| | | { "SUPPORTED_PLATFORMS", "watchsimulator" }, |
| | | }; |
| | | |
| | | static void SetBuildFlagsFromDict(this PBXProject proj, string configGuid, IEnumerable<KeyValuePair<string, string>> data) |
| | | { |
| | | foreach (var kv in data) |
| | | proj.AddBuildPropertyForConfig(configGuid, kv.Key, kv.Value); |
| | | } |
| | | |
| | | internal static void SetDefaultAppExtensionReleaseBuildFlags(this PBXProject proj, string configGuid) |
| | | { |
| | | SetBuildFlagsFromDict(proj, configGuid, appExtensionReleaseBuildFlags); |
| | | } |
| | | |
| | | internal static void SetDefaultAppExtensionDebugBuildFlags(this PBXProject proj, string configGuid) |
| | | { |
| | | SetBuildFlagsFromDict(proj, configGuid, appExtensionDebugBuildFlags); |
| | | } |
| | | |
| | | internal static void SetDefaultWatchExtensionReleaseBuildFlags(this PBXProject proj, string configGuid) |
| | | { |
| | | SetBuildFlagsFromDict(proj, configGuid, watchExtensionReleaseBuildFlags); |
| | | } |
| | | |
| | | internal static void SetDefaultWatchExtensionDebugBuildFlags(this PBXProject proj, string configGuid) |
| | | { |
| | | SetBuildFlagsFromDict(proj, configGuid, watchExtensionDebugBuildFlags); |
| | | } |
| | | |
| | | internal static void SetDefaultWatchAppReleaseBuildFlags(this PBXProject proj, string configGuid) |
| | | { |
| | | SetBuildFlagsFromDict(proj, configGuid, watchAppReleaseBuildFlags); |
| | | } |
| | | |
| | | internal static void SetDefaultWatchAppDebugBuildFlags(this PBXProject proj, string configGuid) |
| | | { |
| | | SetBuildFlagsFromDict(proj, configGuid, watchAppDebugBuildFlags); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Creates an app extension. |
| | | /// </summary> |
| | | /// <returns>The GUID of the new target.</returns> |
| | | /// <param name="proj">A project passed as this argument.</param> |
| | | /// <param name="mainTargetGuid">The GUID of the main target to link the app to.</param> |
| | | /// <param name="name">The name of the app extension.</param> |
| | | /// <param name="bundleId">The bundle ID of the app extension. The bundle ID must be |
| | | /// prefixed with the parent app bundle ID.</param> |
| | | /// <param name="infoPlistPath">Path to the app extension Info.plist document.</param> |
| | | public static string AddAppExtension(this PBXProject proj, string mainTargetGuid, |
| | | string name, string bundleId, string infoPlistPath) |
| | | { |
| | | string ext = ".appex"; |
| | | var newTargetGuid = proj.AddTarget(name, ext, "com.apple.product-type.app-extension"); |
| | | |
| | | foreach (var configName in proj.BuildConfigNames()) |
| | | { |
| | | var configGuid = proj.BuildConfigByName(newTargetGuid, configName); |
| | | if (configName.Contains("Debug")) |
| | | SetDefaultAppExtensionDebugBuildFlags(proj, configGuid); |
| | | else |
| | | SetDefaultAppExtensionReleaseBuildFlags(proj, configGuid); |
| | | proj.SetBuildPropertyForConfig(configGuid, "INFOPLIST_FILE", infoPlistPath); |
| | | proj.SetBuildPropertyForConfig(configGuid, "PRODUCT_BUNDLE_IDENTIFIER", bundleId); |
| | | } |
| | | |
| | | proj.AddSourcesBuildPhase(newTargetGuid); |
| | | proj.AddResourcesBuildPhase(newTargetGuid); |
| | | proj.AddFrameworksBuildPhase(newTargetGuid); |
| | | string copyFilesPhaseGuid = proj.AddCopyFilesBuildPhase(mainTargetGuid, "Embed App Extensions", "", "13"); |
| | | proj.AddFileToBuildSection(mainTargetGuid, copyFilesPhaseGuid, proj.GetTargetProductFileRef(newTargetGuid)); |
| | | |
| | | proj.AddTargetDependency(mainTargetGuid, newTargetGuid); |
| | | |
| | | return newTargetGuid; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Creates a watch application. |
| | | /// </summary> |
| | | /// <returns>The GUID of the new target.</returns> |
| | | /// <param name="proj">A project passed as this argument.</param> |
| | | /// <param name="mainTargetGuid">The GUID of the main target to link the watch app to.</param> |
| | | /// <param name="watchExtensionTargetGuid">The GUID of watch extension as returned by [[AddWatchExtension()]].</param> |
| | | /// <param name="name">The name of the watch app. It must the same as the name of the watch extension.</param> |
| | | /// <param name="bundleId">The bundle ID of the watch app.</param> |
| | | /// <param name="infoPlistPath">Path to the watch app Info.plist document.</param> |
| | | public static string AddWatchApp(this PBXProject proj, string mainTargetGuid, string watchExtensionTargetGuid, |
| | | string name, string bundleId, string infoPlistPath) |
| | | { |
| | | var newTargetGuid = proj.AddTarget(name, ".app", "com.apple.product-type.application.watchapp2"); |
| | | |
| | | var isbcModuleName = proj.nativeTargets[watchExtensionTargetGuid].name.Replace(" ", "_"); |
| | | |
| | | foreach (var configName in proj.BuildConfigNames()) |
| | | { |
| | | var configGuid = proj.BuildConfigByName(newTargetGuid, configName); |
| | | if (configName.Contains("Debug")) |
| | | SetDefaultWatchAppDebugBuildFlags(proj, configGuid); |
| | | else |
| | | SetDefaultWatchAppReleaseBuildFlags(proj, configGuid); |
| | | proj.SetBuildPropertyForConfig(configGuid, "PRODUCT_BUNDLE_IDENTIFIER", bundleId); |
| | | proj.SetBuildPropertyForConfig(configGuid, "INFOPLIST_FILE", infoPlistPath); |
| | | proj.SetBuildPropertyForConfig(configGuid, "IBSC_MODULE", isbcModuleName); |
| | | } |
| | | |
| | | proj.AddResourcesBuildPhase(newTargetGuid); |
| | | string copyFilesGuid = proj.AddCopyFilesBuildPhase(newTargetGuid, "Embed App Extensions", "", "13"); |
| | | proj.AddFileToBuildSection(newTargetGuid, copyFilesGuid, proj.GetTargetProductFileRef(watchExtensionTargetGuid)); |
| | | |
| | | string copyWatchFilesGuid = proj.AddCopyFilesBuildPhase(mainTargetGuid, "Embed Watch Content", "$(CONTENTS_FOLDER_PATH)/Watch", "16"); |
| | | proj.AddFileToBuildSection(mainTargetGuid, copyWatchFilesGuid, proj.GetTargetProductFileRef(newTargetGuid)); |
| | | |
| | | proj.AddTargetDependency(newTargetGuid, watchExtensionTargetGuid); |
| | | proj.AddTargetDependency(mainTargetGuid, newTargetGuid); |
| | | |
| | | return newTargetGuid; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Creates a watch extension. |
| | | /// </summary> |
| | | /// <returns>The GUID of the new target.</returns> |
| | | /// <param name="proj">A project passed as this argument.</param> |
| | | /// <param name="mainTarget">The GUID of the main target to link the watch extension to.</param> |
| | | /// <param name="name">The name of the watch extension.</param> |
| | | /// <param name="bundleId">The bundle ID of the watch extension. The bundle ID must be |
| | | /// prefixed with the parent watch app bundle ID.</param> |
| | | /// <param name="infoPlistPath">Path to the watch extension Info.plist document.</param> |
| | | public static string AddWatchExtension(this PBXProject proj, string mainTarget, |
| | | string name, string bundleId, string infoPlistPath) |
| | | { |
| | | var newTargetGuid = proj.AddTarget(name, ".appex", "com.apple.product-type.watchkit2-extension"); |
| | | |
| | | foreach (var configName in proj.BuildConfigNames()) |
| | | { |
| | | var configGuid = proj.BuildConfigByName(newTargetGuid, configName); |
| | | if (configName.Contains("Debug")) |
| | | SetDefaultWatchExtensionDebugBuildFlags(proj, configGuid); |
| | | else |
| | | SetDefaultWatchExtensionReleaseBuildFlags(proj, configGuid); |
| | | proj.SetBuildPropertyForConfig(configGuid, "PRODUCT_BUNDLE_IDENTIFIER", bundleId); |
| | | proj.SetBuildPropertyForConfig(configGuid, "INFOPLIST_FILE", infoPlistPath); |
| | | } |
| | | |
| | | proj.AddSourcesBuildPhase(newTargetGuid); |
| | | proj.AddResourcesBuildPhase(newTargetGuid); |
| | | proj.AddFrameworksBuildPhase(newTargetGuid); |
| | | |
| | | return newTargetGuid; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds an external project dependency to the project. |
| | | /// </summary> |
| | | /// <param name="path">The path to the external Xcode project (the .xcodeproj file).</param> |
| | | /// <param name="projectPath">The project path to the new project.</param> |
| | | /// <param name="sourceTree">The source tree the path is relative to. The [[PBXSourceTree.Group]] tree is not supported.</param> |
| | | internal static void AddExternalProjectDependency(this PBXProject proj, 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"); |
| | | proj.GroupsAddDuplicate(productGroup); // don't use GroupsAdd here |
| | | |
| | | PBXFileReferenceData fileRef = PBXFileReferenceData.CreateFromFile(path, Path.GetFileName(projectPath), |
| | | sourceTree); |
| | | proj.FileRefsAdd(path, projectPath, null, fileRef); |
| | | proj.CreateSourceGroup(PBXPath.GetDirectory(projectPath)).children.AddGUID(fileRef.guid); |
| | | |
| | | proj.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 static void AddExternalLibraryDependency(this PBXProject proj, string targetGuid, string filename, string remoteFileGuid, string projectPath, |
| | | string remoteInfo) |
| | | { |
| | | PBXNativeTargetData target = proj.nativeTargets[targetGuid]; |
| | | filename = PBXPath.FixSlashes(filename); |
| | | projectPath = PBXPath.FixSlashes(projectPath); |
| | | |
| | | // find the products group to put the new library in |
| | | string projectGuid = proj.FindFileGuidByRealPath(projectPath); |
| | | if (projectGuid == null) |
| | | throw new Exception("No such project"); |
| | | |
| | | string productsGroupGuid = null; |
| | | foreach (var projRef in proj.project.project.projectReferences) |
| | | { |
| | | if (projRef.projectRef == projectGuid) |
| | | { |
| | | productsGroupGuid = projRef.group; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (productsGroupGuid == null) |
| | | throw new Exception("Malformed project: no project in project references"); |
| | | |
| | | PBXGroupData productGroup = proj.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); |
| | | proj.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"); |
| | | proj.references.AddEntry(libRef); |
| | | PBXBuildFileData libBuildFile = PBXBuildFileData.CreateFromFile(libRef.guid, false, null); |
| | | proj.BuildFilesAdd(targetGuid, libBuildFile); |
| | | proj.BuildSectionAny(target, ext, false).files.AddGUID(libBuildFile.guid); |
| | | |
| | | // add to products folder |
| | | productGroup.children.AddGUID(libRef.guid); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Configures file for embed framework section for the given native target. |
| | | /// |
| | | /// This function also internally calls <code>proj.AddFileToBuild(targetGuid, fileGuid)</code> |
| | | /// to ensure that the framework is added to the list of linked frameworks. |
| | | /// |
| | | /// If the target has already configured the given file as embedded framework, this function has |
| | | /// no effect. |
| | | /// |
| | | /// 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. |
| | | /// </summary> |
| | | /// <param name="proj">A project passed as this argument.</param> |
| | | /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param> |
| | | /// <param name="fileGuid">The file GUID returned by [[AddFile]] or [[AddFolderReference]].</param> |
| | | public static void AddFileToEmbedFrameworks(this PBXProject proj, string targetGuid, string fileGuid) |
| | | { |
| | | PBXNativeTargetData target = proj.nativeTargets[targetGuid]; |
| | | |
| | | var phaseGuid = proj.AddCopyFilesBuildPhase(targetGuid, "Embed Frameworks", "", "10"); |
| | | var phase = proj.copyFiles[phaseGuid]; |
| | | var frameworkEmbedFileData = proj.FindFrameworkByFileGuid(phase, fileGuid); |
| | | |
| | | if (frameworkEmbedFileData == null) |
| | | { |
| | | frameworkEmbedFileData = PBXBuildFileData.CreateFromFile(fileGuid, false, null); |
| | | proj.BuildFilesAdd(targetGuid, frameworkEmbedFileData); |
| | | |
| | | phase.files.AddGUID(frameworkEmbedFileData.guid); |
| | | } |
| | | |
| | | frameworkEmbedFileData.codeSignOnCopy = true; |
| | | frameworkEmbedFileData.removeHeadersOnCopy = true; |
| | | } |
| | | } |
| | | } // namespace UnityEditor.iOS.Xcode |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 306a50fa08db34ab5ae296d696d5eb1a |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.IO; |
| | | using System.Linq; |
| | | using System.Xml; |
| | | using System.Xml.Linq; |
| | | using System.Xml.XPath; |
| | | |
| | | #if UNITY_XCODE_API_BUILD |
| | | namespace UnityEditor.iOS.Xcode |
| | | #else |
| | | namespace UnityEditor.iOS.Xcode.Custom |
| | | #endif |
| | | { |
| | | |
| | | public class PlistElement |
| | | { |
| | | protected PlistElement() {} |
| | | |
| | | // convenience methods |
| | | public string AsString() { return ((PlistElementString)this).value; } |
| | | public int AsInteger() { return ((PlistElementInteger)this).value; } |
| | | public bool AsBoolean() { return ((PlistElementBoolean)this).value; } |
| | | public PlistElementArray AsArray() { return (PlistElementArray)this; } |
| | | public PlistElementDict AsDict() { return (PlistElementDict)this; } |
| | | public float AsReal() { return ((PlistElementReal)this).value; } |
| | | public DateTime AsDate() { return ((PlistElementDate)this).value; } |
| | | |
| | | public PlistElement this[string key] |
| | | { |
| | | get { return AsDict()[key]; } |
| | | set { AsDict()[key] = value; } |
| | | } |
| | | } |
| | | |
| | | public class PlistElementString : PlistElement |
| | | { |
| | | public PlistElementString(string v) { value = v; } |
| | | |
| | | public string value; |
| | | } |
| | | |
| | | public class PlistElementInteger : PlistElement |
| | | { |
| | | public PlistElementInteger(int v) { value = v; } |
| | | |
| | | public int value; |
| | | } |
| | | |
| | | public class PlistElementReal : PlistElement |
| | | { |
| | | public PlistElementReal(float v) { value = v; } |
| | | |
| | | public float value; |
| | | } |
| | | |
| | | public class PlistElementBoolean : PlistElement |
| | | { |
| | | public PlistElementBoolean(bool v) { value = v; } |
| | | |
| | | public bool value; |
| | | } |
| | | |
| | | public class PlistElementDate : PlistElement |
| | | { |
| | | public PlistElementDate(DateTime date) { value = date; } |
| | | |
| | | public DateTime value; |
| | | } |
| | | |
| | | public class PlistElementDict : PlistElement |
| | | { |
| | | public PlistElementDict() : base() {} |
| | | |
| | | private SortedDictionary<string, PlistElement> m_PrivateValue = new SortedDictionary<string, PlistElement>(); |
| | | public IDictionary<string, PlistElement> values { get { return m_PrivateValue; }} |
| | | |
| | | new public PlistElement this[string key] |
| | | { |
| | | get { |
| | | if (values.ContainsKey(key)) |
| | | return values[key]; |
| | | return null; |
| | | } |
| | | set { this.values[key] = value; } |
| | | } |
| | | |
| | | |
| | | // convenience methods |
| | | public void SetInteger(string key, int val) |
| | | { |
| | | values[key] = new PlistElementInteger(val); |
| | | } |
| | | |
| | | public void SetString(string key, string val) |
| | | { |
| | | values[key] = new PlistElementString(val); |
| | | } |
| | | |
| | | public void SetBoolean(string key, bool val) |
| | | { |
| | | values[key] = new PlistElementBoolean(val); |
| | | } |
| | | |
| | | public void SetDate(string key, DateTime val) |
| | | { |
| | | values[key] = new PlistElementDate(val); |
| | | } |
| | | |
| | | public void SetReal(string key, float val) |
| | | { |
| | | values[key] = new PlistElementReal(val); |
| | | } |
| | | |
| | | public PlistElementArray CreateArray(string key) |
| | | { |
| | | var v = new PlistElementArray(); |
| | | values[key] = v; |
| | | return v; |
| | | } |
| | | |
| | | public PlistElementDict CreateDict(string key) |
| | | { |
| | | var v = new PlistElementDict(); |
| | | values[key] = v; |
| | | return v; |
| | | } |
| | | } |
| | | |
| | | public class PlistElementArray : PlistElement |
| | | { |
| | | public PlistElementArray() : base() {} |
| | | public List<PlistElement> values = new List<PlistElement>(); |
| | | |
| | | // convenience methods |
| | | public void AddString(string val) |
| | | { |
| | | values.Add(new PlistElementString(val)); |
| | | } |
| | | |
| | | public void AddInteger(int val) |
| | | { |
| | | values.Add(new PlistElementInteger(val)); |
| | | } |
| | | |
| | | public void AddBoolean(bool val) |
| | | { |
| | | values.Add(new PlistElementBoolean(val)); |
| | | } |
| | | |
| | | public void AddDate(DateTime val) |
| | | { |
| | | values.Add(new PlistElementDate(val)); |
| | | } |
| | | |
| | | public void AddReal(float val) |
| | | { |
| | | values.Add(new PlistElementReal(val)); |
| | | } |
| | | |
| | | public PlistElementArray AddArray() |
| | | { |
| | | var v = new PlistElementArray(); |
| | | values.Add(v); |
| | | return v; |
| | | } |
| | | |
| | | public PlistElementDict AddDict() |
| | | { |
| | | var v = new PlistElementDict(); |
| | | values.Add(v); |
| | | return v; |
| | | } |
| | | } |
| | | |
| | | public class PlistDocument |
| | | { |
| | | public PlistElementDict root; |
| | | public string version; |
| | | |
| | | private XDocumentType documentType; |
| | | |
| | | public PlistDocument() |
| | | { |
| | | root = new PlistElementDict(); |
| | | version = "1.0"; |
| | | } |
| | | |
| | | // Parses a string that contains a XML file. No validation is done. |
| | | internal static XDocument ParseXmlNoDtd(string text) |
| | | { |
| | | XmlReaderSettings settings = new XmlReaderSettings(); |
| | | settings.ProhibitDtd = false; |
| | | settings.XmlResolver = null; // prevent DTD download |
| | | |
| | | XmlReader xmlReader = XmlReader.Create(new StringReader(text), settings); |
| | | return XDocument.Load(xmlReader); |
| | | } |
| | | |
| | | // LINQ serializes XML DTD declaration with an explicit empty 'internal subset' |
| | | // (a pair of square brackets at the end of Doctype declaration). |
| | | // Even though this is valid XML, XCode does not like it, hence this workaround. |
| | | internal static string CleanDtdToString(XDocument doc, XDocumentType documentType) |
| | | { |
| | | // LINQ does not support changing the DTD of existing XDocument instances, |
| | | // so we create a dummy document for printing of the Doctype declaration. |
| | | // A single dummy element is added to force LINQ not to omit the declaration. |
| | | // Also, utf-8 encoding is forced since this is the encoding we use when writing to file in UpdateInfoPlist. |
| | | if (documentType != null) |
| | | { |
| | | XDocument tmpDoc = |
| | | new XDocument(new XDeclaration("1.0", "utf-8", null), |
| | | new XDocumentType(documentType.Name, documentType.PublicId, documentType.SystemId, null), |
| | | new XElement(doc.Root.Name)); |
| | | return "" + tmpDoc.Declaration + "\n" + tmpDoc.DocumentType + "\n" + doc.Root + "\n"; |
| | | } |
| | | else |
| | | { |
| | | XDocument tmpDoc = new XDocument(new XDeclaration("1.0", "utf-8", null), new XElement(doc.Root.Name)); |
| | | return "" + tmpDoc.Declaration + Environment.NewLine + doc.Root + "\n"; |
| | | } |
| | | } |
| | | |
| | | internal static string CleanDtdToString(XDocument doc) |
| | | { |
| | | return CleanDtdToString(doc, doc.DocumentType); |
| | | } |
| | | |
| | | private static string GetText(XElement xml) |
| | | { |
| | | return String.Join("", xml.Nodes().OfType<XText>().Select(x => x.Value).ToArray()); |
| | | } |
| | | |
| | | private static PlistElement ReadElement(XElement xml) |
| | | { |
| | | switch (xml.Name.LocalName) |
| | | { |
| | | case "dict": |
| | | { |
| | | List<XElement> children = xml.Elements().ToList(); |
| | | var el = new PlistElementDict(); |
| | | |
| | | if (children.Count % 2 == 1) |
| | | throw new Exception("Malformed plist file"); |
| | | |
| | | for (int i = 0; i < children.Count - 1; i++) |
| | | { |
| | | if (children[i].Name != "key") |
| | | throw new Exception("Malformed plist file. Found '"+children[i].Name+"' where 'key' was expected."); |
| | | string key = GetText(children[i]).Trim(); |
| | | var newChild = ReadElement(children[i+1]); |
| | | if (newChild != null) |
| | | { |
| | | i++; |
| | | el[key] = newChild; |
| | | } |
| | | } |
| | | return el; |
| | | } |
| | | case "array": |
| | | { |
| | | List<XElement> children = xml.Elements().ToList(); |
| | | var el = new PlistElementArray(); |
| | | |
| | | foreach (var childXml in children) |
| | | { |
| | | var newChild = ReadElement(childXml); |
| | | if (newChild != null) |
| | | el.values.Add(newChild); |
| | | } |
| | | return el; |
| | | } |
| | | case "string": |
| | | return new PlistElementString(GetText(xml)); |
| | | case "integer": |
| | | { |
| | | int r; |
| | | if (int.TryParse(GetText(xml), out r)) |
| | | return new PlistElementInteger(r); |
| | | return null; |
| | | } |
| | | case "real": |
| | | { |
| | | float f; |
| | | if (float.TryParse(GetText(xml), out f)) |
| | | return new PlistElementReal(f); |
| | | return null; |
| | | } |
| | | case "date": |
| | | { |
| | | DateTime date; |
| | | if (DateTime.TryParse(GetText(xml), out date)) |
| | | return new PlistElementDate(date.ToUniversalTime()); |
| | | return null; |
| | | } |
| | | case "true": |
| | | return new PlistElementBoolean(true); |
| | | case "false": |
| | | return new PlistElementBoolean(false); |
| | | default: |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | public void Create() |
| | | { |
| | | const string doc = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + |
| | | "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" + |
| | | "<plist version=\"1.0\">" + |
| | | "<dict>" + |
| | | "</dict>" + |
| | | "</plist>"; |
| | | ReadFromString(doc); |
| | | } |
| | | |
| | | public void ReadFromFile(string path) |
| | | { |
| | | ReadFromString(File.ReadAllText(path)); |
| | | } |
| | | |
| | | public void ReadFromStream(TextReader tr) |
| | | { |
| | | ReadFromString(tr.ReadToEnd()); |
| | | } |
| | | |
| | | public void ReadFromString(string text) |
| | | { |
| | | XDocument doc = ParseXmlNoDtd(text); |
| | | version = (string) doc.Root.Attribute("version"); |
| | | XElement xml = doc.XPathSelectElement("plist/dict"); |
| | | |
| | | var dict = ReadElement(xml); |
| | | if (dict == null) |
| | | throw new Exception("Error parsing plist file"); |
| | | root = dict as PlistElementDict; |
| | | if (root == null) |
| | | throw new Exception("Malformed plist file"); |
| | | documentType = doc.DocumentType; |
| | | } |
| | | |
| | | private static XElement WriteElement(PlistElement el) |
| | | { |
| | | if (el is PlistElementBoolean) |
| | | { |
| | | var realEl = el as PlistElementBoolean; |
| | | return new XElement(realEl.value ? "true" : "false"); |
| | | } |
| | | if (el is PlistElementInteger) |
| | | { |
| | | var realEl = el as PlistElementInteger; |
| | | return new XElement("integer", realEl.value.ToString()); |
| | | } |
| | | if (el is PlistElementString) |
| | | { |
| | | var realEl = el as PlistElementString; |
| | | return new XElement("string", realEl.value); |
| | | } |
| | | if (el is PlistElementReal) |
| | | { |
| | | var realEl = el as PlistElementReal; |
| | | return new XElement("real", realEl.value.ToString()); |
| | | } |
| | | if (el is PlistElementDate) |
| | | { |
| | | var realEl = el as PlistElementDate; |
| | | return new XElement("date", realEl.value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")); |
| | | } |
| | | if (el is PlistElementDict) |
| | | { |
| | | var realEl = el as PlistElementDict; |
| | | var dictXml = new XElement("dict"); |
| | | foreach (var kv in realEl.values) |
| | | { |
| | | var keyXml = new XElement("key", kv.Key); |
| | | var valueXml = WriteElement(kv.Value); |
| | | if (valueXml != null) |
| | | { |
| | | dictXml.Add(keyXml); |
| | | dictXml.Add(valueXml); |
| | | } |
| | | } |
| | | return dictXml; |
| | | } |
| | | if (el is PlistElementArray) |
| | | { |
| | | var realEl = el as PlistElementArray; |
| | | var arrayXml = new XElement("array"); |
| | | foreach (var v in realEl.values) |
| | | { |
| | | var elXml = WriteElement(v); |
| | | if (elXml != null) |
| | | arrayXml.Add(elXml); |
| | | } |
| | | return arrayXml; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public void WriteToFile(string path) |
| | | { |
| | | System.Text.Encoding utf8WithoutBom = new System.Text.UTF8Encoding(false); |
| | | File.WriteAllText(path, WriteToString(), utf8WithoutBom); |
| | | } |
| | | |
| | | public void WriteToStream(TextWriter tw) |
| | | { |
| | | tw.Write(WriteToString()); |
| | | } |
| | | |
| | | public string WriteToString() |
| | | { |
| | | var el = WriteElement(root); |
| | | var rootEl = new XElement("plist"); |
| | | rootEl.Add(new XAttribute("version", version)); |
| | | rootEl.Add(el); |
| | | |
| | | var doc = new XDocument(); |
| | | doc.Add(rootEl); |
| | | return CleanDtdToString(doc, documentType).Replace("\r\n", "\n"); |
| | | } |
| | | } |
| | | |
| | | } // namespace UnityEditor.iOS.XCode |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 11737b4a5233044c3b8e8f7167374d01 |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System; |
| | | using System.IO; |
| | | |
| | | #if UNITY_XCODE_API_BUILD |
| | | namespace UnityEditor.iOS.Xcode |
| | | #else |
| | | namespace UnityEditor.iOS.Xcode.Custom |
| | | #endif |
| | | { |
| | | /// <summary> |
| | | /// The ProjectCapabilityManager class helps to add capabilities to the Xcode |
| | | /// project. This operation potentially involves modification of any of the |
| | | /// pbxproj file, the entitlements file(s) and Info.plist file(s). The |
| | | /// manager assumes ownership of all of these files until the last |
| | | /// WriteToFile() invocation. |
| | | /// </summary> |
| | | public class ProjectCapabilityManager |
| | | { |
| | | private readonly string m_BuildPath; |
| | | private readonly string m_TargetGuid; |
| | | private readonly string m_PBXProjectPath; |
| | | private readonly string m_EntitlementFilePath; |
| | | private PlistDocument m_Entitlements; |
| | | private PlistDocument m_InfoPlist; |
| | | protected internal PBXProject project; |
| | | |
| | | /// <summary> |
| | | /// Creates a new instance of ProjectCapabilityManager. The returned |
| | | /// instance assumes ownership of the referenced pbxproj project file, |
| | | /// the entitlements file and project Info.plist files until the last |
| | | /// WriteToFile() call. |
| | | /// </summary> |
| | | /// <param name="pbxProjectPath">Path to the pbxproj file.</param> |
| | | /// <param name="entitlementFilePath">Path to the entitlements file.</param> |
| | | /// <param name="targetName">The name of the target to add entitlements for.</param> |
| | | public ProjectCapabilityManager(string pbxProjectPath, string entitlementFilePath, string targetName) |
| | | { |
| | | m_BuildPath = Directory.GetParent(Path.GetDirectoryName(pbxProjectPath)).FullName; |
| | | |
| | | m_EntitlementFilePath = entitlementFilePath; |
| | | m_PBXProjectPath = pbxProjectPath; |
| | | project = new PBXProject(); |
| | | project.ReadFromString(File.ReadAllText(m_PBXProjectPath)); |
| | | m_TargetGuid = project.TargetGuidByName(targetName); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Writes the modifications to the project file, entitlements file and |
| | | /// the Info.plist file. Any external changes to these files after |
| | | /// the ProjectCapabilityManager instance has been created and before |
| | | /// the call to WriteToFile() will be overwritten. |
| | | /// </summary> |
| | | public void WriteToFile() |
| | | { |
| | | File.WriteAllText(m_PBXProjectPath, project.WriteToString()); |
| | | if (m_Entitlements != null) |
| | | m_Entitlements.WriteToFile(PBXPath.Combine(m_BuildPath, m_EntitlementFilePath)); |
| | | if (m_InfoPlist != null) |
| | | m_InfoPlist.WriteToFile(PBXPath.Combine(m_BuildPath, "Info.plist")); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds iCloud capability to project |
| | | /// </summary> |
| | | /// <param name="enableKeyValueStorage">Enables key-value storage option if set to true</param> |
| | | /// <param name="enableiCloudDocument">Enables iCloud document option if set to true</param> |
| | | /// <param name="customContainers">A list of custom containers to add</param> |
| | | public void AddiCloud(bool enableKeyValueStorage, bool enableiCloudDocument, |
| | | string[] customContainers) |
| | | { |
| | | AddiCloud(enableKeyValueStorage, enableiCloudDocument, true, true, |
| | | customContainers); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds iCloud capability to project |
| | | /// </summary> |
| | | /// <param name="enableKeyValueStorage">Enables key-value storage option if set to true</param> |
| | | /// <param name="enableiCloudDocument">Enables iCloud document option if set to true</param> |
| | | /// <param name="enablecloudKit">Enables cloudKit option if set to true</param> |
| | | /// <param name="addDefaultContainers">Default containers are added if this option is set to true</param> |
| | | /// <param name="customContainers">A list of custom containers to add</param> |
| | | public void AddiCloud(bool enableKeyValueStorage, bool enableiCloudDocument, bool enablecloudKit, bool addDefaultContainers, string[] customContainers) |
| | | { |
| | | var ent = GetOrCreateEntitlementDoc(); |
| | | var val = (ent.root[ICloudEntitlements.ContainerIdKey] = new PlistElementArray()) as PlistElementArray; |
| | | |
| | | // Cloud document storage and CloudKit require specifying services. |
| | | PlistElementArray ser = null; |
| | | if (enableiCloudDocument || enablecloudKit) |
| | | ser = (ent.root[ICloudEntitlements.ServicesKey] = new PlistElementArray()) as PlistElementArray; |
| | | |
| | | if (enableiCloudDocument) |
| | | { |
| | | val.values.Add(new PlistElementString(ICloudEntitlements.ContainerIdValue)); |
| | | ser.values.Add(new PlistElementString(ICloudEntitlements.ServicesDocValue)); |
| | | var ubiquity = (ent.root[ICloudEntitlements.UbiquityContainerIdKey] = new PlistElementArray()) as PlistElementArray; |
| | | |
| | | if (addDefaultContainers) |
| | | ubiquity.values.Add(new PlistElementString(ICloudEntitlements.UbiquityContainerIdValue)); |
| | | |
| | | if (customContainers != null && customContainers.Length > 0) |
| | | { |
| | | // For cloud document, custom containers go in the ubiquity values. |
| | | for (var i = 0; i < customContainers.Length; i++) |
| | | ubiquity.values.Add(new PlistElementString(customContainers[i])); |
| | | } |
| | | } |
| | | |
| | | if (enablecloudKit) |
| | | { |
| | | if (addDefaultContainers && !enableiCloudDocument) |
| | | val.values.Add(new PlistElementString(ICloudEntitlements.ContainerIdValue)); |
| | | |
| | | if (customContainers != null && customContainers.Length > 0) |
| | | { |
| | | // For CloudKit, custom containers also go in the container id values. |
| | | for (var i = 0; i < customContainers.Length; i++) |
| | | val.values.Add(new PlistElementString(customContainers[i])); |
| | | } |
| | | |
| | | ser.values.Add(new PlistElementString(ICloudEntitlements.ServicesKitValue)); |
| | | } |
| | | |
| | | if (enableKeyValueStorage) |
| | | ent.root[ICloudEntitlements.KeyValueStoreKey] = new PlistElementString(ICloudEntitlements.KeyValueStoreValue); |
| | | |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.iCloud, m_EntitlementFilePath, enablecloudKit); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Add Push (or remote) Notifications capability to the project |
| | | /// </summary> |
| | | /// <param name="development">Sets the development option if set to true</param> |
| | | public void AddPushNotifications(bool development) |
| | | { |
| | | GetOrCreateEntitlementDoc().root[PushNotificationEntitlements.Key] = new PlistElementString(development ? PushNotificationEntitlements.DevelopmentValue : PushNotificationEntitlements.ProductionValue); |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.PushNotifications, m_EntitlementFilePath); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds Game Center capability to the project |
| | | /// </summary> |
| | | public void AddGameCenter() |
| | | { |
| | | var arr = (GetOrCreateInfoDoc().root[GameCenterInfo.Key] ?? (GetOrCreateInfoDoc().root[GameCenterInfo.Key] = new PlistElementArray())) as PlistElementArray; |
| | | arr.values.Add(new PlistElementString(GameCenterInfo.Value)); |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.GameCenter); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds wallet capability to the project. |
| | | /// </summary> |
| | | /// <param name="passSubset">Controls the allowed pass types. If null or |
| | | /// empty, then all team pass types are allowed. Otherwise, only the |
| | | /// specified subset of pass types is allowed</param> |
| | | public void AddWallet(string[] passSubset) |
| | | { |
| | | var arr = (GetOrCreateEntitlementDoc().root[WalletEntitlements.Key] = new PlistElementArray()) as PlistElementArray; |
| | | if ((passSubset == null || passSubset.Length == 0) && arr != null) |
| | | { |
| | | arr.values.Add(new PlistElementString(WalletEntitlements.BaseValue + WalletEntitlements.BaseValue)); |
| | | } |
| | | else |
| | | { |
| | | for (var i = 0; i < passSubset.Length; i++) |
| | | { |
| | | if (arr != null) |
| | | arr.values.Add(new PlistElementString(WalletEntitlements.BaseValue + passSubset[i])); |
| | | } |
| | | } |
| | | |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.Wallet, m_EntitlementFilePath); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds Siri capability to project. |
| | | /// </summary> |
| | | public void AddSiri() |
| | | { |
| | | GetOrCreateEntitlementDoc().root[SiriEntitlements.Key] = new PlistElementBoolean(true); |
| | | |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.Siri, m_EntitlementFilePath); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds Apple Pay capability to the project. |
| | | /// </summary> |
| | | /// <param name="merchants">The list of merchant IDs to configure</param> |
| | | public void AddApplePay(string[] merchants) |
| | | { |
| | | var arr = (GetOrCreateEntitlementDoc().root[ApplePayEntitlements.Key] = new PlistElementArray()) as PlistElementArray; |
| | | for (var i = 0; i < merchants.Length; i++) |
| | | { |
| | | arr.values.Add(new PlistElementString(merchants[i])); |
| | | } |
| | | |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.ApplePay, m_EntitlementFilePath); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds In App Purchase capability to the project. |
| | | /// </summary> |
| | | public void AddInAppPurchase() |
| | | { |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.InAppPurchase); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds Maps capability to the project. |
| | | /// </summary> |
| | | /// <param name="options">The routing options to configure.</param> |
| | | public void AddMaps(MapsOptions options) |
| | | { |
| | | var bundleArr = (GetOrCreateInfoDoc().root[MapsInfo.BundleKey] ?? (GetOrCreateInfoDoc().root[MapsInfo.BundleKey] = new PlistElementArray())) as PlistElementArray; |
| | | bundleArr.values.Add(new PlistElementDict()); |
| | | PlistElementDict bundleDic = GetOrCreateUniqueDictElementInArray(bundleArr); |
| | | bundleDic[MapsInfo.BundleNameKey] = new PlistElementString(MapsInfo.BundleNameValue); |
| | | var bundleTypeArr = (bundleDic[MapsInfo.BundleTypeKey] ?? (bundleDic[MapsInfo.BundleTypeKey] = new PlistElementArray())) as PlistElementArray; |
| | | GetOrCreateStringElementInArray(bundleTypeArr, MapsInfo.BundleTypeValue); |
| | | |
| | | var optionArr = (GetOrCreateInfoDoc().root[MapsInfo.ModeKey] ?? |
| | | (GetOrCreateInfoDoc().root[MapsInfo.ModeKey] = new PlistElementArray())) as PlistElementArray; |
| | | if ((options & MapsOptions.Airplane) == MapsOptions.Airplane) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, MapsInfo.ModePlaneValue); |
| | | } |
| | | if ((options & MapsOptions.Bike) == MapsOptions.Bike) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, MapsInfo.ModeBikeValue); |
| | | } |
| | | if ((options & MapsOptions.Bus) == MapsOptions.Bus) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, MapsInfo.ModeBusValue); |
| | | } |
| | | if ((options & MapsOptions.Car) == MapsOptions.Car) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, MapsInfo.ModeCarValue); |
| | | } |
| | | if ((options & MapsOptions.Ferry) == MapsOptions.Ferry) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, MapsInfo.ModeFerryValue); |
| | | } |
| | | if ((options & MapsOptions.Other) == MapsOptions.Other) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, MapsInfo.ModeOtherValue); |
| | | } |
| | | if ((options & MapsOptions.Pedestrian) == MapsOptions.Pedestrian) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, MapsInfo.ModePedestrianValue); |
| | | } |
| | | if ((options & MapsOptions.RideSharing) == MapsOptions.RideSharing) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, MapsInfo.ModeRideShareValue); |
| | | } |
| | | if ((options & MapsOptions.StreetCar) == MapsOptions.StreetCar) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, MapsInfo.ModeStreetCarValue); |
| | | } |
| | | if ((options & MapsOptions.Subway) == MapsOptions.Subway) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, MapsInfo.ModeSubwayValue); |
| | | } |
| | | if ((options & MapsOptions.Taxi) == MapsOptions.Taxi) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, MapsInfo.ModeTaxiValue); |
| | | } |
| | | if ((options & MapsOptions.Train) == MapsOptions.Train) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, MapsInfo.ModeTrainValue); |
| | | } |
| | | |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.Maps); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds Personal VPN capability to the project. |
| | | /// </summary> |
| | | public void AddPersonalVPN() |
| | | { |
| | | var arr = (GetOrCreateEntitlementDoc().root[VPNEntitlements.Key] = new PlistElementArray()) as PlistElementArray; |
| | | arr.values.Add(new PlistElementString(VPNEntitlements.Value)); |
| | | |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.PersonalVPN, m_EntitlementFilePath); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds Background capability to the project. |
| | | /// </summary> |
| | | /// <param name="options">The list of background modes to configure.</param> |
| | | public void AddBackgroundModes(BackgroundModesOptions options) |
| | | { |
| | | var optionArr = (GetOrCreateInfoDoc().root[BackgroundInfo.Key] ?? |
| | | (GetOrCreateInfoDoc().root[BackgroundInfo.Key] = new PlistElementArray())) as PlistElementArray; |
| | | |
| | | if ((options & BackgroundModesOptions.ActsAsABluetoothLEAccessory) == BackgroundModesOptions.ActsAsABluetoothLEAccessory) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, BackgroundInfo.ModeActsBluetoothValue); |
| | | } |
| | | if ((options & BackgroundModesOptions.AudioAirplayPiP) == BackgroundModesOptions.AudioAirplayPiP) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, BackgroundInfo.ModeAudioValue); |
| | | } |
| | | if ((options & BackgroundModesOptions.BackgroundFetch) == BackgroundModesOptions.BackgroundFetch) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, BackgroundInfo.ModeFetchValue); |
| | | } |
| | | if ((options & BackgroundModesOptions.ExternalAccessoryCommunication) == BackgroundModesOptions.ExternalAccessoryCommunication) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, BackgroundInfo.ModeExtAccessoryValue); |
| | | } |
| | | if ((options & BackgroundModesOptions.LocationUpdates) == BackgroundModesOptions.LocationUpdates) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, BackgroundInfo.ModeLocationValue); |
| | | } |
| | | if ((options & BackgroundModesOptions.NewsstandDownloads) == BackgroundModesOptions.NewsstandDownloads) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, BackgroundInfo.ModeNewsstandValue); |
| | | } |
| | | if ((options & BackgroundModesOptions.RemoteNotifications) == BackgroundModesOptions.RemoteNotifications) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, BackgroundInfo.ModePushValue); |
| | | } |
| | | if ((options & BackgroundModesOptions.VoiceOverIP) == BackgroundModesOptions.VoiceOverIP) |
| | | { |
| | | GetOrCreateStringElementInArray(optionArr, BackgroundInfo.ModeVOIPValue); |
| | | } |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.BackgroundModes); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds Keychain Sharing capability to the project. |
| | | /// </summary> |
| | | /// <param name="accessGroups">The list of keychain access groups to configure.</param> |
| | | public void AddKeychainSharing(string[] accessGroups) |
| | | { |
| | | var arr = (GetOrCreateEntitlementDoc().root[KeyChainEntitlements.Key] = new PlistElementArray()) as PlistElementArray; |
| | | if (accessGroups != null) |
| | | { |
| | | for (var i = 0; i < accessGroups.Length; i++) |
| | | { |
| | | arr.values.Add(new PlistElementString(accessGroups[i])); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | arr.values.Add(new PlistElementString(KeyChainEntitlements.DefaultValue)); |
| | | } |
| | | |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.KeychainSharing, m_EntitlementFilePath); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds Inter App Audio capability to the project. |
| | | /// </summary> |
| | | public void AddInterAppAudio() |
| | | { |
| | | GetOrCreateEntitlementDoc().root[AudioEntitlements.Key] = new PlistElementBoolean(true); |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.InterAppAudio, m_EntitlementFilePath); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds Associated Domains capability to the project. |
| | | /// </summary> |
| | | /// <param name="domains">The list of domains to configure.</param> |
| | | public void AddAssociatedDomains(string[] domains) |
| | | { |
| | | var arr = (GetOrCreateEntitlementDoc().root[AssociatedDomainsEntitlements.Key] = new PlistElementArray()) as PlistElementArray; |
| | | for (var i = 0; i < domains.Length; i++) |
| | | { |
| | | arr.values.Add(new PlistElementString(domains[i])); |
| | | } |
| | | |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.AssociatedDomains, m_EntitlementFilePath); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds App Groups capability to the project. |
| | | /// </summary> |
| | | /// <param name="groups">The list of app groups to configure.</param> |
| | | public void AddAppGroups(string[] groups) |
| | | { |
| | | var arr = (GetOrCreateEntitlementDoc().root[AppGroupsEntitlements.Key] = new PlistElementArray()) as PlistElementArray; |
| | | for (var i = 0; i < groups.Length; i++) |
| | | { |
| | | arr.values.Add(new PlistElementString(groups[i])); |
| | | } |
| | | |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.AppGroups, m_EntitlementFilePath); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds HomeKit capability to the project. |
| | | /// </summary> |
| | | public void AddHomeKit() |
| | | { |
| | | GetOrCreateEntitlementDoc().root[HomeKitEntitlements.Key] = new PlistElementBoolean(true); |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.HomeKit, m_EntitlementFilePath); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds Data Protection capability to the project. |
| | | /// </summary> |
| | | public void AddDataProtection() |
| | | { |
| | | GetOrCreateEntitlementDoc().root[DataProtectionEntitlements.Key] = new PlistElementString(DataProtectionEntitlements.Value); |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.DataProtection, m_EntitlementFilePath); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds HealthKit capability to the project. |
| | | /// </summary> |
| | | public void AddHealthKit() |
| | | { |
| | | var capabilityArr = (GetOrCreateInfoDoc().root[HealthInfo.Key] ?? |
| | | (GetOrCreateInfoDoc().root[HealthInfo.Key] = new PlistElementArray())) as PlistElementArray; |
| | | GetOrCreateStringElementInArray(capabilityArr, HealthInfo.Value); |
| | | GetOrCreateEntitlementDoc().root[HealthKitEntitlements.Key] = new PlistElementBoolean(true); |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.HealthKit, m_EntitlementFilePath); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Adds Wireless Accessory Configuration capability to the project. |
| | | /// </summary> |
| | | public void AddWirelessAccessoryConfiguration() |
| | | { |
| | | GetOrCreateEntitlementDoc().root[WirelessAccessoryConfigurationEntitlements.Key] = new PlistElementBoolean(true); |
| | | project.AddCapability(m_TargetGuid, PBXCapabilityType.WirelessAccessoryConfiguration, m_EntitlementFilePath); |
| | | } |
| | | |
| | | private PlistDocument GetOrCreateEntitlementDoc() |
| | | { |
| | | if (m_Entitlements == null) |
| | | { |
| | | m_Entitlements = new PlistDocument(); |
| | | string[] entitlementsFiles = Directory.GetFiles(m_BuildPath, m_EntitlementFilePath); |
| | | if (entitlementsFiles.Length > 0) |
| | | { |
| | | m_Entitlements.ReadFromFile(entitlementsFiles[0]); |
| | | } |
| | | else |
| | | { |
| | | m_Entitlements.Create(); |
| | | } |
| | | } |
| | | |
| | | return m_Entitlements; |
| | | } |
| | | |
| | | private PlistDocument GetOrCreateInfoDoc() |
| | | { |
| | | if (m_InfoPlist == null) |
| | | { |
| | | m_InfoPlist = new PlistDocument(); |
| | | string[] infoFiles = Directory.GetFiles(m_BuildPath + "/", "Info.plist"); |
| | | if (infoFiles.Length > 0) |
| | | { |
| | | m_InfoPlist.ReadFromFile(infoFiles[0]); |
| | | } |
| | | else |
| | | { |
| | | m_InfoPlist.Create(); |
| | | } |
| | | } |
| | | |
| | | return m_InfoPlist; |
| | | } |
| | | |
| | | private PlistElementString GetOrCreateStringElementInArray(PlistElementArray root, string value) |
| | | { |
| | | PlistElementString r = null; |
| | | var c = root.values.Count; |
| | | var exist = false; |
| | | for (var i = 0; i < c; i++) |
| | | { |
| | | if (root.values[i] is PlistElementString && (root.values[i] as PlistElementString).value == value) |
| | | { |
| | | r = root.values[i] as PlistElementString; |
| | | exist = true; |
| | | } |
| | | } |
| | | if (!exist) |
| | | { |
| | | r = new PlistElementString(value); |
| | | root.values.Add(r); |
| | | } |
| | | return r; |
| | | } |
| | | |
| | | private PlistElementDict GetOrCreateUniqueDictElementInArray(PlistElementArray root) |
| | | { |
| | | PlistElementDict r; |
| | | if (root.values.Count == 0) |
| | | { |
| | | r = root.values[0] as PlistElementDict; |
| | | } |
| | | else |
| | | { |
| | | r = new PlistElementDict(); |
| | | root.values.Add(r); |
| | | } |
| | | return r; |
| | | } |
| | | } |
| | | |
| | | // The list of options available for Background Mode. |
| | | [Flags] |
| | | [Serializable] |
| | | public enum BackgroundModesOptions |
| | | { |
| | | None = 0, |
| | | AudioAirplayPiP = 1<<0, |
| | | LocationUpdates = 1<<1, |
| | | VoiceOverIP = 1<<2, |
| | | NewsstandDownloads = 1<<3, |
| | | ExternalAccessoryCommunication = 1<<4, |
| | | UsesBluetoothLEAccessory = 1<<5, |
| | | ActsAsABluetoothLEAccessory = 1<<6, |
| | | BackgroundFetch = 1<<7, |
| | | RemoteNotifications = 1<<8 |
| | | } |
| | | |
| | | // The list of options available for Maps. |
| | | [Serializable] |
| | | [Flags] |
| | | public enum MapsOptions |
| | | { |
| | | None = 0, |
| | | Airplane = 1<<0, |
| | | Bike = 1<<1, |
| | | Bus = 1<<2, |
| | | Car = 1<<3, |
| | | Ferry = 1<<4, |
| | | Pedestrian = 1<<5, |
| | | RideSharing = 1<<6, |
| | | StreetCar = 1<<7, |
| | | Subway = 1<<8, |
| | | Taxi = 1<<9, |
| | | Train = 1<<10, |
| | | Other = 1<<11 |
| | | } |
| | | |
| | | /* Follows the large quantity of string used as key and value all over the place in the info.plist or entitlements file. */ |
| | | internal class GameCenterInfo |
| | | { |
| | | internal static readonly string Key = "UIRequiredDeviceCapabilities"; |
| | | internal static readonly string Value = "gamekit"; |
| | | } |
| | | |
| | | internal class MapsInfo |
| | | { |
| | | internal static readonly string BundleKey = "CFBundleDocumentTypes"; |
| | | internal static readonly string BundleNameKey = "CFBundleTypeName"; |
| | | internal static readonly string BundleNameValue = "MKDirectionsRequest"; |
| | | internal static readonly string BundleTypeKey = "LSItemContentTypes"; |
| | | internal static readonly string BundleTypeValue = "com.apple.maps.directionsrequest"; |
| | | internal static readonly string ModeKey = "MKDirectionsApplicationSupportedModes"; |
| | | internal static readonly string ModePlaneValue = "MKDirectionsModePlane"; |
| | | internal static readonly string ModeBikeValue = "MKDirectionsModeBike"; |
| | | internal static readonly string ModeBusValue = "MKDirectionsModeBus"; |
| | | internal static readonly string ModeCarValue = "MKDirectionsModeCar"; |
| | | internal static readonly string ModeFerryValue = "MKDirectionsModeFerry"; |
| | | internal static readonly string ModeOtherValue = "MKDirectionsModeOther"; |
| | | internal static readonly string ModePedestrianValue = "MKDirectionsModePedestrian"; |
| | | internal static readonly string ModeRideShareValue = "MKDirectionsModeRideShare"; |
| | | internal static readonly string ModeStreetCarValue = "MKDirectionsModeStreetCar"; |
| | | internal static readonly string ModeSubwayValue = "MKDirectionsModeSubway"; |
| | | internal static readonly string ModeTaxiValue = "MKDirectionsModeTaxi"; |
| | | internal static readonly string ModeTrainValue = "MKDirectionsModeTrain"; |
| | | } |
| | | |
| | | internal class BackgroundInfo |
| | | { |
| | | internal static readonly string Key = "UIBackgroundModes"; |
| | | internal static readonly string ModeAudioValue = "audio"; |
| | | internal static readonly string ModeBluetoothValue = "bluetooth-central"; |
| | | internal static readonly string ModeActsBluetoothValue = "bluetooth-peripheral"; |
| | | internal static readonly string ModeExtAccessoryValue = "external-accessory"; |
| | | internal static readonly string ModeFetchValue = "fetch"; |
| | | internal static readonly string ModeLocationValue = "location"; |
| | | internal static readonly string ModeNewsstandValue = "newsstand-content"; |
| | | internal static readonly string ModePushValue = "remote-notification"; |
| | | internal static readonly string ModeVOIPValue = "voip"; |
| | | } |
| | | |
| | | internal class HealthInfo |
| | | { |
| | | internal static readonly string Key = "UIRequiredDeviceCapabilities"; |
| | | internal static readonly string Value = "healthkit"; |
| | | } |
| | | |
| | | internal class ICloudEntitlements |
| | | { |
| | | internal static readonly string ContainerIdKey = "com.apple.developer.icloud-container-identifiers"; |
| | | internal static readonly string ContainerIdValue = "iCloud.$(CFBundleIdentifier)"; |
| | | internal static readonly string UbiquityContainerIdKey = "com.apple.developer.ubiquity-container-identifiers"; |
| | | internal static readonly string UbiquityContainerIdValue = "iCloud.$(CFBundleIdentifier)"; |
| | | internal static readonly string ServicesKey = "com.apple.developer.icloud-services"; |
| | | internal static readonly string ServicesDocValue = "CloudDocuments"; |
| | | internal static readonly string ServicesKitValue = "CloudKit"; |
| | | internal static readonly string KeyValueStoreKey = "com.apple.developer.ubiquity-kvstore-identifier"; |
| | | internal static readonly string KeyValueStoreValue = "$(TeamIdentifierPrefix)$(CFBundleIdentifier)"; |
| | | } |
| | | |
| | | internal class PushNotificationEntitlements |
| | | { |
| | | internal static readonly string Key = "aps-environment"; |
| | | internal static readonly string DevelopmentValue = "development"; |
| | | internal static readonly string ProductionValue = "production"; |
| | | } |
| | | |
| | | internal class WalletEntitlements |
| | | { |
| | | internal static readonly string Key = "com.apple.developer.pass-type-identifiers"; |
| | | internal static readonly string BaseValue = "$(TeamIdentifierPrefix)"; |
| | | internal static readonly string DefaultValue = "*"; |
| | | } |
| | | |
| | | internal class SiriEntitlements |
| | | { |
| | | internal static readonly string Key = "com.apple.developer.siri"; |
| | | } |
| | | |
| | | internal class ApplePayEntitlements |
| | | { |
| | | internal static readonly string Key = "com.apple.developer.in-app-payments"; |
| | | } |
| | | |
| | | internal class VPNEntitlements |
| | | { |
| | | internal static readonly string Key = "com.apple.developer.networking.vpn.api"; |
| | | internal static readonly string Value = "allow-vpn"; |
| | | } |
| | | |
| | | internal class KeyChainEntitlements |
| | | { |
| | | internal static readonly string Key = "keychain-access-groups"; |
| | | internal static readonly string DefaultValue = "$(AppIdentifierPrefix)$(CFBundleIdentifier)"; |
| | | } |
| | | |
| | | internal class AudioEntitlements |
| | | { |
| | | internal static readonly string Key = "inter-app-audio"; |
| | | } |
| | | |
| | | internal class AssociatedDomainsEntitlements |
| | | { |
| | | // value is an array of string of domains |
| | | internal static readonly string Key = "com.apple.developer.associated-domains"; |
| | | } |
| | | |
| | | internal class AppGroupsEntitlements |
| | | { |
| | | // value is an array of string of groups |
| | | internal static readonly string Key = "com.apple.security.application-groups"; |
| | | } |
| | | |
| | | internal class HomeKitEntitlements |
| | | { |
| | | // value is bool true. |
| | | internal static readonly string Key = "com.apple.developer.homekit"; |
| | | } |
| | | |
| | | internal class DataProtectionEntitlements |
| | | { |
| | | internal static readonly string Key = "com.apple.developer.default-data-protection"; |
| | | internal static readonly string Value = "NSFileProtectionComplete"; |
| | | } |
| | | |
| | | internal class HealthKitEntitlements |
| | | { |
| | | // value is bool true. |
| | | internal static readonly string Key = "com.apple.developer.healthkit"; |
| | | } |
| | | |
| | | internal class WirelessAccessoryConfigurationEntitlements |
| | | { |
| | | // value is bool true. |
| | | internal static readonly string Key = "com.apple.external-accessory.wireless-configuration"; |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: b024eec2ab40e432c89d1fc7ba136725 |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: c56323fa0e73f40c181376e6c5e7add3 |
| | | folderAsset: yes |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | DefaultImporter: |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System.Reflection; |
| | | using System.Runtime.CompilerServices; |
| | | using System.Runtime.InteropServices; |
| | | |
| | | // General Information about an assembly is controlled through the following |
| | | // set of attributes. Change these attribute values to modify the information |
| | | // associated with an assembly. |
| | | [assembly: AssemblyTitle("Unity.iOS.Extensions.Xcode")] |
| | | [assembly: AssemblyDescription("Unity Xcode editor's extensions for iOS")] |
| | | [assembly: AssemblyConfiguration("")] |
| | | [assembly: AssemblyCompany("Unity Technologies")] |
| | | [assembly: AssemblyProduct("Unity.iOS.Extensions.Xcode")] |
| | | [assembly: AssemblyCopyright("Copyright © Unity Technologies 2014")] |
| | | [assembly: AssemblyTrademark("")] |
| | | [assembly: AssemblyCulture("")] |
| | | |
| | | // The following GUID is for the ID of the typelib if this project is exposed to COM |
| | | [assembly: Guid("d14b0723-623a-4603-a9b1-7135970e9ae2")] |
| | | |
| | | // Version information for an assembly consists of the following four values: |
| | | // |
| | | // Major Version |
| | | // Minor Version |
| | | // Build Number |
| | | // Revision |
| | | // |
| | | // You can specify all the values or you can default the Build and Revision Numbers |
| | | // by using the '*' as shown below: |
| | | // [assembly: AssemblyVersion("1.0.*")] |
| | | [assembly: AssemblyVersion("1.0.0.0")] |
| | | [assembly: AssemblyFileVersion("1.0.0.0")] |
| | | |
| | | // Show the internal functions to the tests |
| | | [assembly: InternalsVisibleTo("Xcode.Tests")] |
| | | |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: f10fb301ed0ab4310b6ad129e3afc4b3 |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.IO; |
| | | using System.Linq; |
| | | using System.Xml; |
| | | using System.Xml.Linq; |
| | | using System.Xml.XPath; |
| | | |
| | | #if UNITY_XCODE_API_BUILD |
| | | namespace UnityEditor.iOS.Xcode |
| | | #else |
| | | namespace UnityEditor.iOS.Xcode.Custom |
| | | #endif |
| | | { |
| | | internal class XcScheme |
| | | { |
| | | XDocument m_Doc; |
| | | |
| | | // Returns the current build configuration. Returns null if it is not set. |
| | | public string GetBuildConfiguration() |
| | | { |
| | | var el = m_Doc.Root.XPathSelectElement("./LaunchAction"); |
| | | if (el == null) |
| | | throw new Exception("The xcscheme document does not contain build configuration setting"); |
| | | var attr = el.Attribute("buildConfiguration"); |
| | | if (attr == null) |
| | | return null; |
| | | return attr.Value; |
| | | } |
| | | |
| | | public void SetBuildConfiguration(string buildConfigName) |
| | | { |
| | | var el = m_Doc.Root.XPathSelectElement("./LaunchAction"); |
| | | if (el == null) |
| | | throw new Exception("The xcscheme document does not contain build configuration setting"); |
| | | el.SetAttributeValue("buildConfiguration", buildConfigName); |
| | | } |
| | | |
| | | public void ReadFromFile(string path) |
| | | { |
| | | ReadFromString(File.ReadAllText(path)); |
| | | } |
| | | |
| | | public void ReadFromStream(TextReader tr) |
| | | { |
| | | ReadFromString(tr.ReadToEnd()); |
| | | } |
| | | |
| | | public void ReadFromString(string text) |
| | | { |
| | | m_Doc = PlistDocument.ParseXmlNoDtd(text); |
| | | } |
| | | |
| | | public void WriteToFile(string path) |
| | | { |
| | | System.Text.Encoding utf8WithoutBom = new System.Text.UTF8Encoding(false); |
| | | File.WriteAllText(path, WriteToString(), utf8WithoutBom); |
| | | } |
| | | |
| | | public void WriteToStream(TextWriter tw) |
| | | { |
| | | tw.Write(WriteToString()); |
| | | } |
| | | |
| | | public string WriteToString() |
| | | { |
| | | return PlistDocument.CleanDtdToString(m_Doc, null).Replace("\r\n", "\n"); |
| | | } |
| | | } |
| | | |
| | | } // namespace UnityEditor.iOS.XCode |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 9d7d38b7a6d4841e78752e16ec77c301 |
| | | timeCreated: 1538030638 |
| | | licenseType: Pro |
| | | MonoImporter: |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |