mirror of
https://github.com/cocos/cocos-engine.git
synced 2026-09-07 00:02:53 +08:00
191 lines
8.6 KiB
TypeScript
191 lines
8.6 KiB
TypeScript
import { CocosParams, NativePackTool } from "../base/default";
|
|
import * as fs from 'fs-extra';
|
|
import * as ps from 'path';
|
|
import * as os from 'os';
|
|
import { execSync } from "child_process";
|
|
import { toolHelper } from "../utils";
|
|
|
|
export interface IOrientation {
|
|
landscapeLeft: boolean;
|
|
landscapeRight: boolean;
|
|
portrait: boolean;
|
|
upsideDown: boolean;
|
|
}
|
|
|
|
export interface MacOSParams {
|
|
bundleId: string;
|
|
skipUpdateXcodeProject: boolean;
|
|
}
|
|
|
|
export abstract class MacOSPackTool extends NativePackTool {
|
|
params!: CocosParams<MacOSParams>;
|
|
|
|
async create() {
|
|
await this.copyCommonTemplate();
|
|
await this.copyPlatformTemplate();
|
|
await this.generateCMakeConfig();
|
|
await this.excuteCocosTemplateTask();
|
|
return true;
|
|
}
|
|
|
|
abstract generate(): Promise<boolean>;
|
|
|
|
shouldSkipGenerate() {
|
|
const nativePrjDir = this.paths.nativePrjDir;
|
|
const options = this.params.platformParams;
|
|
if (options.skipUpdateXcodeProject && fs.existsSync(ps.join(nativePrjDir, 'CMakeCache.txt'))) {
|
|
console.log('Skip xcode project update');
|
|
return true;
|
|
}
|
|
|
|
const cmakePath = ps.join(this.paths.platformTemplateDirInPrj, 'CMakeLists.txt');
|
|
if (!fs.existsSync(cmakePath)) {
|
|
throw new Error(`CMakeLists.txt not found in ${cmakePath}`);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected isAppleSilicon(): boolean {
|
|
const cpus = os.cpus();
|
|
const model = (cpus && cpus[0] && cpus[0].model) ? cpus[0].model : '';
|
|
return /Apple/.test(model) && process.platform === 'darwin';
|
|
}
|
|
|
|
protected getXcodeMajorVerion(): number {
|
|
try {
|
|
const output = execSync('xcrun xcodebuild -version').toString('utf8');
|
|
return Number.parseInt(output.match(/Xcode\s(\d+)\.\d+/)![1]);
|
|
} catch (e) {
|
|
console.error(e);
|
|
// fallback to default Xcode version
|
|
return 11;
|
|
}
|
|
}
|
|
|
|
async modifyXcodeProject() {
|
|
if (this.params.platformParams.skipUpdateXcodeProject) {
|
|
if (this.getXcodeMajorVerion() < 12) {
|
|
console.error(`SKIP UPDATE XCODE PROJECT is only supported with Xcode 12 or later`);
|
|
return;
|
|
}
|
|
await this.xcodeFixAssetsReferences();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* When "Skip Xcode Project Update" is checked, changes to the contents of the "data" directory
|
|
* still need to be synchronized with Xcode. One way to achieve this is to modify the Xcode
|
|
* project file directly and use directory references to access the "data" directory.
|
|
* However, this method is not supported in Xcode 11 and earlier project formats due to
|
|
* differences in their formats.
|
|
*/
|
|
async xcodeFixAssetsReferences() {
|
|
const nativePrjDir = this.paths.nativePrjDir;
|
|
const xcode = require(ps.join(this.params.enginePath, 'scripts/native-pack-tool/xcode'));
|
|
const projs = fs.readdirSync(nativePrjDir).filter((x) => x.endsWith('.xcodeproj')).map((x) => ps.join(nativePrjDir, x));
|
|
if (projs.length === 0) {
|
|
throw new Error(`can not find xcode project file in ${nativePrjDir}`);
|
|
} else {
|
|
try {
|
|
for (const proj of projs) {
|
|
const pbxfile = ps.join(proj, 'project.pbxproj');
|
|
console.log(`parsing pbxfile ${pbxfile}`);
|
|
const projectFile = xcode.project(pbxfile);
|
|
await (function () {
|
|
return new Promise((resolve, reject) => {
|
|
projectFile.parse((err: Error) => {
|
|
if (err) {
|
|
return reject(err);
|
|
}
|
|
resolve(projectFile);
|
|
});
|
|
});
|
|
})();
|
|
console.log(` modifiy Xcode project file ${pbxfile}`);
|
|
{
|
|
// Resources/ add references to files/folders in assets/
|
|
const assetsDir = this.paths.buildDir;
|
|
const objects = projectFile.hash.project.objects;
|
|
const KeyResource = `Resources`;
|
|
type ResourceItem = [string, { children: { value: string, comment: string }[] }];
|
|
const resources: ResourceItem[] = Object.entries(objects.PBXGroup).filter(([, x]) => (x as any).name === KeyResource) as any;
|
|
let hash: string = resources[0][0];
|
|
if (resources.length > 1) {
|
|
console.log(` multiple Resources/ group found!`);
|
|
const itemWeight = (a: ResourceItem): number => {
|
|
const hasImageAsset = a[1].children.filter((c) => c.comment.endsWith('.xcassets')).length > 0;
|
|
const finalBuildTarget = a[1].children.filter((c) => c.comment.indexOf(`CMakeFiles/${this.params.projectName}`) > -1).length > 0;
|
|
console.log(` ${a[0]} hasImageAsset ${hasImageAsset}, is final target ${finalBuildTarget}`);
|
|
return (finalBuildTarget ? 1 : 0) * 100 + (hasImageAsset ? 1 : 0) * 10 + a[1].children.length;
|
|
};
|
|
hash = resources.sort((a, b) => itemWeight(b) - itemWeight(a))[0][0];
|
|
console.log(` select ${hash}`);
|
|
}
|
|
|
|
const filterFolders = (name: string): boolean => {
|
|
// NOTE: `assets/remote` should not be linked into Resources/
|
|
// return name !== '.' && name !== '..' && name !== 'remote';
|
|
return name === 'data'; // only accept `data` folder
|
|
};
|
|
fs.readdirSync(assetsDir, { encoding: 'utf8' }).filter(filterFolders).forEach(f => {
|
|
const full = ps.normalize(ps.join(assetsDir, f));
|
|
const options: any = {};
|
|
const st = fs.statSync(full);
|
|
if (st.isDirectory()) {
|
|
options.lastKnownFileType = 'folder';
|
|
}
|
|
// add file ref
|
|
const newResFile = projectFile.addFile(full, hash, options);
|
|
{
|
|
// add file to build file
|
|
const newBuildFile = {
|
|
fileRef: newResFile.fileRef,
|
|
uuid: projectFile.generateUuid(),
|
|
isa: 'PBXBuildFile',
|
|
basename: `${f}`,
|
|
group: KeyResource,
|
|
};
|
|
projectFile.addToPbxBuildFileSection(newBuildFile);
|
|
// add file to ResourceBuildPhase of `Resources`
|
|
const [phaseId] = Object.entries(objects.PBXResourcesBuildPhase).find(([k, x]) => {
|
|
return k.endsWith('_comment') && x === KeyResource;
|
|
}) as any;
|
|
const id = phaseId.split('_comment')[0];
|
|
objects["PBXResourcesBuildPhase"][id].files.push({
|
|
value: newBuildFile.uuid,
|
|
comment: full,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
fs.writeFileSync(pbxfile, projectFile.writeSync());
|
|
console.log(` replace pbxfile: ${pbxfile}.`);
|
|
}
|
|
} catch (e) {
|
|
console.error(`disable ZERO_CHECK, failed to update xcode.`);
|
|
console.error(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
async checkIfXcodeInstalled() {
|
|
let xcodeFound = false;
|
|
const xcodeInstalled = await toolHelper.runCommand('xcode-select', ['-p'], (code, stdout, stderr) => {
|
|
if (code === 0) {
|
|
console.log(`[xcode-select] ${stdout}`);
|
|
if (stdout.indexOf('Xcode.app') > 0) {
|
|
xcodeFound = true;
|
|
}
|
|
} else {
|
|
console.log(`[xcode-select] ${stdout}`);
|
|
console.error(`[xcode-select] ${stderr}`);
|
|
}
|
|
});
|
|
if (!xcodeInstalled) {
|
|
toolHelper.runCommand('xcode-select', ['--install']);
|
|
return false;
|
|
}
|
|
return xcodeFound;
|
|
}
|
|
}
|