diff --git a/ets2panda/driver/build_system/src/plugins/plugins_driver.ts b/ets2panda/driver/build_system/src/plugins/plugins_driver.ts index 4af2ec6aad84af74ab156975ba850ee4dc42de2a..0555e35c2e4560bd7f0b815608ed31ca119aa932 100644 --- a/ets2panda/driver/build_system/src/plugins/plugins_driver.ts +++ b/ets2panda/driver/build_system/src/plugins/plugins_driver.ts @@ -18,10 +18,13 @@ import { LogData, LogDataFactory } from '../logger'; -import { BuildConfig } from '../types'; +import { ApiCheckConfig, BuildConfig, ConfigPermission, SdkConfig, SyscapConfig } from '../types'; import { ErrorCode } from '../error_code'; import { FileManager } from './FileManager'; import { initKoalaPlugins } from '../init/init_koala_modules'; +import { MESSAGE_CONFIG_COLOR_RED, MESSAGE_CONFIG_COLOR_RESET, RUNTIME_OS_OH, STAGE_COMPILE_MODE } from '../pre_define'; +import fs from 'fs'; +import path from 'path' export enum PluginHook { NEW = 'afterNew', @@ -73,6 +76,7 @@ class PluginContext { private projectConfig: object | undefined; private fileManager: FileManager | undefined; private contextPtr: number | undefined; + private apiCheckConfig: BuildConfig | undefined; constructor() { this.ast = undefined; @@ -80,6 +84,7 @@ class PluginContext { this.projectConfig = undefined; this.fileManager = undefined; this.contextPtr = undefined; + this.apiCheckConfig = undefined; } public setArkTSAst(ast: object): void { @@ -124,6 +129,317 @@ class PluginContext { public getContextPtr(): number | undefined { return this.contextPtr; } + + public setApiCheckConfig(apiCheckConfig: BuildConfig): void { + this.apiCheckConfig = { + ...this.initApiCheckConfig(), + ...apiCheckConfig + }; + this.readPermissions(); + this.readCardPageSet(); + this.readSystemModules(); + this.readSyscapInfo(); + this.projectConfig = { + ...this.projectConfig, + ...this.apiCheckConfig + }; + } + + private initApiCheckConfig(): ApiCheckConfig { + return { + aceModuleJsonPath: '', + sdkConfigPaths: '', + compileMode: '', + permissions: { + requestPermissions: [], + definePermissions: [] + }, + projectPath: '', + aceProfilePath: '', + cardPageSet: [], + systemModules: [], + allModulesPaths: [], + sdkConfigs: [], + externalSdkPaths: [], + sdkConfigPrefix: '', + deviceTypes: [], + deviceTypesMessage: '', + runtimeOS: '', + syscapIntersectionSet: new Set([]), + syscapUnionSet: new Set([]), + permissionsArray: [] + }; + } + + private readPermissions(): void { + if (!this.apiCheckConfig) { + return; + } + const permissions: ConfigPermission = this.apiCheckConfig.permissions; + const requestPermissions = permissions.requestPermissions + ? this.getPermissionFromConfig(permissions.requestPermissions) + : []; + + const definePermissions = permissions.definePermissions + ? this.getPermissionFromConfig(permissions.definePermissions) + : []; + + this.apiCheckConfig.permissionsArray = [ + ...requestPermissions, + ...definePermissions + ]; + } + + private getPermissionFromConfig(array: Array<{ name: string }>): string[] { + return array.map((item: { name: string }) => { + return String(item.name); + }); + } + + private readCardPageSet(): void { + if (!this.apiCheckConfig) { + return; + } + if (this.apiCheckConfig.aceModuleJsonPath && fs.existsSync(this.apiCheckConfig.aceModuleJsonPath)) { + this.apiCheckConfig.compileMode = STAGE_COMPILE_MODE; + const moduleJson: any = JSON.parse(fs.readFileSync(this.apiCheckConfig.aceModuleJsonPath).toString()); + const extensionAbilities: any = moduleJson?.module?.extensionAbilities; + if (extensionAbilities && extensionAbilities.length > 0) { + this.setCardPages(extensionAbilities); + } + } + } + + private setCardPages(extensionAbilities: any): void { + if (extensionAbilities && extensionAbilities.length > 0) { + extensionAbilities.forEach((extensionAbility: any) => { + if (extensionAbility.type === 'form' && extensionAbility.metadata) { + extensionAbility.metadata.forEach((metadata: any) => { + if (metadata.resource) { + this.readCardResource(metadata.resource); + } + }); + } + }); + } + } + + private readCardResource(resource: string): void { + if (!this.apiCheckConfig) { + return; + } + const cardJsonFileName: string = `${resource.replace(/\$profile\:/, '')}.json`; + const modulePagePath: string = path.resolve(this.apiCheckConfig.aceProfilePath, cardJsonFileName); + if (fs.existsSync(modulePagePath)) { + const cardConfig: any = JSON.parse(fs.readFileSync(modulePagePath, 'utf-8')); + if (cardConfig.forms) { + cardConfig.forms.forEach((form: any) => { + this.readCardForm(form); + }); + } + } + } + + private readCardForm(form: any): void { + if (!this.apiCheckConfig) { + return; + } + if ((form.type && form.type === 'eTS') || (form.uiSyntax && form.uiSyntax === 'arkts')) { + const cardPath = path.resolve(this.apiCheckConfig.projectPath, '..', form.src); + if (cardPath && fs.existsSync(cardPath) && !this.apiCheckConfig.cardPageSet.includes(cardPath)) { + this.apiCheckConfig.cardPageSet.push(cardPath); + } + } + } + + private readSystemModules(): void { + if (!this.apiCheckConfig) { + return; + } + const apiDirPath = path.resolve(this.apiCheckConfig.buildSdkPath, './api'); + const arktsDirPath = path.resolve(this.apiCheckConfig.buildSdkPath, './arkts'); + const kitsDirPath = path.resolve(this.apiCheckConfig.buildSdkPath, './kits'); + const systemModulePathArray = [apiDirPath, arktsDirPath, kitsDirPath]; + + systemModulePathArray.forEach(systemModulesPath => { + if (fs.existsSync(systemModulesPath) && this.apiCheckConfig) { + const modulePaths: string[] = []; + this.readFile(systemModulesPath, modulePaths); + this.apiCheckConfig.systemModules.push(...fs.readdirSync(systemModulesPath)); + modulePaths.filter(filePath => { + const dirName = path.dirname(filePath); + return !(dirName === apiDirPath || dirName === arktsDirPath || dirName === kitsDirPath); + }).map((filePath: string) => { + return filePath + .replace(apiDirPath, '') + .replace(arktsDirPath, '') + .replace(kitsDirPath, '') + .replace(/(^\\)|(.d.e?ts$)/g, '') + .replace(/\\/g, '/'); + }); + this.apiCheckConfig.allModulesPaths.push(...modulePaths); + } + }); + const defaultSdkConfigs: SdkConfig[] = [ + { + 'apiPath': systemModulePathArray, + 'prefix': '@ohos' + }, { + 'apiPath': systemModulePathArray, + 'prefix': '@system' + }, { + 'apiPath': systemModulePathArray, + 'prefix': '@arkts' + } + ]; + const externalApiPathStr = this.apiCheckConfig.sdkConfigPaths || ''; + const externalApiPaths = externalApiPathStr.split(path.delimiter); + this.apiCheckConfig.externalSdkPaths = [...externalApiPaths]; + const extendSdkConfigs: SdkConfig[] = []; + this.collectExternalModules(externalApiPaths, extendSdkConfigs); + this.apiCheckConfig.sdkConfigs = [...defaultSdkConfigs, ...extendSdkConfigs]; + } + + private collectExternalModules(sdkPaths: string[], extendSdkConfigs: SdkConfig[]): void { + if (!this.apiCheckConfig) { + return; + } + for (let i = 0; i < sdkPaths.length; i++) { + const sdkPath = sdkPaths[i]; + const sdkConfigPath = path.resolve(sdkPath, 'sdkConfig.json'); + if (!fs.existsSync(sdkConfigPath)) { + continue; + } + const sdkConfig: SdkConfig = JSON.parse(fs.readFileSync(sdkConfigPath, 'utf-8')); + if (!sdkConfig.apiPath) { + continue; + } + let externalApiPathArray: string[] = []; + if (Array.isArray(sdkConfig.apiPath)) { + externalApiPathArray = sdkConfig.apiPath; + } else { + externalApiPathArray.push(sdkConfig.apiPath); + } + const resolveApiPathArray: string[] = []; + externalApiPathArray.forEach((element: string) => { + const resolvePath: string = path.resolve(sdkPath, element); + resolveApiPathArray.push(resolvePath); + if (fs.existsSync(resolvePath) && this.apiCheckConfig) { + const extrenalModulePaths: string[] = []; + this.apiCheckConfig.systemModules.push(...fs.readdirSync(resolvePath)); + this.readFile(resolvePath, extrenalModulePaths); + this.apiCheckConfig.allModulesPaths.push(...extrenalModulePaths); + } + }); + this.apiCheckConfig.sdkConfigPrefix += `|${sdkConfig.prefix.replace(/^@/, '')}`; + sdkConfig.apiPath = resolveApiPathArray; + extendSdkConfigs.push(sdkConfig); + } + } + + private readFile(dir: string, utFiles: string[]): void { + try { + const files: string[] = fs.readdirSync(dir); + files.forEach((element) => { + const filePath: string = path.join(dir, element); + const status: fs.Stats = fs.statSync(filePath); + if (status.isDirectory()) { + this.readFile(filePath, utFiles); + } else { + utFiles.push(filePath); + } + }); + } catch (e) { + console.error(MESSAGE_CONFIG_COLOR_RED, 'ArkTS ERROR: ' + e, MESSAGE_CONFIG_COLOR_RESET); + } + } + + private readSyscapInfo(): void { + if (!this.apiCheckConfig) { + return; + } + this.apiCheckConfig.deviceTypesMessage = this.apiCheckConfig.deviceTypes.join(','); + const deviceDir: string = path.resolve(__dirname, '../../../../../api/device-define/'); + const deviceInfoMap: Map = new Map(); + const syscaps: Array = []; + let allSyscaps: string[] = []; + this.apiCheckConfig.deviceTypes.forEach((deviceType: string) => { + this.collectOhSyscapInfos(deviceType, deviceDir, deviceInfoMap); + }); + if (this.apiCheckConfig.runtimeOS !== RUNTIME_OS_OH) { + this.collectExternalSyscapInfos(this.apiCheckConfig.externalSdkPaths, this.apiCheckConfig.deviceTypes, + deviceInfoMap); + } + deviceInfoMap.forEach((value: string[]) => { + syscaps.push(value); + allSyscaps = allSyscaps.concat(value); + }); + const intersectNoRepeatTwice = (arrs: Array) => { + return arrs.reduce(function (prev: string[], cur: string[]) { + return Array.from(new Set(cur.filter((item: string) => { + return prev.includes(item); + }))); + }); + }; + let syscapIntersection: string[] = []; + if (this.apiCheckConfig.deviceTypes.length === 1 || syscaps.length === 1) { + syscapIntersection = syscaps[0]; + } else if (syscaps.length > 1) { + syscapIntersection = intersectNoRepeatTwice(syscaps); + } + this.apiCheckConfig.syscapIntersectionSet = new Set(syscapIntersection); + this.apiCheckConfig.syscapUnionSet = new Set(allSyscaps); + } + + private collectOhSyscapInfos(deviceType: string, deviceDir: string, deviceInfoMap: Map) { + let syscapFilePath: string = ''; + if (deviceType === 'phone') { + syscapFilePath = path.resolve(deviceDir, 'default.json'); + } else { + syscapFilePath = path.resolve(deviceDir, deviceType + '.json'); + } + if (fs.existsSync(syscapFilePath)) { + const content: SyscapConfig = JSON.parse(fs.readFileSync(syscapFilePath, 'utf-8')); + if (deviceInfoMap.get(deviceType)) { + deviceInfoMap.set(deviceType, (deviceInfoMap.get(deviceType) as string[]).concat(content.SysCaps)); + } else { + deviceInfoMap.set(deviceType, content.SysCaps); + } + } + } + + private collectExternalSyscapInfos( + externalApiPaths: string[], + deviceTypes: string[], + deviceInfoMap: Map + ) { + const externalDeviceDirs: string[] = []; + externalApiPaths.forEach((externalApiPath: string) => { + const externalDeviceDir: string = path.resolve(externalApiPath, './api/device-define'); + if (fs.existsSync(externalDeviceDir)) { + externalDeviceDirs.push(externalDeviceDir); + } + }); + externalDeviceDirs.forEach((externalDeviceDir: string) => { + deviceTypes.forEach((deviceType: string) => { + let syscapFilePath: string = ''; + const files: string[] = fs.readdirSync(externalDeviceDir); + files.forEach((fileName: string) => { + if (fileName.startsWith(deviceType)) { + syscapFilePath = path.resolve(externalDeviceDir, fileName); + if (fs.existsSync(syscapFilePath)) { + const content: SyscapConfig = JSON.parse(fs.readFileSync(syscapFilePath, 'utf-8')); + if (deviceInfoMap.get(deviceType)) { + deviceInfoMap.set(deviceType, (deviceInfoMap.get(deviceType) as string[]).concat(content.SysCaps)); + } else { + deviceInfoMap.set(deviceType, content.SysCaps); + } + } + } + }); + }); + }); + } } export class PluginDriver { diff --git a/ets2panda/driver/build_system/src/pre_define.ts b/ets2panda/driver/build_system/src/pre_define.ts index a434824c5a61c1d7ed9b0320931b98245b884c97..bf2921a3df18bae31bc7d8731eb796b146ecaa96 100644 --- a/ets2panda/driver/build_system/src/pre_define.ts +++ b/ets2panda/driver/build_system/src/pre_define.ts @@ -65,3 +65,8 @@ export type Record = { export const KOALA_WRAPPER_PATH_FROM_SDK: string = process.env.USE_KOALA_LIBARKTS ? './build-tools/ui2abc/libarkts/lib/libarkts.js' : './build-tools/koala-wrapper/build/lib/es2panda' export const UI_PLUGIN_PATH_FROM_SDK: string = './build-tools/ui2abc/ui-plugin/lib/entry.js' export const MEMO_PLUGIN_PATH_FROM_SDK: string = './build-tools/ui2abc/memo-plugin/lib/entry.js' + +export const STAGE_COMPILE_MODE: string = 'moduleJson'; +export const MESSAGE_CONFIG_COLOR_RED: string = '\u001b[31m'; +export const MESSAGE_CONFIG_COLOR_RESET: string = '\u001b[39m'; +export const RUNTIME_OS_OH: string = 'OpenHarmony'; \ No newline at end of file diff --git a/ets2panda/driver/build_system/src/types.ts b/ets2panda/driver/build_system/src/types.ts index e909926c44f606d8b7099b7a573115ed98ab5713..a4a9d8a650302832bea436e9f4fa38813b885087 100644 --- a/ets2panda/driver/build_system/src/types.ts +++ b/ets2panda/driver/build_system/src/types.ts @@ -189,7 +189,7 @@ export interface DependentModuleConfig { byteCodeHar: boolean; } -export interface BuildConfig extends BuildBaseConfig, DeclgenConfig, LoggerConfig, ModuleConfig, PathConfig, FrameworkConfig { +export interface BuildConfig extends BuildBaseConfig, DeclgenConfig, LoggerConfig, ModuleConfig, PathConfig, FrameworkConfig, ApiCheckConfig { plugins: PluginsConfig; paths: PathsConfig; // paths config passed from template to generate arktsconfig.json "paths" configs. compileFiles: string[]; @@ -333,4 +333,39 @@ export interface CompilePayload { fileInfo: CompileFileInfo; buildConfig: BuildConfig; moduleInfos: [string, ModuleInfo][]; +} + +export interface ApiCheckConfig { + permissionsArray: string[]; + cardPageSet: string[]; + sdkConfigs: SdkConfig[]; + systemModules: string[]; + allModulesPaths: string[]; + externalSdkPaths: string[]; + sdkConfigPrefix: string; + sdkConfigPaths: string; + permissions: ConfigPermission; + projectPath: string; + aceModuleJsonPath: string; + compileMode: string; + aceProfilePath: string; + deviceTypes: string[]; + runtimeOS: string; + deviceTypesMessage: string; + syscapIntersectionSet: Set; + syscapUnionSet: Set; +} + +export interface SyscapConfig { + SysCaps: string[] +} + +export interface ConfigPermission { + requestPermissions: Array<{ name: string }>; + definePermissions: Array<{ name: string }>; +} + +export interface SdkConfig { + prefix: string; + apiPath: string[]; } \ No newline at end of file