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 fs from 'fs'; 17import path from 'path'; 18 19import { 20 projectConfig, 21 sdkConfigs 22} from '../../../../main'; 23import { toUnixPath } from '../../../utils'; 24import { 25 ArkTSEvolutionModule, 26 FileInfo, 27 AliasConfig 28} from './type'; 29import { 30 hasExistingPaths, 31 isSubPathOf 32} from '../utils'; 33import { 34 CommonLogger, 35 LogData, 36 LogDataFactory 37} from '../logger'; 38import { 39 ArkTSErrorDescription, 40 ArkTSInternalErrorDescription, 41 ErrorCode 42} from '../error_code'; 43import { EXTNAME_TS } from '../common/ark_define'; 44import { 45 ARKTS_1_1, 46 ARKTS_1_2, 47 ARKTS_HYBRID 48} from './pre_define'; 49 50export let entryFileLanguageInfo = new Map(); 51 52export class FileManager { 53 private static instance: FileManager | undefined = undefined; 54 55 static arkTSModuleMap: Map<string, ArkTSEvolutionModule> = new Map(); 56 static aliasConfig: Map<string, Map<string, AliasConfig>> = new Map(); 57 static dynamicLibPath: Set<string> = new Set(); 58 static staticSDKDeclPath: Set<string> = new Set(); 59 static staticSDKGlueCodePath: Set<string> = new Set(); 60 static mixCompile: boolean = false; 61 static glueCodeFileInfos: Map<string, FileInfo> = new Map(); 62 static isInteropSDKEnabled: boolean = false; 63 static sharedObj: Object | undefined = undefined; 64 65 private constructor() { } 66 67 public static init( 68 dependentModuleMap: Map<string, ArkTSEvolutionModule>, 69 aliasPaths?: Map<string, string>, 70 dynamicSDKPath?: Set<string>, 71 staticSDKDeclPath?: Set<string>, 72 staticSDKGlueCodePath?: Set<string> 73 ): void { 74 if (FileManager.instance === undefined) { 75 FileManager.instance = new FileManager(); 76 FileManager.initLanguageVersionFromDependentModuleMap(dependentModuleMap); 77 FileManager.initAliasConfig(aliasPaths); 78 FileManager.initSDK(dynamicSDKPath, staticSDKDeclPath, staticSDKGlueCodePath); 79 } 80 } 81 82 public static initForTest( 83 dependentModuleMap: Map<string, ArkTSEvolutionModule>, 84 aliasPaths: Map<string, string>, 85 dynamicSDKPath?: Set<string>, 86 staticSDKDeclPath?: Set<string>, 87 staticSDKGlueCodePath?: Set<string> 88 ): void { 89 if (FileManager.instance === undefined) { 90 FileManager.instance = new FileManager(); 91 FileManager.initLanguageVersionFromDependentModuleMap(dependentModuleMap); 92 FileManager.initAliasConfig(aliasPaths); 93 FileManager.initSDK(dynamicSDKPath, staticSDKDeclPath, staticSDKGlueCodePath, false); 94 } 95 } 96 97 public static getInstance(): FileManager { 98 if (!FileManager.instance) { 99 FileManager.instance = new FileManager(); 100 } 101 return FileManager.instance; 102 } 103 104 public static setRollUpObj(shared: Object): void { 105 FileManager.sharedObj = shared; 106 } 107 108 public static setMixCompile(mixCompile: boolean): void { 109 FileManager.mixCompile = mixCompile; 110 } 111 112 private static initLanguageVersionFromDependentModuleMap( 113 dependentModuleMap: Map<string, ArkTSEvolutionModule> 114 ): void { 115 const convertedMap = new Map<string, ArkTSEvolutionModule>(); 116 117 for (const [key, module] of dependentModuleMap) { 118 module.dynamicFiles = module.dynamicFiles?.map(toUnixPath); 119 module.staticFiles = module.staticFiles?.map(toUnixPath); 120 const convertedModule: ArkTSEvolutionModule = { 121 ...module, 122 modulePath: toUnixPath(module.modulePath), 123 declgenV1OutPath: module.declgenV1OutPath ? toUnixPath(module.declgenV1OutPath) : undefined, 124 declgenV2OutPath: module.declgenV2OutPath ? toUnixPath(module.declgenV2OutPath) : undefined, 125 declgenBridgeCodePath: module.declgenBridgeCodePath ? toUnixPath(module.declgenBridgeCodePath) : undefined, 126 declFilesPath: module.declFilesPath ? toUnixPath(module.declFilesPath) : undefined, 127 }; 128 convertedMap.set(key, convertedModule); 129 } 130 131 this.arkTSModuleMap = convertedMap; 132 } 133 134 private static initAliasConfig(aliasPaths: Map<string, string>): void { 135 if (!aliasPaths) { 136 return; 137 } 138 139 for (const [pkgName, filePath] of aliasPaths) { 140 const rawContent = fs.readFileSync(filePath, 'utf-8'); 141 const jsonData = JSON.parse(rawContent); 142 const pkgAliasMap = this.parseAliasJson(pkgName, jsonData); 143 this.aliasConfig.set(pkgName, pkgAliasMap); 144 } 145 } 146 147 private static parseAliasJson(pkgName: string, jsonData: Object): Map<string, AliasConfig> { 148 const map = new Map<string, AliasConfig>(); 149 150 for (const [aliasKey, config] of Object.entries(jsonData)) { 151 if (!this.isValidAliasConfig(config)) { 152 const errInfo: LogData = LogDataFactory.newInstance( 153 ErrorCode.ETS2BUNDLE_EXTERNAL_ALIAS_CONFIG_FORMAT_INVALID, 154 ArkTSErrorDescription, 155 'Invalid alias config format detected.', 156 `Package: ${pkgName}`, 157 ['Please ensure each alias entry contains "originalAPIName" and "isStatic" fields.'] 158 ); 159 160 FileManager.logError(errInfo); 161 } 162 163 map.set(aliasKey, { 164 originalAPIName: config.originalAPIName, 165 isStatic: config.isStatic 166 }); 167 } 168 169 return map; 170 } 171 172 private static isValidAliasConfig(config: Object): config is AliasConfig { 173 return typeof config === 'object' && 174 config !== null && 175 'originalAPIName' in config && 176 'isStatic' in config; 177 } 178 179 private static initSDK( 180 dynamicSDKPath?: Set<string>, 181 staticSDKBaseUrl?: Set<string>, 182 staticSDKGlueCodePaths?: Set<string>, 183 checkFileExist: boolean = true 184 ): void { 185 if (dynamicSDKPath) { 186 for (const path of dynamicSDKPath) { 187 FileManager.dynamicLibPath.add(toUnixPath(path)); 188 } 189 } 190 const isStaticBaseValid = !staticSDKBaseUrl || hasExistingPaths(staticSDKBaseUrl); 191 const isGlueCodeValid = !staticSDKGlueCodePaths || hasExistingPaths(staticSDKGlueCodePaths); 192 FileManager.isInteropSDKEnabled = isStaticBaseValid && isGlueCodeValid; 193 if (!FileManager.isInteropSDKEnabled && checkFileExist) { 194 return; 195 } 196 if (staticSDKBaseUrl) { 197 for (const path of staticSDKBaseUrl) { 198 FileManager.staticSDKDeclPath.add(toUnixPath(path)); 199 } 200 } 201 if (staticSDKGlueCodePaths) { 202 for (const path of staticSDKGlueCodePaths) { 203 FileManager.staticSDKGlueCodePath.add(toUnixPath(path)); 204 } 205 } 206 } 207 208 public static cleanFileManagerObject(): void { 209 if (this.instance) { 210 this.instance = undefined; 211 } 212 213 FileManager.arkTSModuleMap?.clear(); 214 FileManager.dynamicLibPath?.clear(); 215 FileManager.staticSDKDeclPath?.clear(); 216 FileManager.staticSDKGlueCodePath?.clear(); 217 FileManager.glueCodeFileInfos?.clear(); 218 FileManager.aliasConfig?.clear(); 219 FileManager.mixCompile = false; 220 entryFileLanguageInfo.clear(); 221 } 222 223 getLanguageVersionByFilePath(filePath: string): { 224 languageVersion: string, 225 pkgName: string 226 } | undefined { 227 const path = toUnixPath(filePath); 228 229 const moduleMatch = FileManager.matchModulePath(path); 230 if (moduleMatch) { 231 return moduleMatch; 232 } 233 234 const sdkMatch = FileManager.matchSDKPath(path); 235 if (sdkMatch) { 236 return sdkMatch; 237 } 238 const firstLine = readFirstLineSync(filePath); 239 if (firstLine.includes('use static')) { 240 return { 241 languageVersion: ARKTS_1_2, 242 pkgName: '' 243 }; 244 } 245 return { 246 languageVersion: ARKTS_1_1, 247 pkgName: '' 248 }; 249 } 250 251 private static matchModulePath(path: string): { 252 languageVersion: string, 253 pkgName: string 254 } | undefined { 255 let matchedModuleInfo: ArkTSEvolutionModule; 256 257 for (const [, moduleInfo] of FileManager.arkTSModuleMap) { 258 if (isSubPathOf(path, moduleInfo.modulePath)) { 259 matchedModuleInfo = moduleInfo; 260 break; 261 } 262 } 263 264 if (!matchedModuleInfo) { 265 return undefined; 266 } 267 268 const isHybrid = matchedModuleInfo.language === ARKTS_HYBRID; 269 const pkgName = matchedModuleInfo.packageName; 270 271 if (!isHybrid) { 272 return { 273 languageVersion: matchedModuleInfo.language, 274 pkgName 275 }; 276 } 277 278 const isDynamic = 279 matchedModuleInfo.dynamicFiles.includes(path) || 280 (matchedModuleInfo.declgenV2OutPath && isSubPathOf(path, matchedModuleInfo.declgenV2OutPath)); 281 282 if (isDynamic) { 283 return { 284 languageVersion: ARKTS_1_1, 285 pkgName 286 }; 287 } 288 289 const isStatic = 290 matchedModuleInfo.staticFiles.includes(path) || 291 (matchedModuleInfo.declgenV1OutPath && isSubPathOf(path, matchedModuleInfo.declgenV1OutPath)) || 292 (matchedModuleInfo.declgenBridgeCodePath && isSubPathOf(path, matchedModuleInfo.declgenBridgeCodePath)); 293 294 if (isStatic) { 295 return { 296 languageVersion: ARKTS_1_2, 297 pkgName 298 }; 299 } 300 301 return undefined; 302 } 303 304 private static logError(error: LogData): void { 305 if (FileManager.sharedObj) { 306 CommonLogger.getInstance(FileManager.sharedObj).printErrorAndExit(error); 307 } else { 308 console.error(error.toString()); 309 } 310 } 311 312 private static matchSDKPath(path: string): { 313 languageVersion: string, 314 pkgName: string 315 } | undefined { 316 const sdkMatches: [Set<string> | undefined, string][] = [ 317 [FileManager.dynamicLibPath, ARKTS_1_1], 318 [FileManager.staticSDKDeclPath, ARKTS_1_2], 319 [FileManager.staticSDKGlueCodePath, ARKTS_1_2], 320 ]; 321 322 for (const [paths, version] of sdkMatches) { 323 const isMatch = paths && Array.from(paths).some( 324 p => p && (isSubPathOf(path, p)) 325 ); 326 if (isMatch) { 327 return { languageVersion: version, pkgName: 'SDK' }; 328 } 329 } 330 return undefined; 331 } 332 333 queryOriginApiName(moduleName: string, containingFile: string): AliasConfig { 334 if (!FileManager.mixCompile) { 335 return undefined; 336 } 337 if (!FileManager.isInteropSDKEnabled) { 338 return undefined; 339 } 340 const result = this.getLanguageVersionByFilePath(containingFile); 341 if (!result) { 342 return undefined; 343 } 344 345 const alias = FileManager.aliasConfig.get(result.pkgName); 346 if (!alias) { 347 return undefined; 348 } 349 350 return alias.get(moduleName); 351 } 352 353 getGlueCodePathByModuleRequest(moduleRequest: string): { fullPath: string, basePath: string } | undefined { 354 const extensions = ['.ts', '.ets']; 355 for (const basePath of FileManager.staticSDKGlueCodePath) { 356 const fullPath = extensions 357 .map(ext => path.resolve(basePath, moduleRequest + ext)) 358 .find(fs.existsSync); 359 360 if (fullPath) { 361 return { 362 fullPath: toUnixPath(fullPath), 363 basePath: toUnixPath(basePath) 364 }; 365 } 366 } 367 368 return undefined; 369 } 370} 371 372export function initFileManagerInRollup(share: Object): void { 373 if (!share.projectConfig.mixCompile) { 374 return; 375 } 376 377 FileManager.mixCompile = true; 378 const sdkInfo = collectSDKInfo(share); 379 380 FileManager.init( 381 share.projectConfig.dependentModuleMap, 382 share.projectConfig.aliasPaths, 383 sdkInfo.dynamicSDKPath, 384 sdkInfo.staticSDKInteropDecl, 385 sdkInfo.staticSDKGlueCodePath 386 ); 387 FileManager.setRollUpObj(share); 388} 389 390export function collectSDKInfo(share: Object): { 391 dynamicSDKPath: Set<string>, 392 staticSDKInteropDecl: Set<string>, 393 staticSDKGlueCodePath: Set<string> 394} { 395 const dynamicSDKPath: Set<string> = new Set(); 396 const staticInteroSDKBasePath = process.env.staticInteroSDKBasePath || 397 path.resolve(share.projectConfig.etsLoaderPath, '../../../ets1.2/build-tools/interop'); 398 const staticSDKInteropDecl: Set<string> = new Set([ 399 path.resolve(staticInteroSDKBasePath, './declarations/kits'), 400 path.resolve(staticInteroSDKBasePath, './declarations/api'), 401 path.resolve(staticInteroSDKBasePath, './declarations/arkts'), 402 ].map(toUnixPath)); 403 404 const staticSDKGlueCodePath: Set<string> = new Set([ 405 path.resolve(staticInteroSDKBasePath, './bridge/kits'), 406 path.resolve(staticInteroSDKBasePath, './bridge/api'), 407 path.resolve(staticInteroSDKBasePath, './bridge/arkts'), 408 ].map(toUnixPath)); 409 410 const declarationsPath: string = path.resolve(share.projectConfig.etsLoaderPath, './declarations').replace(/\\/g, '/'); 411 const componentPath: string = path.resolve(share.projectConfig.etsLoaderPath, './components').replace(/\\/g, '/'); 412 const etsComponentPath: string = path.resolve(share.projectConfig.etsLoaderPath, '../../component').replace(/\\/g, '/'); 413 414 if (process.env.externalApiPaths) { 415 const externalApiPaths = path.resolve(process.env.externalApiPaths, '../'); 416 staticSDKGlueCodePath.add(path.resolve(externalApiPaths, './ets1.2/interop/bridge')); 417 staticSDKInteropDecl.add(path.resolve(externalApiPaths, './ets1.2/interop/declarations')); 418 } 419 420 dynamicSDKPath.add(declarationsPath); 421 dynamicSDKPath.add(componentPath); 422 dynamicSDKPath.add(etsComponentPath); 423 dynamicSDKPath.add(toUnixPath(share.projectConfig.etsLoaderPath)); 424 sdkConfigs.forEach(({ apiPath }) => { 425 apiPath.forEach(path => { 426 dynamicSDKPath.add(toUnixPath(path)); 427 }); 428 }); 429 return { 430 dynamicSDKPath: dynamicSDKPath, 431 staticSDKInteropDecl: staticSDKInteropDecl, 432 staticSDKGlueCodePath: staticSDKGlueCodePath 433 }; 434} 435 436function readFirstLineSync(filePath: string): string { 437 const buffer = fs.readFileSync(filePath, 'utf-8'); 438 const newlineIndex = buffer.indexOf('\n'); 439 if (newlineIndex === -1) { 440 return buffer.trim(); 441 } 442 return buffer.substring(0, newlineIndex).trim(); 443} 444 445export function isBridgeCode(filePath: string, projectConfig: Object): boolean { 446 if (!projectConfig?.mixCompile) { 447 return false; 448 } 449 for (const [pkgName, dependentModuleInfo] of projectConfig.dependentModuleMap) { 450 if (isSubPathOf(filePath, dependentModuleInfo.declgenBridgeCodePath)) { 451 return true; 452 } 453 } 454 return false; 455} 456 457export function isMixCompile(): boolean { 458 return process.env.mixCompile === 'true'; 459} 460 461/** 462 * Delete the 1.2 part in abilityPagesFullPath. This array will be used in transform. 463 * The 1.2 source files will not participate in the 1.1 compilation process. 464 */ 465export function processAbilityPagesFullPath(abilityPagesFullPath: Set<string>): void { 466 if (!isMixCompile()) { 467 return; 468 } 469 470 const extensions = ['.ts', '.ets']; 471 472 for (const filePath of Array.from(abilityPagesFullPath)) { 473 let realPath: string | null = null; 474 475 for (const ext of extensions) { 476 const candidate = filePath.endsWith(ext) ? filePath : filePath + ext; 477 if (fs.existsSync(candidate)) { 478 realPath = candidate; 479 break; 480 } 481 } 482 483 if (!realPath) { 484 continue; 485 } 486 487 const firstLine = readFirstLineSync(realPath); 488 if (firstLine.includes('use static')) { 489 abilityPagesFullPath.delete(filePath); 490 } 491 } 492} 493 494 495export function transformAbilityPages(abilityPath: string): boolean { 496 const entryBridgeCodePath = process.env.entryBridgeCodePath; 497 if (!entryBridgeCodePath) { 498 const errInfo = LogDataFactory.newInstance( 499 ErrorCode.ETS2BUNDLE_INTERNAL_MISSING_BRIDGECODE_PATH_INFO, 500 ArkTSInternalErrorDescription, 501 `Missing entryBridgeCodePath` 502 ); 503 throw Error(errInfo.toString()); 504 } 505 if (!entryFileLanguageInfo?.get(abilityPath)) { 506 return false; 507 } 508 if (abilityPath.includes(':')) { 509 abilityPath = abilityPath.substring(0, abilityPath.lastIndexOf(':')); 510 } 511 const bridgeCodePath = path.join(entryBridgeCodePath, abilityPath + EXTNAME_TS); 512 if (fs.existsSync(bridgeCodePath)) { 513 projectConfig.entryObj[transformModuleNameToRelativePath(abilityPath)] = bridgeCodePath; 514 return true; 515 } 516 return false; 517} 518 519function transformModuleNameToRelativePath(moduleName): string { 520 let defaultSourceRoot = 'src/main'; 521 const normalizedModuleName = moduleName.replace(/\\/g, '/'); 522 const normalizedRoot = defaultSourceRoot.replace(/\\/g, '/'); 523 524 const rootIndex = normalizedModuleName.indexOf(`/${normalizedRoot}/`); 525 if (rootIndex === -1) { 526 const errInfo = LogDataFactory.newInstance( 527 ErrorCode.ETS2BUNDLE_INTERNAL_WRONG_MODULE_NAME_FROM_ACEMODULEJSON, 528 ArkTSInternalErrorDescription, 529 `defaultSourceRoot '${defaultSourceRoot}' not found ` + 530 `when process moduleName '${moduleName}'` 531 ); 532 throw Error(errInfo.toString()); 533 } 534 535 const relativePath = normalizedModuleName.slice(rootIndex + normalizedRoot.length + 1); 536 return './' + relativePath; 537} 538 539export function getApiPathForInterop(apiDirs: string[], languageVersion: string): void { 540 if (languageVersion !== ARKTS_1_2) { 541 return; 542 } 543 544 const staticPaths = [...FileManager.staticSDKDeclPath]; 545 apiDirs.unshift(...staticPaths); 546} 547 548export function rebuildEntryObj(projectConfig: Object): void { 549 const entryObj = projectConfig.entryObj; 550 551 const removeExt = (p: string): string => p.replace(/\.[^/.]+$/, ''); 552 553 projectConfig.entryObj = Object.keys(entryObj).reduce((newEntry, key) => { 554 const newKey = key.replace(/^\.\//, ''); 555 const rawPath = entryObj[key]?.replace('?entry', ''); 556 if (!rawPath || !fs.existsSync(rawPath)) { 557 return newEntry; 558 } 559 560 const firstLine = fs.readFileSync(rawPath, 'utf-8').split('\n')[0]; 561 562 if (!firstLine.includes('use static')) { 563 newEntry[newKey] = rawPath; 564 } else if (rawPath.startsWith(projectConfig.projectRootPath)) { 565 const bridgePath = process.env.entryBridgeCodePath; 566 if (!bridgePath) { 567 const errInfo = LogDataFactory.newInstance( 568 ErrorCode.ETS2BUNDLE_INTERNAL_MISSING_BRIDGECODE_PATH_INFO, 569 ArkTSInternalErrorDescription, 570 `Missing entryBridgeCodePath` 571 ); 572 throw Error(errInfo.toString()); 573 } 574 575 const relativePath = path.relative(projectConfig.projectRootPath, rawPath); 576 const withoutExt = removeExt(relativePath); 577 newEntry[newKey] = path.join(bridgePath, withoutExt + '.ts'); 578 } 579 580 return newEntry; 581 }, {} as Record<string, string>); 582} 583