PostProcessBuild_iOS.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. //-----------------------------------------------------------------------------
  2. // Copyright 2012-2024 RenderHeads Ltd. All rights reserved.
  3. //-----------------------------------------------------------------------------
  4. #if (UNITY_IOS || UNITY_TVOS || UNITY_VISIONOS) && UNITY_2017_1_OR_NEWER
  5. // Unity versions where xcframework support was added
  6. // 2023.2.18f1
  7. // 2022.3.23f1
  8. // 2021.3.37f1
  9. // There has to be a better way...
  10. #if UNITY_2023_2_OR_NEWER && !(UNITY_2023_2_0 || UNITY_2023_2_1 || UNITY_2023_2_2 || UNITY_2023_2_3 || UNITY_2023_2_4 || UNITY_2023_2_5 || UNITY_2023_2_6 || UNITY_2023_2_7 || UNITY_2023_2_8 || UNITY_2023_2_9 || UNITY_2023_2_10 || UNITY_2023_2_11 || UNITY_2023_2_12 || UNITY_2023_2_13 || UNITY_2023_2_14 || UNITY_2023_2_15 || UNITY_2023_2_16 || UNITY_2023_2_17)
  11. #define AVPROVIDEO_UNITY_SUPPORTS_XCFRAMEWORKS
  12. #elif UNITY_2023_1_OR_NEWER
  13. #define AVPROVIDEO_UNITY_DOES_NOT_SUPPORT_XCFRAMEWORKS
  14. #elif UNITY_2022_3_OR_NEWER && !(UNITY_2022_3_0 || UNITY_2022_3_1 || UNITY_2022_3_2 || UNITY_2022_3_3 || UNITY_2022_3_4 || UNITY_2022_3_5 || UNITY_2022_3_6 || UNITY_2022_3_7 || UNITY_2022_3_8 || UNITY_2022_3_9 || UNITY_2022_3_10 || UNITY_2022_3_11 || UNITY_2022_3_12 || UNITY_2022_3_13 || UNITY_2022_3_14 || UNITY_2022_3_15 || UNITY_2022_3_16 || UNITY_2022_3_17 || UNITY_2022_3_18 || UNITY_2022_3_19 || UNITY_2022_3_20 || UNITY_2022_3_21 || UNITY_2022_3_22)
  15. #define AVPROVIDEO_UNITY_SUPPORTS_XCFRAMEWORKS
  16. #elif UNITY_2022_1_OR_NEWER
  17. #define AVPROVIDEO_UNITY_DOES_NOT_SUPPORT_XCFRAMEWORKS
  18. #elif UNITY_2021_3_OR_NEWER && !(UNITY_2021_3_0 || UNITY_2021_3_1 || UNITY_2021_3_2 || UNITY_2021_3_3 || UNITY_2021_3_4 || UNITY_2021_3_5 || UNITY_2021_3_6 || UNITY_2021_3_7 || UNITY_2021_3_8 || UNITY_2021_3_9 || UNITY_2021_3_10 || UNITY_2021_3_11 || UNITY_2021_3_12 || UNITY_2021_3_13 || UNITY_2021_3_14 || UNITY_2021_3_15 || UNITY_2021_3_16 || UNITY_2021_3_17 || UNITY_2021_3_18 || UNITY_2021_3_19 || UNITY_2021_3_20 || UNITY_2021_3_21 || UNITY_2021_3_22 || UNITY_2021_3_23 || UNITY_2021_3_24 || UNITY_2021_3_25 || UNITY_2021_3_26 || UNITY_2021_3_27 || UNITY_2021_3_28 || UNITY_2021_3_29 || UNITY_2021_3_30 || UNITY_2021_3_31 || UNITY_2021_3_32 || UNITY_2021_3_33 || UNITY_2021_3_34 || UNITY_2021_3_35 || UNITY_2021_3_36)
  19. #define AVPROVIDEO_UNITY_SUPPORTS_XCFRAMEWORKS
  20. #else
  21. #define AVPROVIDEO_UNITY_DOES_NOT_SUPPORT_XCFRAMEWORKS
  22. #endif
  23. using UnityEngine;
  24. using UnityEditor;
  25. using UnityEditor.Callbacks;
  26. using UnityEditor.iOS.Xcode;
  27. using System;
  28. using System.Collections.Generic;
  29. using System.IO;
  30. namespace RenderHeads.Media.AVProVideo.Editor
  31. {
  32. public class PostProcessBuild_iOS
  33. {
  34. const string AVProVideoPluginName = "AVProVideo.xcframework";
  35. const string AVProVideoBootstrap = "extern void AVPPluginUnityRegisterRenderingPlugin(void*);\nvoid AVPPluginBootstrap(void) {\n\tAVPPluginUnityRegisterRenderingPlugin(UnityRegisterRenderingPluginV5);\n}\n";
  36. const string AVProVideoForceSwift = "import Foundation\n";
  37. private class Platform
  38. {
  39. public BuildTarget target { get; }
  40. public string name { get; }
  41. public string guid { get; }
  42. public static Platform GetPlatformForTarget(BuildTarget target)
  43. {
  44. switch (target)
  45. {
  46. case BuildTarget.iOS:
  47. return new Platform(BuildTarget.iOS, "iOS", "a7ee58e0e533849d3a37458bc7df6df7");
  48. case BuildTarget.tvOS:
  49. return new Platform(BuildTarget.tvOS, "tvOS", "f83f62879d8fb417cb18d0547c9bfd02");
  50. #if UNITY_2022_3
  51. case BuildTarget.VisionOS:
  52. return new Platform(BuildTarget.VisionOS, "visionOS", "fe151797423674af0941aae11c872b90");
  53. #endif
  54. default:
  55. return null;
  56. }
  57. }
  58. private Platform(BuildTarget target, string name, string guid)
  59. {
  60. this.target = target;
  61. this.name = name;
  62. this.guid = guid;
  63. }
  64. }
  65. /// <summary>
  66. /// Get the plugin path for the platform specified
  67. /// </summary>
  68. /// <param name="platform">The platform</param>
  69. /// <param name="pluginName">The plugin's file name</param>
  70. /// <returns>The path of the plugin within Unity's assets folder</returns>
  71. private static string PluginPathForPlatform(Platform platform, string pluginName)
  72. {
  73. // See if we can find the plugin by GUID
  74. string pluginPath = AssetDatabase.GUIDToAssetPath(platform.guid);
  75. // If not, try and find it by name
  76. if (pluginPath.Length == 0)
  77. {
  78. Debug.LogWarningFormat("[AVProVideo] Failed to find plugin by GUID, will attempt to find it by name.");
  79. string[] guids = AssetDatabase.FindAssets(pluginName);
  80. if (guids != null && guids.Length > 0)
  81. {
  82. foreach (string guid in guids)
  83. {
  84. string assetPath = AssetDatabase.GUIDToAssetPath(guid);
  85. if (assetPath.Contains(platform.name))
  86. {
  87. pluginPath = assetPath;
  88. break;
  89. }
  90. }
  91. }
  92. }
  93. if (pluginPath.Length > 0)
  94. {
  95. Debug.LogFormat("[AVProVideo] Found plugin at '{0}'", pluginPath);
  96. }
  97. return pluginPath;
  98. }
  99. /// <summary>
  100. /// Gets the target guid if Unity's framework target from the project provided
  101. /// </summary>
  102. /// <param name="project">The project to get the guid from</param>
  103. /// <returns></returns>
  104. private static string GetUnityFrameworkTargetGuid(PBXProject project)
  105. {
  106. return project.GetUnityFrameworkTargetGuid();
  107. }
  108. /// <summary>
  109. /// Copies a directory.
  110. /// </summary>
  111. /// <remarks>
  112. /// Intended for use outside of Unity's project structure, this will skip meta files when copying.
  113. /// </remarks>
  114. /// <param name="src">The directory info of the directory to copy</param>
  115. /// <param name="dst">The directory info of the destination directory</param>
  116. private static void CopyDirectory(DirectoryInfo srcDirInfo, DirectoryInfo dstDirInfo)
  117. {
  118. // Make sure the target directory exists
  119. Directory.CreateDirectory(dstDirInfo.FullName);
  120. // Copy over the sub-directories
  121. foreach (DirectoryInfo subSrcDirInfo in srcDirInfo.GetDirectories())
  122. {
  123. DirectoryInfo subDstDirInfo = dstDirInfo.CreateSubdirectory(subSrcDirInfo.Name);
  124. CopyDirectory(subSrcDirInfo, subDstDirInfo);
  125. }
  126. // Copy over the files
  127. foreach (FileInfo srcFileInfo in srcDirInfo.GetFiles())
  128. {
  129. if (srcFileInfo.Extension == ".meta")
  130. {
  131. // Do not want to copy Unity's meta files into the built project
  132. continue;
  133. }
  134. else
  135. if (srcFileInfo.Name == ".DS_Store")
  136. {
  137. // Do not want to copy .DS_Store files into the built project
  138. continue;
  139. }
  140. else
  141. {
  142. srcFileInfo.CopyTo(Path.Combine(dstDirInfo.FullName, srcFileInfo.Name), true);
  143. }
  144. }
  145. }
  146. /// <summary>
  147. /// Copies a directory.
  148. /// </summary>
  149. /// <remarks>
  150. /// Intended for use outside of Unity's project structure, this will skip meta files when copying.
  151. /// </remarks>
  152. /// <param name="src">The path of the directory to copy</param>
  153. /// <param name="dst">The path where the directory will be copied to</param>
  154. private static void CopyDirectory(string src, string dst)
  155. {
  156. CopyDirectory(new DirectoryInfo(src), new DirectoryInfo(dst));
  157. }
  158. /// <summary>
  159. /// Tests the target build platform to see if it's supported by this script
  160. /// </summary>
  161. /// <param name="target">The target build platform</param>
  162. /// <returns>true if the build target is supported, false otherwise</returns>
  163. private static bool IsBuildTargetSupported(BuildTarget target)
  164. {
  165. switch (target)
  166. {
  167. case BuildTarget.iOS:
  168. case BuildTarget.tvOS:
  169. #if UNITY_2022_3
  170. case BuildTarget.VisionOS:
  171. #endif
  172. return true;
  173. default:
  174. return false;
  175. }
  176. }
  177. /// <summary>
  178. /// Gets the Xcode project name for the target specified.
  179. /// </summary>
  180. /// <param name="target">The build target</param>
  181. /// <returns>The Xcode project name</returns>
  182. private static string GetXcodeProjectNameForBuildTarget(BuildTarget target)
  183. {
  184. switch (target)
  185. {
  186. case BuildTarget.iOS:
  187. case BuildTarget.tvOS:
  188. return "Unity-iPhone.xcodeproj";
  189. #if UNITY_2022_3
  190. case BuildTarget.VisionOS:
  191. return "Unity-VisionOS.xcodeproj";
  192. #endif
  193. default:
  194. Debug.LogError($"[AVProVideo] GetXcodeProjectNameForBuildTarget - unrecognised build target: {target}");
  195. return null;
  196. }
  197. }
  198. // Converts the Unity asset path to the expected path in the built Xcode project.
  199. private static string ConvertPluginAssetPathToXcodeProjectPath(string pluginPath, string subFolder)
  200. {
  201. List<string> components = new List<string>(pluginPath.Split(new char[] { '/' }));
  202. #if UNITY_TVOS
  203. // Unity just copies the xcframework into the frameworks folder
  204. string frameworkPath = Path.Combine(subFolder, components[^1]);
  205. #else
  206. #if UNITY_VISIONOS
  207. // For reasons unknown unity puts everything under an ARM64 folder on visionOS
  208. components.Insert(0, "ARM64");
  209. components.Insert(0, subFolder);
  210. #else
  211. components[0] = subFolder;
  212. #endif
  213. #if UNITY_2019_1_OR_NEWER
  214. string frameworkPath = string.Join("/", components);
  215. #else
  216. string frameworkPath = string.Join("/", components.ToArray());
  217. #endif
  218. #endif
  219. return frameworkPath;
  220. }
  221. //
  222. private static void StripMetaFilesFromDirectory(DirectoryInfo dirInfo)
  223. {
  224. // Remove any meta files
  225. foreach (FileInfo srcFileInfo in dirInfo.GetFiles())
  226. {
  227. if (srcFileInfo.Extension == ".meta")
  228. {
  229. Debug.Log($"[AVProVideo] Deleting {srcFileInfo.FullName}");
  230. File.Delete(srcFileInfo.FullName);
  231. }
  232. else
  233. if (srcFileInfo.Name == ".DS_Store")
  234. {
  235. Debug.Log($"[AVProVideo] Deleting {srcFileInfo.FullName}");
  236. File.Delete(srcFileInfo.FullName);
  237. }
  238. }
  239. // Do the same for any sub-directories
  240. foreach (DirectoryInfo subDirInfo in dirInfo.GetDirectories("*"))
  241. {
  242. StripMetaFilesFromDirectory(subDirInfo);
  243. }
  244. }
  245. /// <summary>
  246. /// Post-process the generated Xcode project to add the plugin and any build configuration required.
  247. /// </summary>
  248. /// <param name="target">The target build platform</param>
  249. /// <param name="path">The path to the built project</param>
  250. [PostProcessBuild]
  251. public static void ModifyProject(BuildTarget target, string path)
  252. {
  253. if (!IsBuildTargetSupported(target))
  254. {
  255. // Nothing to be done
  256. return;
  257. }
  258. Debug.Log("[AVProVideo] Post-processing generated Xcode project...");
  259. Platform platform = Platform.GetPlatformForTarget(target);
  260. if (platform == null)
  261. {
  262. Debug.LogWarningFormat("[AVProVideo] Unknown build target: {0}, stopping", target);
  263. return;
  264. }
  265. // Create the path to the generated Xcode project file
  266. string xcodeProjectName = GetXcodeProjectNameForBuildTarget(target);
  267. if (xcodeProjectName == null)
  268. {
  269. return;
  270. }
  271. string xcodeProjectPath = Path.Combine(path, xcodeProjectName, "project.pbxproj");
  272. Debug.Log($"[AVProVideo] Opening Xcode project at: {path}");
  273. // Open the project
  274. PBXProject project = new PBXProject();
  275. project.ReadFromFile(xcodeProjectPath);
  276. // Attempt to find the plugin path
  277. string pluginPath = PluginPathForPlatform(platform, AVProVideoPluginName);
  278. if (pluginPath.Length == 0)
  279. {
  280. Debug.LogErrorFormat("[AVProVideo] Failed to find '{0}' for '{1}' in the Unity project. Something is horribly wrong, please reinstall AVPro Video.", AVProVideoPluginName, platform);
  281. return;
  282. }
  283. Debug.Log($"[AVProVideo] Plugin path: {pluginPath}");
  284. string destPluginPath = Path.Combine("Libraries", "AVProVideo");
  285. Directory.CreateDirectory(Path.Combine(path, destPluginPath));
  286. // Get the Unity framework target GUID
  287. string unityFrameworkTargetGuid = GetUnityFrameworkTargetGuid(project);
  288. #if AVPROVIDEO_UNITY_DOES_NOT_SUPPORT_XCFRAMEWORKS
  289. // Get the path to the xcframework
  290. string xcframeworkPath = Path.Combine(destPluginPath, AVProVideoPluginName);
  291. // Copy over the xcframework to the generated xcode project
  292. Debug.Log($"[AVProVideo] Copying AVProVideo.xcframework into the Xcode project at {destPluginPath}");
  293. CopyDirectory(pluginPath, Path.Combine(path, xcframeworkPath));
  294. if (!project.ContainsFileByProjectPath(xcframeworkPath))
  295. {
  296. Debug.Log("[AVProVideo] Adding AVProVideo.xcframework to the UnityFramework target");
  297. // Add the xcframework and sundry files to the project
  298. string xcframeworkGuid = project.AddFile(xcframeworkPath, xcframeworkPath);
  299. // Get the frameworks build phase and add the xcframework to it
  300. string frameworksBuildPhaseForUnityFrameworkTarget = project.GetFrameworksBuildPhaseByTarget(unityFrameworkTargetGuid);
  301. project.AddFileToBuildSection(unityFrameworkTargetGuid, frameworksBuildPhaseForUnityFrameworkTarget, xcframeworkGuid);
  302. }
  303. #else
  304. // If we've upgraded Unity to a version that supports xcframeworks we need to purge the meta files from the plugin
  305. // string xcframeworkPath = ConvertPluginAssetPathToXcodeProjectPath(pluginPath, "Frameworks");
  306. string xcframeworkPath = string.Empty;
  307. IReadOnlyList<string> paths = project.GetRealPathsOfAllFiles(PBXSourceTree.Source);
  308. foreach (string p in paths)
  309. {
  310. if (p.EndsWith(AVProVideoPluginName))
  311. {
  312. xcframeworkPath = p;
  313. break;
  314. }
  315. }
  316. if (!string.IsNullOrEmpty(xcframeworkPath))
  317. {
  318. string dest_xcframeworkPath = Path.Combine(path, xcframeworkPath);
  319. Debug.Log($"[AVProVideo] xcframework path is: {dest_xcframeworkPath}");
  320. StripMetaFilesFromDirectory(new DirectoryInfo(dest_xcframeworkPath));
  321. }
  322. // string dest_xcframeworkPath = Path.Combine(path, xcframeworkPath);
  323. // if (!project.ContainsFileByProjectPath(xcframeworkPath))
  324. // {
  325. // Debug.Log("[AVProVideo] Stripping meta files from AVProVideo.xcframework");
  326. // StripMetaFilesFromDirectory(new DirectoryInfo(dest_xcframeworkPath));
  327. // }
  328. else
  329. {
  330. Debug.LogError($"[AVProVideo] Failed to find AVProPlugin.xcframework in the built project");
  331. }
  332. #endif
  333. Debug.Log("[AVProVideo] Writing AVProVideoBootstrap.m to the UnityFramework target");
  334. string bootstrapPath = Path.Combine(destPluginPath, "AVProVideoBootstrap.m");
  335. File.WriteAllText(Path.Combine(path, bootstrapPath), AVProVideoBootstrap);
  336. string bootstrapGuid = project.AddFile(bootstrapPath, bootstrapPath);
  337. project.AddFileToBuild(unityFrameworkTargetGuid, bootstrapGuid);
  338. string forceSwiftPath = Path.Combine(destPluginPath, "AVProVideoForceSwift.swift");
  339. Debug.Log("[AVProVideo] Writing AVProVideoForceSwift.swift to the UnityFramework target");
  340. File.WriteAllText(Path.Combine(path, forceSwiftPath), AVProVideoForceSwift);
  341. string forceSwiftGuid = project.AddFile(forceSwiftPath, forceSwiftPath);
  342. project.AddFileToBuild(unityFrameworkTargetGuid, forceSwiftGuid);
  343. // Make sure the swift version is set to 5.0
  344. string swiftVersionStr = project.GetBuildPropertyForAnyConfig(unityFrameworkTargetGuid, "SWIFT_VERSION");
  345. decimal swiftVersion;
  346. if (!Decimal.TryParse(swiftVersionStr, out swiftVersion) || (swiftVersion < 5))
  347. {
  348. Debug.Log("[AVProVideo] setting SWIFT_VERSION to 5.0 for the UnityFramework target");
  349. project.SetBuildProperty(unityFrameworkTargetGuid, "SWIFT_VERSION", "5.0");
  350. }
  351. // Done
  352. project.WriteToFile(xcodeProjectPath);
  353. Debug.Log("[AVProVideo] Finished modifying Xcode project");
  354. }
  355. }
  356. }
  357. #endif