Merge branch 'master' of http://192.168.1.20:10010/r/Project_SG_client
| 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); |
| | | |
| | | // 1.1 环境要求:iOS 13.0 及以上 |
| | | project.SetBuildProperty(targetGUID, "IPHONEOS_DEPLOYMENT_TARGET", "13.0"); |
| | | project.SetBuildProperty(fwTargetGUID, "IPHONEOS_DEPLOYMENT_TARGET", "13.0"); |
| | | |
| | | project.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-ObjC"); |
| | | |
| | | // Firebase 调试日志参数 |
| | | project.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-FIRAnalyticsDebugEnabled"); |
| | | project.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-FIRDebugEnabled"); |
| | | project.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-FIRAnalyticsVerboseLoggingEnabled"); |
| | | |
| | | //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); |
| | | } |
| | | } |
| | | |
| | | // Adjust SDK 依赖的系统框架(文档要求 Status 设为 Optional) |
| | | project.AddFrameworkToProject(fwTargetGUID, "AdServices.framework", true); |
| | | project.AddFrameworkToProject(fwTargetGUID, "AdSupport.framework", true); |
| | | project.AddFrameworkToProject(fwTargetGUID, "AppTrackingTransparency.framework", true); |
| | | project.AddFrameworkToProject(fwTargetGUID, "StoreKit.framework", true); |
| | | |
| | | File.WriteAllText(_projectPath, project.WriteToString()); |
| | | |
| | | // SPM 依赖:Firebase / Facebook / Adjust(Unity PBXProject API 不支持 SPM,需直接操作 pbxproj) |
| | | AddSwiftPackageDependencies(_projectPath, targetGUID); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 向 pbxproj 注入 Swift Package Manager 依赖(XCRemoteSwiftPackageReference + XCSwiftPackageProductDependency) |
| | | /// 版本号需根据 demo 或实际需求调整 |
| | | /// </summary> |
| | | private static void AddSwiftPackageDependencies(string projectPath, string targetGUID) |
| | | { |
| | | string content = File.ReadAllText(projectPath); |
| | | |
| | | // ---- 1. 生成 GUID(使用确定性命名避免重复注入)---- |
| | | string firebasePkgRefGuid = "FIMPSPM00001"; // XCRemoteSwiftPackageReference |
| | | string fbPkgRefGuid = "FIMPSPM00002"; |
| | | string adjustPkgRefGuid = "FIMPSPM00003"; |
| | | |
| | | string firebaseAnalyticsProdGuid = "FIMPSPM00011"; // XCSwiftPackageProductDependency (FirebaseAnalytics) |
| | | string firebaseCoreProdGuid = "FIMPSPM00012"; // XCSwiftPackageProductDependency (FirebaseCore) |
| | | string fbCoreKitProdGuid = "FIMPSPM00021"; // XCSwiftPackageProductDependency (FacebookCore) |
| | | string fbLoginKitProdGuid = "FIMPSPM00022"; // XCSwiftPackageProductDependency (FacebookLogin) |
| | | string adjustProdGuid = "FIMPSPM00031"; // XCSwiftPackageProductDependency (Adjust) |
| | | |
| | | // 如果已经注入过,跳过 |
| | | if (content.Contains(firebasePkgRefGuid)) |
| | | { |
| | | UnityEngine.Debug.Log("[XCodeProjectMod] SPM dependencies already injected, skipping."); |
| | | return; |
| | | } |
| | | |
| | | // ---- 2. XCRemoteSwiftPackageReference section ---- |
| | | string pkgRefSection = "/* XCRemoteSwiftPackageReference section */"; |
| | | string pkgRefEntries = string.Format( |
| | | "\t\t{{0}} /* XCRemoteSwiftPackageReference \"Firebase\" */ = {{isa = XCRemoteSwiftPackageReference; repositoryURL = \"https://github.com/firebase/firebase-ios-sdk\"; requirement = {{kind = upToNextMajorVersion; minimumVersion = 10.24.0; }}; }};\n" + |
| | | "\t\t{{1}} /* XCRemoteSwiftPackageReference \"Facebook\" */ = {{isa = XCRemoteSwiftPackageReference; repositoryURL = \"https://github.com/facebook/facebook-ios-sdk\"; requirement = {{kind = upToNextMajorVersion; minimumVersion = 17.0.0; }}; }};\n" + |
| | | "\t\t{{2}} /* XCRemoteSwiftPackageReference \"Adjust\" */ = {{isa = XCRemoteSwiftPackageReference; repositoryURL = \"https://github.com/adjust/ios_sdk\"; requirement = {{kind = upToNextMajorVersion; minimumVersion = 4.38.2; }}; }};\n", |
| | | firebasePkgRefGuid, fbPkgRefGuid, adjustPkgRefGuid); |
| | | content = content.Replace(pkgRefSection, pkgRefSection + "\n" + pkgRefEntries); |
| | | |
| | | // ---- 3. XCSwiftPackageProductDependency section ---- |
| | | string pkgProdSection = "/* XCSwiftPackageProductDependency section */"; |
| | | string pkgProdEntries = string.Format( |
| | | "\t\t{{0}} /* FirebaseAnalytics */ = {{isa = XCSwiftPackageProductDependency; package = {{1}} /* XCRemoteSwiftPackageReference \"Firebase\" */; productName = FirebaseAnalytics; }};\n" + |
| | | "\t\t{{2}} /* FirebaseCore */ = {{isa = XCSwiftPackageProductDependency; package = {{1}} /* XCRemoteSwiftPackageReference \"Firebase\" */; productName = FirebaseCore; }};\n" + |
| | | "\t\t{{3}} /* FacebookCore */ = {{isa = XCSwiftPackageProductDependency; package = {{4}} /* XCRemoteSwiftPackageReference \"Facebook\" */; productName = FacebookCore; }};\n" + |
| | | "\t\t{{5}} /* FacebookLogin */ = {{isa = XCSwiftPackageProductDependency; package = {{4}} /* XCRemoteSwiftPackageReference \"Facebook\" */; productName = FacebookLogin; }};\n" + |
| | | "\t\t{{6}} /* Adjust */ = {{isa = XCSwiftPackageProductDependency; package = {{7}} /* XCRemoteSwiftPackageReference \"Adjust\" */; productName = Adjust; }};\n", |
| | | firebaseAnalyticsProdGuid, firebasePkgRefGuid, |
| | | firebaseCoreProdGuid, |
| | | fbCoreKitProdGuid, fbPkgRefGuid, |
| | | fbLoginKitProdGuid, |
| | | adjustProdGuid, adjustPkgRefGuid); |
| | | content = content.Replace(pkgProdSection, pkgProdSection + "\n" + pkgProdEntries); |
| | | |
| | | // ---- 4. PBXNativeTarget packageProductDependencies ---- |
| | | // 在 target 的 packageProductDependencies 中添加引用 |
| | | string pkgProdDeps = string.Format( |
| | | "\t\t\t\tpackageProductDependencies = (\n" + |
| | | "\t\t\t\t\t{0} /* FirebaseAnalytics */,\n" + |
| | | "\t\t\t\t\t{1} /* FirebaseCore */,\n" + |
| | | "\t\t\t\t\t{2} /* FacebookCore */,\n" + |
| | | "\t\t\t\t\t{3} /* FacebookLogin */,\n" + |
| | | "\t\t\t\t\t{4} /* Adjust */,\n" + |
| | | "\t\t\t\t);", |
| | | firebaseAnalyticsProdGuid, firebaseCoreProdGuid, |
| | | fbCoreKitProdGuid, fbLoginKitProdGuid, |
| | | adjustProdGuid); |
| | | |
| | | // 找到 main target 的 buildPhases 结束位置附近,插入 packageProductDependencies |
| | | // 在 target 的 buildPhases 数组结束后添加 |
| | | string targetMarker = targetGUID + " /* " + PBXProject.GetUnityTargetName() + " */"; |
| | | int targetStart = content.IndexOf(targetMarker); |
| | | if (targetStart >= 0) |
| | | { |
| | | // 找到这个 target dict 中的 buildPhases 结束的 ); |
| | | int buildPhasesEnd = FindClosingParen(content, targetStart, "buildPhases"); |
| | | if (buildPhasesEnd >= 0) |
| | | { |
| | | content = content.Insert(buildPhasesEnd, "\n" + pkgProdDeps); |
| | | } |
| | | } |
| | | |
| | | // ---- 5. PBXProject packageReferences ---- |
| | | // 在 project 的 packageReferences 中添加引用 |
| | | string pkgRefs = string.Format( |
| | | "\t\t\t\tpackageReferences = (\n" + |
| | | "\t\t\t\t\t{{0}} /* XCRemoteSwiftPackageReference \"Firebase\" */,\n" + |
| | | "\t\t\t\t\t{{1}} /* XCRemoteSwiftPackageReference \"Facebook\" */,\n" + |
| | | "\t\t\t\t\t{{2}} /* XCRemoteSwiftPackageReference \"Adjust\" */,\n" + |
| | | "\t\t\t\t);", |
| | | firebasePkgRefGuid, fbPkgRefGuid, adjustPkgRefGuid); |
| | | |
| | | // 在 PBXProject 的 mainGroup 后面添加 packageReferences |
| | | int projectSection = content.IndexOf("/* PBXProject */ = {"); |
| | | if (projectSection >= 0) |
| | | { |
| | | // 找到 attributes 区块 |
| | | int attrsPos = content.IndexOf("attributes = {", projectSection); |
| | | if (attrsPos >= 0) |
| | | { |
| | | // 在 attributes 闭括号前插入 |
| | | int attrsClose = content.IndexOf("};", attrsPos + 14); |
| | | if (attrsClose >= 0) |
| | | { |
| | | content = content.Insert(attrsClose, "\n" + pkgRefs); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // ---- 6. PBXFrameworksBuildPhase ---- |
| | | // 将 SPM product 加到 Frameworks build phase |
| | | string frameworkBuildPhaseMarker = "/* Frameworks */ = {"; |
| | | int fwPhaseIdx = content.IndexOf(frameworkBuildPhaseMarker); |
| | | if (fwPhaseIdx >= 0) |
| | | { |
| | | int filesStart = content.IndexOf("files = (", fwPhaseIdx); |
| | | if (filesStart >= 0) |
| | | { |
| | | int filesEnd = content.IndexOf(");", filesStart + 9); |
| | | if (filesEnd >= 0) |
| | | { |
| | | string fwEntries = string.Format( |
| | | "\t\t\t\t{0} /* FirebaseAnalytics in Frameworks */,\n" + |
| | | "\t\t\t\t{1} /* FirebaseCore in Frameworks */,\n" + |
| | | "\t\t\t\t{2} /* FacebookCore in Frameworks */,\n" + |
| | | "\t\t\t\t{3} /* FacebookLogin in Frameworks */,\n" + |
| | | "\t\t\t\t{4} /* Adjust in Frameworks */,\n", |
| | | firebaseAnalyticsProdGuid, firebaseCoreProdGuid, |
| | | fbCoreKitProdGuid, fbLoginKitProdGuid, |
| | | adjustProdGuid); |
| | | content = content.Insert(filesEnd, fwEntries); |
| | | } |
| | | } |
| | | } |
| | | |
| | | File.WriteAllText(projectPath, content); |
| | | UnityEngine.Debug.Log("[XCodeProjectMod] SPM dependencies (Firebase/Facebook/Adjust) injected successfully."); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 在 pbxproj 文本中,从指定 key(如 buildPhases)的数组起始位置找到其闭括号 ");" 的位置 |
| | | /// </summary> |
| | | private static int FindClosingParen(string content, int searchStart, string key) |
| | | { |
| | | int keyIdx = content.IndexOf(key + " = (", searchStart); |
| | | if (keyIdx < 0) return -1; |
| | | int openIdx = content.IndexOf("(", keyIdx); |
| | | return content.IndexOf(");", openIdx); |
| | | } |
| | | |
| | | private static void BuildPlist(string path) |
| | | { |
| | | string _plistPath = path + "/Info.plist"; |
| | | |
| | | PlistDocument _plist = new PlistDocument(); |
| | | _plist.ReadFromString(File.ReadAllText(_plistPath)); |
| | | |
| | | PlistElementDict _rootDict = _plist.root; |
| | | _rootDict.SetString("NSUserTrackingUsageDescription", "Please rest assured, granting permission will not access your private information on other websites. This permission is only used to identify the device and ensure service security and enhance browsing experience"); |
| | | |
| | | // Facebook (funshine.plist: kFacebookAppID / kFacebookAppSecret) |
| | | _rootDict.SetString("FacebookAppID", "1445747540428317"); |
| | | _rootDict.SetString("FacebookClientToken", ""); |
| | | _rootDict.SetString("FacebookDisplayName", "Legend of the Sword Master"); |
| | | |
| | | // 适配欧盟地区政策法规 - Google Analytics 默认允许 |
| | | _rootDict.SetBoolean("GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE", true); |
| | | _rootDict.SetBoolean("GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE", true); |
| | | _rootDict.SetBoolean("GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA", true); |
| | | _rootDict.SetBoolean("GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS", true); |
| | | |
| | | // App 名称本地化 |
| | | _rootDict.SetBoolean("Application has localized display name", true); |
| | | _rootDict.SetString("CFBundleDevelopmentRegion", "english"); |
| | | |
| | | 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("fb-messenger-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("- (BOOL)application:(UIApplication*)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation\n{", "\n return [_fimpfunBridge application:application openURL:url sourceApplication:sourceApplication annotation:annotation];"); |
| | | _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;"); |
| | | |
| | | // 3.1 适配欧盟政策:在 viewDidAppear 中调用 adaptingEUGDPR(文档要求) |
| | | var _viewController = new XClass(projectPath + "/Classes/UnityViewControllerBase.mm"); |
| | | _viewController.WriteBelow("@implementation UnityViewControllerBase", |
| | | "\n- (void)viewDidAppear:(BOOL)animated {\n [super viewDidAppear:animated];\n [GetAppController().fimpfunBridge viewDidAppear];\n}\n"); |
| | | |
| | | // 创建 InfoPlist.strings(App 名称本地化) |
| | | CreateInfoPlistStrings(projectPath); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 创建 InfoPlist.strings 文件用于 App 名称本地化 |
| | | /// </summary> |
| | | private static void CreateInfoPlistStrings(string projectPath) |
| | | { |
| | | string stringsDir = projectPath + "/English.lproj"; |
| | | if (!Directory.Exists(stringsDir)) |
| | | { |
| | | Directory.CreateDirectory(stringsDir); |
| | | } |
| | | string stringsPath = stringsDir + "/InfoPlist.strings"; |
| | | if (!File.Exists(stringsPath)) |
| | | { |
| | | File.WriteAllText(stringsPath, "CFBundleDisplayName = \"Legend of the Sword Master\";\n"); |
| | | } |
| | | |
| | | // 将 InfoPlist.strings 添加到 Xcode 工程 |
| | | string _projectPath = PBXProject.GetPBXProjectPath(projectPath); |
| | | PBXProject project = new PBXProject(); |
| | | project.ReadFromString(File.ReadAllText(_projectPath)); |
| | | string targetGUID = project.TargetGuidByName(PBXProject.GetUnityTargetName()); |
| | | |
| | | string relPath = "English.lproj/InfoPlist.strings"; |
| | | string fileGuid = project.AddFile(relPath, relPath, PBXSourceTree.Source); |
| | | project.AddFileToBuild(targetGUID, fileGuid); |
| | | |
| | | // 添加 Sign In with Apple entitlements |
| | | AddEntitlements(projectPath, project, targetGUID); |
| | | |
| | | File.WriteAllText(_projectPath, project.WriteToString()); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 添加 Sign In with Apple 等 entitlements |
| | | /// </summary> |
| | | private static void AddEntitlements(string projectPath, PBXProject project, string targetGUID) |
| | | { |
| | | string entPath = projectPath + "/" + PBXProject.GetUnityTargetName() + ".entitlements"; |
| | | |
| | | // 如果 entitlements 文件不存在,创建 |
| | | if (!File.Exists(entPath)) |
| | | { |
| | | string entContent = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + |
| | | "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" + |
| | | "<plist version=\"1.0\">\n" + |
| | | "<dict>\n" + |
| | | "\t<key>com.apple.developer.applesignin</key>\n" + |
| | | "\t<array>\n" + |
| | | "\t\t<string>Default</string>\n" + |
| | | "\t</array>\n" + |
| | | "</dict>\n" + |
| | | "</plist>\n"; |
| | | File.WriteAllText(entPath, entContent); |
| | | } |
| | | else |
| | | { |
| | | // 已有 entitlements 文件,检查是否已有 Sign In with Apple |
| | | string existingContent = File.ReadAllText(entPath); |
| | | if (!existingContent.Contains("com.apple.developer.applesignin")) |
| | | { |
| | | existingContent = existingContent.Replace("</dict>", |
| | | "\t<key>com.apple.developer.applesignin</key>\n" + |
| | | "\t<array>\n" + |
| | | "\t\t<string>Default</string>\n" + |
| | | "\t</array>\n" + |
| | | "</dict>"); |
| | | File.WriteAllText(entPath, existingContent); |
| | | } |
| | | } |
| | | |
| | | // 将 entitlements 文件加入工程并设置 build property |
| | | string entFileName = PBXProject.GetUnityTargetName() + ".entitlements"; |
| | | string fileGuid = project.AddFile(entFileName, entFileName, PBXSourceTree.Source); |
| | | project.AddFileToBuild(targetGUID, fileGuid); |
| | | project.SetBuildProperty(targetGUID, "CODE_SIGN_ENTITLEMENTS", entFileName); |
| | | } |
| | | |
| | | |
| | | |
| | | 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: |