• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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