UNPKG

unity-find-fault

Version:

A tool to find fault in unity project.

250 lines 11.3 kB
import fg from "fast-glob"; import fs from "fs-extra"; import path from "path"; import { UnityHelper } from "./UnityHelper.js"; import { Record } from "./Record.js"; import { toolchain } from "../toolchain.js"; import { SVN } from "./SVN.js"; export class UIStriper { async removeUseless() { const assetSourcesRoot = path.join(toolchain.opts.projectRoot, 'Assets/AssetSources'); const keepMap = {}; const cfgHitMap = {}; const usedBySrcMap = {}; const configuredPaths = []; if (toolchain.opts.cfg != null) { // 部分写死保留的prefab if (toolchain.opts.cfg.keep != null) { for (const k of toolchain.opts.cfg.keep) { const keeps = await fg(k, { cwd: toolchain.opts.projectRoot }); for (const f of keeps) { keepMap[path.normalize(f)] = true; } } } // 读入表格使用的模型 if (toolchain.opts.cfg.prefabCfg != null) { for (const pc of toolchain.opts.cfg.prefabCfg) { if (pc.disabled) continue; cfgHitMap[pc.path] = true; if (pc.path != '*') { configuredPaths.push(pc.path.substring(0, pc.path.indexOf('*'))); } for (const rule of pc.rules) { let jfiles; if (rule.cfgFile.includes('*')) { // 包含多个json文件 jfiles = await fg(rule.cfgFile, { cwd: assetSourcesRoot }); jfiles = jfiles.map((v) => path.join(assetSourcesRoot, v)); } else { jfiles = [path.join(assetSourcesRoot, 'data', rule.cfgFile)]; } for (let jf of jfiles) { let cfgs = await fs.readJson(jf); if (!(cfgs instanceof Array)) { cfgs = [cfgs]; } const keys = rule.cfgKey.split('.'); const values = this.getMatches(cfgs, keys); for (const v of values) { const p = pc.path.replaceAll('*', v).toLowerCase(); if (p.includes('model/chatBubble')) { console.log('kkk'); } usedBySrcMap[p] = true; usedBySrcMap[p.replace('.prefab', 'b.prefab')] = true; usedBySrcMap[p.replace('.prefab', '_b.prefab')] = true; } } } } } } else { console.error('no cfg!'); process.exit(1); } // 收集ts代码/cs代码引用到的ui prefab //#region const srcs = await fg(['TsScripts/**/*.ts', 'Assets/**/*.cs'], { cwd: toolchain.opts.projectRoot }); const variables = [], notExists = [], potentialPrefabs = [], potentialPaths = [], exactlyNames = []; const allMchs = []; for (const src of srcs) { const file = path.join(toolchain.opts.projectRoot, src); const content = await fs.readFile(file, 'utf-8'); const lines = content.split(/\r?\n/); for (const line of lines) { if (line.startsWith('import ') || line.includes('this.elems.get') || line.includes('ElemFinder.')) continue; const mchs = line.matchAll(/('|"|`)\S*?\1/g); for (const mch of mchs) { const s = mch[0]; if (s.startsWith('.') || s.startsWith('\\') || s.startsWith('http://') || s.startsWith('https://') || s.startsWith('images/') || s.startsWith('com/') || s.search(/[\w]/) < 0 || s.search(/[\u4e00-\u9fa5]+/) >= 0) continue; if (!allMchs.includes(s)) { allMchs.push(s); } } } } for (const mch of allMchs) { // 尝试进行通配检测 const quoted = mch.substring(1, mch.length - 1); if (quoted == 'Draw_3b') { console.log('aaa'); } const extn = path.extname(quoted); if (extn == '.prefab') { // 明确是prefab const str = quoted.replaceAll(/\{\d+\}/g, '*').replaceAll(/\$\{\w+\}/g, '*').replaceAll(/"\s*\+\s*\w+\s*\+\s*"/g, '*'); if (str == '.prefab' || str == '*.prefab' || str.includes('*/*/')) continue; if (cfgHitMap[str]) continue; let files; if (str.includes('*')) { files = []; // 检查是否有关联配置信息 if (!configuredPaths.includes(str.substring(0, str.indexOf('*')))) { const hits = await fg(str, { cwd: assetSourcesRoot }); if (hits.length > 0) { files = hits; } else { if (!variables.includes(str)) variables.push(str); continue; } } } else { files = [str]; } for (const f of files) { const fp = path.join(assetSourcesRoot, f); if (fs.existsSync(fp)) { if (f.includes('model/chatBubble')) { console.log('kkk'); } usedBySrcMap[f.toLowerCase()] = true; } else { if (!notExists.includes(f)) notExists.push(f); } } } else if (extn == '') { // 有可能是prefab if (quoted.startsWith('Assets/')) { if (!potentialPaths.includes(quoted)) { potentialPaths.push(quoted); } } else if (quoted.length > 1 && quoted.search(/[\s=\?:]/) < 0) { if (quoted.includes('/')) { let pattern = `**/${quoted}`; if (!quoted.endsWith('.prefab')) { pattern += '.prefab'; } const mchs = await fg(pattern, { cwd: assetSourcesRoot }); for (const mch of mchs) { if (!potentialPrefabs.includes(mch)) { potentialPrefabs.push(mch); } } } else { exactlyNames.push(quoted); } } } } console.log('-----------------------------'); notExists.forEach((v) => console.log('prefab not exists:', v)); console.log('-----------------------------'); variables.forEach((v) => console.log('[Attention!] Variable prefab hit nothing:', v)); console.log('-----------------------------'); //#endregion // 收集用到的guid //#region const usedGUIDMap = await UnityHelper.collectUsedGUID(toolchain.opts.projectRoot); //#endregion // 删除没用的ui prefab,既不被代码引用,又不被其他prefab引用判定为没用 //#region const svn = new SVN(); const prefabRoots = ['Assets/AssetSources', 'Assets/Arts']; const toBeDeleteds = []; for (const pr of prefabRoots) { const prefabs = await fg('**/*.prefab', { cwd: path.join(toolchain.opts.projectRoot, pr) }); for (const prefab of prefabs) { const f = path.join(pr, prefab); if (keepMap[f]) continue; if (prefab.endsWith('chat_bag_shengdan.prefab')) { console.log('ggg'); } if (!usedBySrcMap[prefab.toLowerCase()]) { // 没在代码里引用,检查是否被其他prefab引用 const file = path.join(toolchain.opts.projectRoot, f); const guid = await UnityHelper.readGUID(file + '.meta'); if (!usedGUIDMap[guid]) { // 也没被其他prefab引用,检查是否代码里通过拼接方式使用 const pn = path.dirname(prefab), bn = path.basename(prefab, '.prefab'); if (!potentialPaths.includes(pn) && !potentialPrefabs.includes(prefab) && !exactlyNames.includes(bn)) { // 确认没用,查看提交时间,如果是7天内提交的,则保留,否则直接删除 const reserved = await svn.isModifiedRecent(file, 7); if (reserved.result) { console.log('Though not used but modified recent, reserved: ' + prefab); continue; } toBeDeleteds.push({ guid, file, date: reserved.date }); } } } } } //#endregion if (toolchain.opts.output) { // 不删除,只输出哪些文件,方便执行deleteFile命令 let content = ''; for (const d of toBeDeleteds) { content += d.file + ' ' + (d.date ?? '[no date]') + '\n'; } await fs.writeFile(toolchain.opts.output, content, 'utf-8'); console.log(`${toBeDeleteds.length} prefab found, see: ${toolchain.opts.output}`); } else { for (const d of toBeDeleteds) { await fs.unlink(d.file); await fs.unlink(d.file + '.meta'); await Record.Instance.recordGUID(d.guid, d.file); // console.log('remove prefab:', file); } console.log('deleted ui prefabs:', toBeDeleteds.length); } } getMatches(arr, keys) { const out = []; for (const item of arr) { const sitem = item[keys[0]]; let sitemArr; if (sitem instanceof Array) { sitemArr = sitem; } else { sitemArr = [sitem]; } if (keys.length > 1) { out.push(...this.getMatches(sitemArr, keys.slice(1))); } else { out.push(...sitemArr); } } return out; } } //# sourceMappingURL=UIStriper.js.map