unity-find-fault
Version:
A tool to find fault in unity project.
250 lines • 11.3 kB
JavaScript
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