1/* 2 * Copyright (c) 2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { FileManager } from './interop_manager'; 17import { ResolveModuleInfo, getResolveModule, readDeaclareFiles } from '../../../ets_checker'; 18import { 19 mkdirsSync, 20 readFile, 21 toUnixPath 22} from '../../../utils'; 23import { 24 ArkTSEvolutionModule, 25 BuildType, 26 DeclFilesConfig, 27 DECLGEN_CACHE_FILE, 28 Params, 29 ProjectConfig, 30 RunnerParms 31} from './type'; 32import fs from 'fs'; 33import path from 'path'; 34import * as ts from 'typescript'; 35import { EXTNAME_D_ETS, EXTNAME_JS } from '../common/ark_define'; 36import { getRealModulePath } from '../../system_api/api_check_utils'; 37import { generateInteropDecls } from 'declgen/build/src/generateInteropDecls'; 38import { calculateFileHash } from '../utils'; 39import { processInteropUI } from '../../../process_interop_ui'; 40 41export function run(param: Params): boolean { 42 FileManager.init(param.dependentModuleMap); 43 DeclfileProductor.init(param); 44 param.tasks.forEach(task => { 45 const moduleInfo = FileManager.arkTSModuleMap.get(task.packageName); 46 if (moduleInfo?.dynamicFiles.length <= 0) { 47 return; 48 } 49 if (task.buildTask === BuildType.DECLGEN) { 50 DeclfileProductor.getInstance().runDeclgen(moduleInfo); 51 } else if (task.buildTask === BuildType.INTEROP_CONTEXT) { 52 DeclfileProductor.getInstance().writeDeclFileInfo(moduleInfo, task.mainModuleName); 53 } else if (task.buildTask === BuildType.BYTE_CODE_HAR) { 54 //todo 55 } 56 }); 57 FileManager.cleanFileManagerObject(); 58 return true; 59} 60 61class DeclfileProductor { 62 private static declFileProductor: DeclfileProductor; 63 64 static compilerOptions: ts.CompilerOptions; 65 static sdkConfigPrefix = 'ohos|system|kit|arkts'; 66 static sdkConfigs = []; 67 static systemModules = []; 68 static defaultSdkConfigs = []; 69 static projectPath; 70 private projectConfig; 71 private pkgDeclFilesConfig: { [pkgName: string]: DeclFilesConfig } = {}; 72 73 static init(param: Params): void { 74 DeclfileProductor.declFileProductor = new DeclfileProductor(param); 75 DeclfileProductor.compilerOptions = ts.readConfigFile( 76 path.join(__dirname, '../../../../tsconfig.json'), ts.sys.readFile).config.compilerOptions; 77 DeclfileProductor.initSdkConfig(); 78 Object.assign(DeclfileProductor.compilerOptions, { 79 emitNodeModulesFiles: true, 80 importsNotUsedAsValues: ts.ImportsNotUsedAsValues.Preserve, 81 module: ts.ModuleKind.CommonJS, 82 moduleResolution: ts.ModuleResolutionKind.NodeJs, 83 noEmit: true, 84 packageManagerType: 'ohpm', 85 allowJs: true, 86 allowSyntheticDefaultImports: true, 87 esModuleInterop: true, 88 noImplicitAny: false, 89 noUnusedLocals: false, 90 noUnusedParameters: false, 91 experimentalDecorators: true, 92 resolveJsonModule: true, 93 skipLibCheck: false, 94 sourceMap: true, 95 target: 8, 96 types: [], 97 typeRoots: [], 98 lib: ['lib.es2021.d.ts'], 99 alwaysStrict: true, 100 checkJs: false, 101 maxFlowDepth: 2000, 102 etsAnnotationsEnable: false, 103 etsLoaderPath: path.join(__dirname, '../../../'), 104 needDoArkTsLinter: true, 105 isCompatibleVersion: false, 106 skipTscOhModuleCheck: false, 107 skipArkTSStaticBlocksCheck: false, 108 incremental: true, 109 tsImportSendableEnable: false, 110 skipPathsInKeyForCompilationSettings: true, 111 }); 112 DeclfileProductor.projectPath = param.projectConfig.projectRootPath; 113 } 114 static getInstance(param?: Params): DeclfileProductor { 115 if (!this.declFileProductor) { 116 this.declFileProductor = new DeclfileProductor(param); 117 } 118 return this.declFileProductor; 119 } 120 121 private constructor(param: Params) { 122 this.projectConfig = param.projectConfig as ProjectConfig; 123 } 124 125 runDeclgen(moduleInfo: ArkTSEvolutionModule): void { 126 const cachePath = `${moduleInfo.declgenV2OutPath}/.${DECLGEN_CACHE_FILE}`; 127 let existingCache = {}; 128 const filesToProcess = []; 129 130 if (fs.existsSync(cachePath)) { 131 existingCache = JSON.parse(fs.readFileSync(cachePath, 'utf-8')); 132 } 133 134 let inputList = []; 135 let hashMap = {}; 136 moduleInfo.dynamicFiles.forEach(path => { 137 let unixPath = toUnixPath(path); 138 const fileHash = calculateFileHash(path); 139 if (!existingCache[unixPath] || existingCache[unixPath] !== fileHash) { 140 filesToProcess.push(unixPath); 141 hashMap[unixPath] = fileHash; 142 } 143 }); 144 if (filesToProcess.length === 0) { 145 return; 146 } 147 readDeaclareFiles().forEach(path => { 148 filesToProcess.push(toUnixPath(path)); 149 }); 150 151 inputList = inputList.filter(filePath => !filePath.endsWith('.js')); 152 const config: RunnerParms = { 153 inputDirs: [], 154 inputFiles: filesToProcess, 155 outDir: moduleInfo.declgenV2OutPath, 156 // use package name as folder name 157 rootDir: moduleInfo.modulePath, 158 customResolveModuleNames: resolveModuleNames, 159 customCompilerOptions: DeclfileProductor.compilerOptions, 160 includePaths: [moduleInfo.modulePath] 161 }; 162 if (!fs.existsSync(config.outDir)) { 163 fs.mkdirSync(config.outDir, { recursive: true }); 164 } 165 fs.mkdirSync(config.outDir, { recursive: true }); 166 generateInteropDecls(config); 167 processInteropUI(FileManager.arkTSModuleMap.get(moduleInfo.packageName)?.declgenV2OutPath); 168 const newCache = { 169 ...existingCache, 170 ...hashMap 171 }; 172 fs.writeFileSync(cachePath, JSON.stringify(newCache, null, 2)); 173 } 174 175 writeDeclFileInfo(moduleInfo: ArkTSEvolutionModule, mainModuleName: string): void { 176 moduleInfo.isNative = moduleInfo.isNative ?? moduleInfo.packageName.endsWith('.so'); 177 moduleInfo.dynamicFiles.forEach(file => { 178 this.addDeclFilesConfig(file, mainModuleName, this.projectConfig.bundleName, moduleInfo); 179 }); 180 181 const declFilesConfigFile: string = toUnixPath(moduleInfo.declFilesPath); 182 mkdirsSync(path.dirname(declFilesConfigFile)); 183 if (this.pkgDeclFilesConfig[moduleInfo.packageName]) { 184 fs.writeFileSync(declFilesConfigFile, JSON.stringify(this.pkgDeclFilesConfig[moduleInfo.packageName], null, 2), 'utf-8'); 185 } 186 } 187 188 addDeclFilesConfig(filePath: string, mainModuleName: string, bundleName: string, moduleInfo: ArkTSEvolutionModule): void { 189 const projectFilePath = getRelativePath(filePath, moduleInfo.modulePath); 190 191 const declgenV2OutPath: string = this.getDeclgenV2OutPath(moduleInfo.packageName); 192 if (!declgenV2OutPath) { 193 return; 194 } 195 if (!this.pkgDeclFilesConfig[moduleInfo.packageName]) { 196 this.pkgDeclFilesConfig[moduleInfo.packageName] = { packageName: moduleInfo.packageName, files: {} }; 197 } 198 if (filePath.endsWith(EXTNAME_JS)) { 199 return; 200 } 201 if (this.pkgDeclFilesConfig[moduleInfo.packageName].files[projectFilePath]) { 202 return; 203 } 204 // The module name of the entry module of the project during the current compilation process. 205 const normalizedFilePath: string = moduleInfo.isNative 206 ? moduleInfo.moduleName 207 : `${moduleInfo.packageName}/${projectFilePath}`; 208 const declPath: string = path.join(toUnixPath(declgenV2OutPath), projectFilePath) + EXTNAME_D_ETS; 209 const isNativeFlag = moduleInfo.isNative ? 'Y' : 'N'; 210 const ohmUrl: string = `${isNativeFlag}&${mainModuleName}&${bundleName}&${normalizedFilePath}&${moduleInfo.packageVersion}`; 211 this.pkgDeclFilesConfig[moduleInfo.packageName].files[projectFilePath] = { declPath, ohmUrl: `@normalized:${ohmUrl}` }; 212 } 213 214 getDeclgenV2OutPath(pkgName: string): string { 215 if (FileManager.arkTSModuleMap.size && FileManager.arkTSModuleMap.get(pkgName)) { 216 const arkTsModuleInfo: ArkTSEvolutionModule = FileManager.arkTSModuleMap.get(pkgName); 217 return arkTsModuleInfo.declgenV2OutPath; 218 } 219 return ''; 220 } 221 222 static initSdkConfig(): void { 223 const apiDirPath = path.resolve(__dirname, '../../../../../api'); 224 const arktsDirPath = path.resolve(__dirname, '../../../../../arkts'); 225 const kitsDirPath = path.resolve(__dirname, '../../../../../kits'); 226 const systemModulePathArray = [apiDirPath]; 227 if (!process.env.isFaMode) { 228 systemModulePathArray.push(arktsDirPath, kitsDirPath); 229 } 230 systemModulePathArray.forEach(systemModulesPath => { 231 if (fs.existsSync(systemModulesPath)) { 232 const modulePaths = []; 233 readFile(systemModulesPath, modulePaths); 234 DeclfileProductor.systemModules.push(...fs.readdirSync(systemModulesPath)); 235 const moduleSubdir = modulePaths.filter(filePath => { 236 const dirName = path.dirname(filePath); 237 return !(dirName === apiDirPath || dirName === arktsDirPath || dirName === kitsDirPath); 238 }).map(filePath => { 239 return filePath 240 .replace(apiDirPath, '') 241 .replace(arktsDirPath, '') 242 .replace(kitsDirPath, '') 243 .replace(/(^\\)|(.d.e?ts$)/g, '') 244 .replace(/\\/g, '/'); 245 }); 246 } 247 }); 248 DeclfileProductor.defaultSdkConfigs = [ 249 { 250 'apiPath': systemModulePathArray, 251 'prefix': '@ohos' 252 }, { 253 'apiPath': systemModulePathArray, 254 'prefix': '@system' 255 }, { 256 'apiPath': systemModulePathArray, 257 'prefix': '@arkts' 258 } 259 ]; 260 DeclfileProductor.sdkConfigs = [...DeclfileProductor.defaultSdkConfigs]; 261 } 262} 263 264function resolveModuleNames(moduleNames: string[], containingFile: string): ts.ResolvedModuleFull[] { 265 const resolvedModules: ts.ResolvedModuleFull[] = []; 266 267 for (const moduleName of moduleNames) { 268 let resolvedModule: ts.ResolvedModuleFull | null = null; 269 270 resolvedModule = resolveWithDefault(moduleName, containingFile); 271 if (resolvedModule) { 272 resolvedModules.push(resolvedModule); 273 continue; 274 } 275 276 resolvedModule = resolveSdkModule(moduleName); 277 if (resolvedModule) { 278 resolvedModules.push(resolvedModule); 279 continue; 280 } 281 282 resolvedModule = resolveEtsModule(moduleName, containingFile); 283 if (resolvedModule) { 284 resolvedModules.push(resolvedModule); 285 continue; 286 } 287 288 resolvedModule = resolveTsModule(moduleName, containingFile); 289 if (resolvedModule) { 290 resolvedModules.push(resolvedModule); 291 continue; 292 } 293 294 resolvedModule = resolveOtherModule(moduleName, containingFile); 295 resolvedModules.push(resolvedModule ?? null); 296 } 297 298 return resolvedModules; 299} 300 301function resolveWithDefault( 302 moduleName: string, 303 containingFile: string 304): ts.ResolvedModuleFull | null { 305 const result = ts.resolveModuleName(moduleName, containingFile, DeclfileProductor.compilerOptions, moduleResolutionHost); 306 if (!result.resolvedModule) { 307 return null; 308 } 309 310 const resolvedFileName = result.resolvedModule.resolvedFileName; 311 if (resolvedFileName && path.extname(resolvedFileName) === EXTNAME_JS) { 312 const resultDETSPath = resolvedFileName.replace(EXTNAME_JS, EXTNAME_D_ETS); 313 if (ts.sys.fileExists(resultDETSPath)) { 314 return getResolveModule(resultDETSPath, EXTNAME_D_ETS); 315 } 316 } 317 318 return result.resolvedModule; 319} 320 321function resolveEtsModule(moduleName: string, containingFile: string): ts.ResolvedModuleFull | null { 322 if (!/\.ets$/.test(moduleName) || /\.d\.ets$/.test(moduleName)) { 323 return null; 324 } 325 326 const modulePath = path.resolve(path.dirname(containingFile), moduleName); 327 return ts.sys.fileExists(modulePath) ? getResolveModule(modulePath, '.ets') : null; 328} 329 330function resolveSdkModule(moduleName: string): ts.ResolvedModuleFull | null { 331 const prefixRegex = new RegExp(`^@(${DeclfileProductor.sdkConfigPrefix})\\.`, 'i'); 332 if (!prefixRegex.test(moduleName.trim())) { 333 return null; 334 } 335 336 for (const sdkConfig of DeclfileProductor.sdkConfigs) { 337 const resolveModuleInfo: ResolveModuleInfo = getRealModulePath(sdkConfig.apiPath, moduleName, ['.d.ts', '.d.ets']); 338 const modulePath: string = resolveModuleInfo.modulePath; 339 const isDETS: boolean = resolveModuleInfo.isEts; 340 341 const moduleKey = moduleName + (isDETS ? '.d.ets' : '.d.ts'); 342 if (DeclfileProductor.systemModules.includes(moduleKey) && ts.sys.fileExists(modulePath)) { 343 return getResolveModule(modulePath, isDETS ? '.d.ets' : '.d.ts'); 344 } 345 } 346 347 return null; 348} 349 350function resolveTsModule(moduleName: string, containingFile: string): ts.ResolvedModuleFull | null { 351 if (!/\.ts$/.test(moduleName)) { 352 return null; 353 } 354 355 356 const modulePath = path.resolve(path.dirname(containingFile), moduleName); 357 return ts.sys.fileExists(modulePath) ? getResolveModule(modulePath, '.ts') : null; 358} 359 360function resolveOtherModule(moduleName: string, containingFile: string): ts.ResolvedModuleFull | null { 361 const apiModulePath = path.resolve(__dirname, '../../../api', moduleName + '.d.ts'); 362 const systemDETSModulePath = path.resolve(__dirname, '../../../api', moduleName + '.d.ets'); 363 const kitModulePath = path.resolve(__dirname, '../../../kits', moduleName + '.d.ts'); 364 const kitSystemDETSModulePath = path.resolve(__dirname, '../../../kits', moduleName + '.d.ets'); 365 const jsModulePath = path.resolve(__dirname, '../node_modules', moduleName + (moduleName.endsWith('.js') ? '' : '.js')); 366 const fileModulePath = path.resolve(__dirname, '../node_modules', moduleName + '/index.js'); 367 const DETSModulePath = path.resolve(path.dirname(containingFile), 368 moduleName.endsWith('.d.ets') ? moduleName : moduleName + EXTNAME_D_ETS); 369 370 if (ts.sys.fileExists(apiModulePath)) { 371 return getResolveModule(apiModulePath, '.d.ts'); 372 } else if (ts.sys.fileExists(systemDETSModulePath)) { 373 return getResolveModule(systemDETSModulePath, '.d.ets'); 374 } else if (ts.sys.fileExists(kitModulePath)) { 375 return getResolveModule(kitModulePath, '.d.ts'); 376 } else if (ts.sys.fileExists(kitSystemDETSModulePath)) { 377 return getResolveModule(kitSystemDETSModulePath, '.d.ets'); 378 } else if (ts.sys.fileExists(jsModulePath)) { 379 return getResolveModule(jsModulePath, '.js'); 380 } else if (ts.sys.fileExists(fileModulePath)) { 381 return getResolveModule(fileModulePath, '.js'); 382 } else if (ts.sys.fileExists(DETSModulePath)) { 383 return getResolveModule(DETSModulePath, '.d.ets'); 384 } else { 385 const srcIndex = DeclfileProductor.projectPath.indexOf('src' + path.sep + 'main'); 386 if (srcIndex > 0) { 387 const DETSModulePathFromModule = path.resolve( 388 DeclfileProductor.projectPath.substring(0, srcIndex), 389 moduleName + path.sep + 'index' + EXTNAME_D_ETS 390 ); 391 if (ts.sys.fileExists(DETSModulePathFromModule)) { 392 return getResolveModule(DETSModulePathFromModule, '.d.ets'); 393 } 394 } 395 return null; 396 } 397} 398 399function getRelativePath(filePath: string, pkgPath: string): string { 400 // rollup uses commonjs plugin to handle commonjs files, 401 // the commonjs files are prefixed with '\x00' and need to be removed. 402 if (filePath.startsWith('\x00')) { 403 filePath = filePath.replace('\x00', ''); 404 } 405 let unixFilePath: string = toUnixPath(filePath); 406 407 // Handle .d.ets and .d.ts extensions 408 const dEtsIndex = unixFilePath.lastIndexOf('.d.ets'); 409 const dTsIndex = unixFilePath.lastIndexOf('.d.ts'); 410 411 if (dEtsIndex !== -1) { 412 unixFilePath = unixFilePath.substring(0, dEtsIndex); 413 } else if (dTsIndex !== -1) { 414 unixFilePath = unixFilePath.substring(0, dTsIndex); 415 } else { 416 // Fallback to regular extension removal if not a .d file 417 const lastDotIndex = unixFilePath.lastIndexOf('.'); 418 if (lastDotIndex !== -1) { 419 unixFilePath = unixFilePath.substring(0, lastDotIndex); 420 } 421 } 422 423 const projectFilePath: string = unixFilePath.replace(toUnixPath(pkgPath) + '/', ''); 424 return projectFilePath; 425} 426 427const moduleResolutionHost: ts.ModuleResolutionHost = { 428 fileExists: (fileName: string): boolean => { 429 let exists = ts.sys.fileExists(fileName); 430 if (exists === undefined) { 431 exists = ts.sys.fileExists(fileName); 432 } 433 return exists; 434 }, 435 436 readFile(fileName: string): string | undefined { 437 return ts.sys.readFile(fileName); 438 }, 439 realpath(path: string): string { 440 return ts.sys.realpath(path); 441 }, 442 trace(s: string): void { 443 console.info(s); 444 } 445};