LubanTools_Gen.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Runtime.InteropServices;
  8. using System.Text;
  9. using System.Threading.Tasks;
  10. using Newtonsoft.Json.Linq;
  11. using UnityEditor;
  12. using Debug = UnityEngine.Debug;
  13. namespace YIUI.Luban.Editor
  14. {
  15. public partial class LubanTools
  16. {
  17. [MenuItem("ET/Excel/ExcelExporter")]
  18. public static void MenuLubanGen()
  19. {
  20. LubanGen();
  21. }
  22. public static bool LubanGen()
  23. {
  24. ClearAll();
  25. return CreateLubanConf();
  26. }
  27. private class SchemaFile
  28. {
  29. public string fileName { get; set; }
  30. public string type { get; set; }
  31. }
  32. private static bool CreateLubanConf()
  33. {
  34. var m_LubanInfos = new Dictionary<string, List<string>>();
  35. var m_LubanConfigs = new Dictionary<string, string>();
  36. foreach (string packageDir in Directory.GetDirectories("Packages", "cn.etetet.*"))
  37. {
  38. var d = Path.Combine(packageDir, "Luban");
  39. if (!Directory.Exists(d))
  40. {
  41. continue;
  42. }
  43. foreach (string directory in Directory.GetDirectories(d))
  44. {
  45. var basePath = Path.Combine(directory, "Base");
  46. var datasPath = Path.Combine(directory, "Datas");
  47. if (!Directory.Exists(basePath) || !Directory.Exists(datasPath))
  48. {
  49. continue;
  50. }
  51. var configCollectionName = Path.GetFileName(directory);
  52. var lubanConfigPath = Path.Combine(directory, "Base/luban.conf");
  53. if (File.Exists(lubanConfigPath))
  54. {
  55. if (m_LubanConfigs.TryGetValue(configCollectionName, out string config))
  56. {
  57. Debug.LogError($"{configCollectionName}, 已经存在这个分类的luban.conf 一种类型只允许存在一个, {config}");
  58. continue;
  59. }
  60. m_LubanConfigs.Add(configCollectionName, lubanConfigPath);
  61. }
  62. if (!m_LubanInfos.TryGetValue(configCollectionName, out var lubanInfos))
  63. {
  64. lubanInfos = new();
  65. m_LubanInfos.Add(configCollectionName, lubanInfos);
  66. }
  67. lubanInfos.Add(directory);
  68. }
  69. }
  70. var allSchemaFiles = new Dictionary<string, List<SchemaFile>>();
  71. foreach ((var configCollectionsName, var lubanInfo) in m_LubanInfos)
  72. {
  73. if (!allSchemaFiles.TryGetValue(configCollectionsName, out var schemaFiles))
  74. {
  75. schemaFiles = new();
  76. allSchemaFiles.Add(configCollectionsName, schemaFiles);
  77. }
  78. foreach (var configPath in lubanInfo)
  79. {
  80. var tablesPath = Path.Combine(configPath, "Base/__tables__.xlsx").Replace('\\', '/');
  81. if (File.Exists(tablesPath))
  82. {
  83. schemaFiles.Add(new() { fileName = $"../../../../../{tablesPath}", type = "table" });
  84. }
  85. var beansPath = Path.Combine(configPath, "Base/__beans__.xlsx").Replace('\\', '/');
  86. if (File.Exists(beansPath))
  87. {
  88. schemaFiles.Add(new() { fileName = $"../../../../../{beansPath}", type = "bean" });
  89. }
  90. var enumsPath = Path.Combine(configPath, "Base/__enums__.xlsx").Replace('\\', '/');
  91. if (File.Exists(enumsPath))
  92. {
  93. schemaFiles.Add(new() { fileName = $"../../../../../{enumsPath}", type = "enum" });
  94. }
  95. var definesPath = Path.Combine(configPath, "Base/Defines").Replace('\\', '/');
  96. if (Directory.Exists(definesPath))
  97. {
  98. schemaFiles.Add(new() { fileName = $"../../../../../{definesPath}", type = "" });
  99. }
  100. }
  101. }
  102. var succeed = false;
  103. try
  104. {
  105. CreateLubanBefore();
  106. EditorUtility.DisplayProgressBar("Luban", "导出Luban配置中...", 0);
  107. var tasks = new List<Task<bool>>();
  108. foreach ((var configCollectionsName, var schemaFiles) in allSchemaFiles)
  109. {
  110. tasks.Add(Task.Run(() =>
  111. {
  112. if (!m_LubanConfigs.TryGetValue(configCollectionsName, out var lubanConfigPath))
  113. {
  114. Debug.LogError($"类型{configCollectionsName} 源文件 luban.conf 不存在");
  115. return false;
  116. }
  117. if (!File.Exists(lubanConfigPath))
  118. {
  119. Debug.LogError($"类型{configCollectionsName} 源文件 luban.conf 不存在");
  120. return false;
  121. }
  122. try
  123. {
  124. string fileContent = File.ReadAllText(lubanConfigPath);
  125. JObject json = JObject.Parse(fileContent);
  126. JArray schemaFilesArray = JArray.FromObject(schemaFiles);
  127. json["schemaFiles"] = schemaFilesArray;
  128. string updatedJsonContent = json.ToString(Newtonsoft.Json.Formatting.Indented);
  129. File.WriteAllText(lubanConfigPath, updatedJsonContent, Encoding.UTF8);
  130. //Debug.Log($"开始导出 {configCollectionsName}");
  131. var result = RunLubanGen($"{lubanConfigPath}/../");
  132. if (!result)
  133. {
  134. return false;
  135. }
  136. else
  137. {
  138. //Debug.Log($"Luban导出完成,{lubanConfigPath}");
  139. }
  140. }
  141. catch (Exception e)
  142. {
  143. Debug.LogError($"创建 LubanConf失败 {configCollectionsName} {e.Message}");
  144. return false;
  145. }
  146. return true;
  147. }));
  148. }
  149. Task.WaitAll(tasks.ToArray());
  150. succeed = tasks.All(t => t.Result);
  151. }
  152. catch (Exception e)
  153. {
  154. Console.WriteLine(e);
  155. throw;
  156. }
  157. finally
  158. {
  159. EditorUtility.ClearProgressBar();
  160. if (succeed)
  161. {
  162. CreateLubanAfterSucceed();
  163. AssetDatabase.SaveAssets();
  164. EditorApplication.ExecuteMenuItem("Assets/Refresh");
  165. CloseWindowRefresh?.Invoke();
  166. Debug.Log($"Luban 导出成功");
  167. }
  168. else
  169. {
  170. CreateLubanAfterFailed();
  171. CloseWindow?.Invoke();
  172. Debug.LogError($"Luban 导出失败");
  173. }
  174. }
  175. return true;
  176. }
  177. private static bool m_UsePs = true; // 是否使用PowerShell
  178. private static bool m_ToUnixEOL = false; // 是否转换为Unix换行符
  179. private static void ConvertLineEndings(string filePath, string newLine)
  180. {
  181. string fileContent = File.ReadAllText(filePath);
  182. string updatedContent = fileContent.Replace("\r\n", "\n").Replace("\n", newLine);
  183. File.WriteAllText(filePath, updatedContent);
  184. }
  185. private static bool RunLubanGen(string configCollectionPath, bool tips = false)
  186. {
  187. var scriptDir = Path.GetDirectoryName(configCollectionPath);
  188. var filePattern = m_UsePs ? "*.ps1" : "*.bat";
  189. var foundScripts = Directory.GetFiles(scriptDir, filePattern);
  190. if (foundScripts.Length == 0)
  191. {
  192. Debug.LogError($"找不到Luban脚本: 在目录 {scriptDir} 中找不到任何 {filePattern} 文件");
  193. return false;
  194. }
  195. if (foundScripts.Length > 1)
  196. {
  197. var tasks = new List<Task<bool>>();
  198. foreach (var script in foundScripts)
  199. {
  200. tasks.Add(Task.Run(() => ExecuteSingleScript(script, tips)));
  201. }
  202. Task.WaitAll(tasks.ToArray());
  203. return tasks.All(t => t.Result);
  204. }
  205. return ExecuteSingleScript(foundScripts[0], tips);
  206. }
  207. private static bool ExecuteSingleScript(string scriptPath, bool tips)
  208. {
  209. if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
  210. {
  211. if (m_UsePs)
  212. {
  213. return RunProcess("powershell.exe", $"-ExecutionPolicy Bypass -File \"{scriptPath}\"", tips);
  214. }
  215. else
  216. {
  217. ConvertLineEndings(scriptPath, "\r\n");
  218. return RunProcess(scriptPath, "", tips);
  219. }
  220. }
  221. else
  222. {
  223. if (m_UsePs)
  224. {
  225. return RunProcess("/usr/local/bin/pwsh", $"-ExecutionPolicy Bypass -File \"{scriptPath}\"", tips);
  226. }
  227. else
  228. {
  229. ChangePermissions(scriptPath, "755");
  230. return RunProcess("/bin/bash", $"-c \"{scriptPath}\"", tips);
  231. }
  232. }
  233. }
  234. private static void ChangePermissions(string filePath, string permissions)
  235. {
  236. try
  237. {
  238. ProcessStartInfo processInfo = new ProcessStartInfo
  239. {
  240. FileName = "/bin/chmod",
  241. Arguments = $"{permissions} \"{filePath}\"",
  242. RedirectStandardOutput = true,
  243. UseShellExecute = false,
  244. CreateNoWindow = true
  245. };
  246. using (Process process = Process.Start(processInfo))
  247. {
  248. process.WaitForExit();
  249. string output = process.StandardOutput.ReadToEnd();
  250. // You can handle the output if needed
  251. }
  252. }
  253. catch (System.Exception e)
  254. {
  255. Debug.LogError($"Failed to change file permissions: {e}");
  256. }
  257. }
  258. private static bool RunProcess(string exe, string arguments, bool tips = false, string workingDirectory = ".", bool waitExit = true)
  259. {
  260. var redirectStandardOutput = false;
  261. var redirectStandardError = false;
  262. var useShellExecute = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
  263. if (waitExit)
  264. {
  265. redirectStandardOutput = true;
  266. redirectStandardError = true;
  267. useShellExecute = false;
  268. }
  269. var ImportAllOutput = new StringBuilder();
  270. var ImportAllError = new StringBuilder();
  271. var importProcess = new Process();
  272. importProcess.StartInfo.FileName = exe;
  273. importProcess.StartInfo.Arguments = arguments;
  274. importProcess.StartInfo.WorkingDirectory = workingDirectory;
  275. importProcess.StartInfo.UseShellExecute = useShellExecute;
  276. importProcess.StartInfo.CreateNoWindow = true;
  277. importProcess.StartInfo.RedirectStandardOutput = redirectStandardOutput;
  278. importProcess.StartInfo.RedirectStandardError = redirectStandardError;
  279. importProcess.StartInfo.StandardOutputEncoding = Encoding.UTF8;
  280. importProcess.StartInfo.StandardErrorEncoding = Encoding.UTF8;
  281. importProcess.OutputDataReceived += (_, args) =>
  282. {
  283. if (!string.IsNullOrEmpty(args.Data))
  284. {
  285. if (args.Data.Contains("ERROR"))
  286. {
  287. ImportAllError.AppendLine(args.Data);
  288. }
  289. ImportAllOutput.AppendLine(args.Data);
  290. }
  291. };
  292. importProcess.ErrorDataReceived += (_, args) =>
  293. {
  294. if (!string.IsNullOrEmpty(args.Data))
  295. {
  296. ImportAllError.AppendLine(args.Data);
  297. }
  298. };
  299. var succeed = false;
  300. try
  301. {
  302. importProcess.Start();
  303. importProcess.BeginOutputReadLine();
  304. importProcess.BeginErrorReadLine();
  305. var exited = importProcess.WaitForExit(20000);
  306. if (!exited)
  307. {
  308. Debug.Log($"Luban导出超时,终止进程 请检查原因是 真是太大 还是有问题!! 如果是真的太大就修改这个超时时间");
  309. importProcess.Kill();
  310. succeed = false;
  311. }
  312. else
  313. {
  314. importProcess.WaitForExit();
  315. var output = ImportAllOutput.ToString();
  316. if (!string.IsNullOrEmpty(output))
  317. {
  318. //Debug.Log($"Luban导出日志:\n{output}");
  319. }
  320. var error = ImportAllError.ToString();
  321. succeed = string.IsNullOrEmpty(error);
  322. if (!succeed)
  323. {
  324. Debug.Log($"Luban导出日志:\n{output}");
  325. if (tips)
  326. {
  327. UnityTipsHelper.ShowError($"Luban导出错误:\n{error}");
  328. }
  329. else
  330. {
  331. Debug.LogError($"Luban导出错误:\n{error}");
  332. }
  333. }
  334. }
  335. }
  336. catch (Exception e)
  337. {
  338. Debug.Log($"Luban导出执行报错: {e}");
  339. return false;
  340. }
  341. finally
  342. {
  343. importProcess.Close();
  344. }
  345. return succeed;
  346. }
  347. private static List<string> GetLubanCodeModePath()
  348. {
  349. var allPath = new List<string>();
  350. foreach (string directory in Directory.GetDirectories("Packages", "cn.etetet.*"))
  351. {
  352. var cPath = Path.Combine(directory, "CodeMode/Model/Client/LubanGen");
  353. if (Directory.Exists(cPath))
  354. {
  355. allPath.Add(cPath);
  356. }
  357. var csPath = Path.Combine(directory, "CodeMode/Model/ClientServer/LubanGen");
  358. if (Directory.Exists(csPath))
  359. {
  360. allPath.Add(csPath);
  361. }
  362. var sPath = Path.Combine(directory, "CodeMode/Model/Server/LubanGen");
  363. if (Directory.Exists(sPath))
  364. {
  365. allPath.Add(sPath);
  366. }
  367. }
  368. return allPath;
  369. }
  370. //执行前
  371. private static void CreateLubanBefore()
  372. {
  373. if (Directory.Exists(BackupRootPath))
  374. {
  375. Directory.Delete(BackupRootPath, true);
  376. }
  377. Directory.CreateDirectory(BackupRootPath);
  378. foreach (var path in GetLubanCodeModePath())
  379. {
  380. foreach (var file in Directory.EnumerateFiles(path, "*.cs", SearchOption.AllDirectories))
  381. {
  382. if (Path.GetExtension(file).Equals(".cs", StringComparison.OrdinalIgnoreCase))
  383. {
  384. try
  385. {
  386. var relativePath = Path.GetRelativePath(path, file);
  387. var backupPath = Path.Combine(BackupRootPath, path, relativePath);
  388. Directory.CreateDirectory(Path.GetDirectoryName(backupPath) ?? throw new InvalidOperationException());
  389. File.Copy(file, backupPath, true);
  390. File.Delete(file);
  391. }
  392. catch (Exception ex)
  393. {
  394. Debug.LogError($"Backup failed: {file}\n{ex}");
  395. }
  396. }
  397. }
  398. }
  399. }
  400. /// <summary>
  401. /// 转换单个文件的换行符为Unix格式 (LF)
  402. /// </summary>
  403. private static void ConvertFileToUnixEOL(string filePath)
  404. {
  405. try
  406. {
  407. // 读取文件内容
  408. var content = File.ReadAllText(filePath);
  409. bool modified = false;
  410. // 检查是否包含 Windows 换行符 (CRLF)
  411. if (content.Contains("\r\n"))
  412. {
  413. // 转换为 Unix 换行符 (LF)
  414. content = content.Replace("\r\n", "\n");
  415. modified = true;
  416. }
  417. // 检查是否包含旧 Mac 换行符 (CR)
  418. else if (content.Contains("\r") && !content.Contains("\n"))
  419. {
  420. // 转换为 Unix 换行符 (LF)
  421. content = content.Replace("\r", "\n");
  422. modified = true;
  423. }
  424. // 如果有修改,写回文件
  425. if (modified)
  426. {
  427. File.WriteAllText(filePath, content);
  428. var fileType = Path.GetExtension(filePath).ToLower();
  429. Debug.Log($"Converted {fileType} EOL to Unix format: {filePath}");
  430. }
  431. }
  432. catch (Exception ex)
  433. {
  434. Debug.LogError($"Failed to convert EOL for file: {filePath}\n{ex}");
  435. }
  436. }
  437. //执行后 成功
  438. private static void CreateLubanAfterSucceed()
  439. {
  440. foreach (var path in GetLubanCodeModePath())
  441. {
  442. var directories = Directory.GetDirectories(path, "*", SearchOption.AllDirectories).ToList();
  443. directories.Add(path);
  444. foreach (var dir in directories.OrderByDescending(d => d.Length))
  445. {
  446. if (!Directory.EnumerateFiles(dir, "*.cs", SearchOption.AllDirectories).Any())
  447. {
  448. var parentDir = Path.GetDirectoryName(dir);
  449. if (!string.IsNullOrEmpty(parentDir))
  450. {
  451. var metaFileName = Path.GetFileName(dir) + ".meta";
  452. var metaFilePath = Path.Combine(parentDir, metaFileName);
  453. if (File.Exists(metaFilePath))
  454. {
  455. try
  456. {
  457. File.Delete(metaFilePath);
  458. }
  459. catch
  460. {
  461. /* 可添加日志 */
  462. }
  463. }
  464. }
  465. try
  466. {
  467. Directory.Delete(dir, true);
  468. }
  469. catch
  470. {
  471. /* 可添加日志记录 */
  472. }
  473. }
  474. }
  475. }
  476. if (m_ToUnixEOL)
  477. {
  478. // 遍历所有生成的 .cs 文件,统一转换为 Unix 换行符 (LF)
  479. foreach (var path in GetLubanCodeModePath())
  480. {
  481. foreach (var file in Directory.EnumerateFiles(path, "*.cs", SearchOption.AllDirectories))
  482. {
  483. ConvertFileToUnixEOL(file);
  484. }
  485. }
  486. // 处理生成的 JSON 文件,统一转换为 Unix 换行符 (LF)
  487. var jsonConfigPath = "Packages/cn.etetet.yiuilubangen/Assets/LubanGen/Config/Json";
  488. if (Directory.Exists(jsonConfigPath))
  489. {
  490. foreach (var file in Directory.EnumerateFiles(jsonConfigPath, "*.json", SearchOption.AllDirectories))
  491. {
  492. ConvertFileToUnixEOL(file);
  493. }
  494. }
  495. // 处理所有的 luban.conf 文件,统一转换为 Unix 换行符 (LF)
  496. foreach (string packageDir in Directory.GetDirectories("Packages", "cn.etetet.*"))
  497. {
  498. var lubanDir = Path.Combine(packageDir, "Luban");
  499. if (!Directory.Exists(lubanDir))
  500. {
  501. continue;
  502. }
  503. foreach (var confFile in Directory.EnumerateFiles(lubanDir, "luban.conf", SearchOption.AllDirectories))
  504. {
  505. ConvertFileToUnixEOL(confFile);
  506. }
  507. }
  508. }
  509. if (Directory.Exists(BackupRootPath))
  510. {
  511. Directory.Delete(BackupRootPath, true);
  512. }
  513. }
  514. //临时备份目录
  515. private static readonly string BackupRootPath = Path.Combine(Path.GetTempPath(), "LubanBackup");
  516. //执行后 失败
  517. private static void CreateLubanAfterFailed()
  518. {
  519. if (!Directory.Exists(BackupRootPath)) return;
  520. foreach (var codeModePath in GetLubanCodeModePath())
  521. {
  522. var backupCodePath = Path.Combine(BackupRootPath, codeModePath);
  523. if (!Directory.Exists(backupCodePath)) continue;
  524. foreach (var backupFile in Directory.EnumerateFiles(backupCodePath, "*.cs", SearchOption.AllDirectories))
  525. {
  526. try
  527. {
  528. var relativePath = Path.GetRelativePath(backupCodePath, backupFile);
  529. var originalPath = Path.Combine(codeModePath, relativePath);
  530. Directory.CreateDirectory(Path.GetDirectoryName(originalPath) ?? throw new InvalidOperationException());
  531. File.Copy(backupFile, originalPath, true);
  532. }
  533. catch (Exception ex)
  534. {
  535. Debug.LogError($"Restore failed: {backupFile}\n{ex}");
  536. }
  537. }
  538. }
  539. Directory.Delete(BackupRootPath, true);
  540. }
  541. private static void ClearAll()
  542. {
  543. try
  544. {
  545. var modelAssembly = Assembly.Load("ET.Model");
  546. if (modelAssembly == null)
  547. {
  548. //throw new Exception("没有找到 ET.Model 程序集");
  549. return;
  550. }
  551. var type = modelAssembly.GetType("ET.LubanEditorConfigCategory");
  552. if (type == null)
  553. {
  554. //throw new Exception("找不到 ET.LubanEditorConfigCategory 类型");
  555. return;
  556. }
  557. var clearAllMethod = type.GetMethod("ClearAll", BindingFlags.Public | BindingFlags.Static);
  558. if (clearAllMethod == null)
  559. {
  560. //throw new Exception("找不到 ClearAll 方法");
  561. return;
  562. }
  563. clearAllMethod.Invoke(null, null);
  564. }
  565. catch
  566. {
  567. //throw;
  568. }
  569. }
  570. }
  571. }