• 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 * as path from 'path';
17import * as fs from 'fs';
18
19import { changeFileExtension, ensurePathExists } from '../utils';
20import { BuildConfig, ModuleInfo } from '../types';
21import { LANGUAGE_VERSION, SYSTEM_SDK_PATH_FROM_SDK } from '../preDefine';
22
23interface DynamicPathItem {
24  language: string;
25  declPath: string;
26  runtimeName: string;
27}
28
29interface ArkTSConfigObject {
30  compilerOptions: {
31    package: string;
32    baseUrl: string;
33    paths: Record<string, string[]>;
34    dependencies: string[] | undefined;
35    entry: string;
36    dynamicPaths: Record<string, DynamicPathItem>;
37  };
38}
39
40export class ArkTSConfigGenerator {
41  private static instance: ArkTSConfigGenerator | undefined;
42  private stdlibStdPath: string;
43  private stdlibEscompatPath: string;
44  private systemSdkPath: string;
45
46  private moduleInfos: Map<string, ModuleInfo>;
47  private pathSection: Record<string, string[]>;
48
49  private constructor(buildConfig: BuildConfig, moduleInfos: Map<string, ModuleInfo>) {
50    let pandaStdlibPath: string =
51      buildConfig.pandaStdlibPath ?? path.resolve(buildConfig.pandaSdkPath!!, 'lib', 'stdlib');
52    this.stdlibStdPath = path.resolve(pandaStdlibPath, 'std');
53    this.stdlibEscompatPath = path.resolve(pandaStdlibPath, 'escompat');
54    this.systemSdkPath = path.resolve(buildConfig.buildSdkPath, SYSTEM_SDK_PATH_FROM_SDK);
55
56    this.moduleInfos = moduleInfos;
57    this.pathSection = {};
58  }
59
60  public static getInstance(buildConfig?: BuildConfig, moduleInfos?: Map<string, ModuleInfo>): ArkTSConfigGenerator {
61    if (!ArkTSConfigGenerator.instance) {
62      if (!buildConfig || !moduleInfos) {
63        throw new Error('buildConfig and moduleInfos is required for the first instantiation of ArkTSConfigGenerator.');
64      }
65      ArkTSConfigGenerator.instance = new ArkTSConfigGenerator(buildConfig, moduleInfos);
66    }
67    return ArkTSConfigGenerator.instance;
68  }
69
70  public static getGenerator(buildConfig: BuildConfig, moduleInfos: Map<string, ModuleInfo>): ArkTSConfigGenerator {
71    return new ArkTSConfigGenerator(buildConfig, moduleInfos);
72  }
73
74  public static destroyInstance(): void {
75    ArkTSConfigGenerator.instance = undefined;
76  }
77
78  private generateSystemSdkPathSection(pathSection: Record<string, string[]>): void {
79    function traverse(currentDir: string, relativePath: string = '', isExcludedDir: boolean = false): void {
80      const items = fs.readdirSync(currentDir);
81      for (const item of items) {
82        const itemPath = path.join(currentDir, item);
83        const stat = fs.statSync(itemPath);
84
85        if (stat.isFile()) {
86          const basename = path.basename(item, '.d.ets');
87          const key = isExcludedDir ? basename : relativePath ? `${relativePath}.${basename}` : basename;
88          pathSection[key] = [changeFileExtension(itemPath, '', '.d.ets')];
89        }
90        if (stat.isDirectory()) {
91          // For non-arkui files under api dir,
92          // fill path section with `"pathFromApi.subdir.fileName" = [${absolute_path_to_file}]`;
93          // For arkui files under api dir,
94          // fill path section with `"fileName" = [${absolute_path_to_file}]`.
95          const isCurrentDirExcluded = path.basename(currentDir) === 'arkui' && item === 'runtime-api';
96          const newRelativePath = isCurrentDirExcluded ? '' : relativePath ? `${relativePath}.${item}` : item;
97          traverse(path.resolve(currentDir, item), newRelativePath, isCurrentDirExcluded || isExcludedDir);
98        }
99      }
100    }
101
102    let apiPath: string = path.resolve(this.systemSdkPath, 'api');
103    fs.existsSync(apiPath) ? traverse(apiPath) : console.error(`sdk path ${apiPath} not exist.`);
104
105    let arktsPath: string = path.resolve(this.systemSdkPath, 'arkts');
106    fs.existsSync(arktsPath) ? traverse(arktsPath) : console.error(`sdk path ${arktsPath} not exist.`);
107
108    let kitsPath: string = path.resolve(this.systemSdkPath, 'kits');
109    fs.existsSync(kitsPath) ? traverse(kitsPath) : console.error(`sdk path ${kitsPath} not exist.`);
110  }
111
112  private getPathSection(): Record<string, string[]> {
113    if (Object.keys(this.pathSection).length !== 0) {
114      return this.pathSection;
115    }
116
117    this.pathSection.std = [this.stdlibStdPath];
118    this.pathSection.escompat = [this.stdlibEscompatPath];
119
120    this.generateSystemSdkPathSection(this.pathSection);
121
122    this.moduleInfos.forEach((moduleInfo: ModuleInfo, packageName: string) => {
123      if (moduleInfo.language === LANGUAGE_VERSION.ARKTS_1_2) {
124        this.pathSection[moduleInfo.packageName] = [path.resolve(moduleInfo.moduleRootPath, moduleInfo.sourceRoots[0])];
125      }
126    });
127
128    return this.pathSection;
129  }
130
131  private getDependenciesSection(moduleInfo: ModuleInfo, dependenciesSection: string[]): void {
132    let depModules: Map<string, ModuleInfo> = moduleInfo.staticDepModuleInfos;
133    depModules.forEach((depModuleInfo: ModuleInfo) => {
134      dependenciesSection.push(depModuleInfo.arktsConfigFile);
135    });
136  }
137
138  private getOhmurl(file: string, moduleInfo: ModuleInfo): string {
139    let unixFilePath: string = file.replace(/\\/g, '/');
140    let ohmurl: string = moduleInfo.packageName + '/' + unixFilePath;
141    return changeFileExtension(ohmurl, '');
142  }
143
144  private getDynamicPathSection(moduleInfo: ModuleInfo, dynamicPathSection: Record<string, DynamicPathItem>): void {
145    let depModules: Map<string, ModuleInfo> = moduleInfo.dynamicDepModuleInfos;
146
147    depModules.forEach((depModuleInfo: ModuleInfo) => {
148      if (!depModuleInfo.declFilesPath || !fs.existsSync(depModuleInfo.declFilesPath)) {
149        console.error(`Module ${moduleInfo.packageName} depends on dynamic module ${depModuleInfo.packageName}, but
150          decl file not found on path ${depModuleInfo.declFilesPath}`);
151        return;
152      }
153      let declFilesObject = JSON.parse(fs.readFileSync(depModuleInfo.declFilesPath, 'utf-8'));
154      Object.keys(declFilesObject.files).forEach((file: string) => {
155        let ohmurl: string = this.getOhmurl(file, depModuleInfo);
156        dynamicPathSection[ohmurl] = {
157          language: 'js',
158          declPath: declFilesObject.files[file].declPath,
159          runtimeName: declFilesObject.files[file].ohmUrl
160        };
161
162        let absFilePath: string = path.resolve(depModuleInfo.moduleRootPath, file);
163        let entryFileWithoutExtension: string = changeFileExtension(depModuleInfo.entryFile, '');
164        if (absFilePath === entryFileWithoutExtension) {
165          dynamicPathSection[depModuleInfo.packageName] = dynamicPathSection[ohmurl];
166        }
167      });
168    });
169  }
170
171  public writeArkTSConfigFile(moduleInfo: ModuleInfo): void {
172    if (!moduleInfo.sourceRoots || moduleInfo.sourceRoots.length === 0) {
173      console.error('SourceRoots not set from hvigor.');
174    }
175    let pathSection = this.getPathSection();
176    let dependenciesSection: string[] = [];
177    let dynamicPathSection: Record<string, DynamicPathItem> = {};
178    this.getDynamicPathSection(moduleInfo, dynamicPathSection);
179
180    let baseUrl: string = path.resolve(moduleInfo.moduleRootPath, moduleInfo.sourceRoots[0]);
181    let arktsConfig: ArkTSConfigObject = {
182      compilerOptions: {
183        package: moduleInfo.packageName,
184        baseUrl: baseUrl,
185        paths: pathSection,
186        dependencies: dependenciesSection.length === 0 ? undefined : dependenciesSection,
187        entry: moduleInfo.entryFile,
188        dynamicPaths: dynamicPathSection
189      }
190    };
191
192    ensurePathExists(moduleInfo.arktsConfigFile);
193    fs.writeFileSync(moduleInfo.arktsConfigFile, JSON.stringify(arktsConfig, null, 2), 'utf-8');
194  }
195}
196