• 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 fs from 'fs';
17import * as path from 'path';
18import {
19    ABC_SUFFIX,
20    ARKTS_CONFIG_FILE_PATH,
21    changeFileExtension,
22    ensurePathExists,
23    getFileName,
24    getRootPath,
25    MOCK_ENTRY_DIR_PATH,
26    MOCK_ENTRY_FILE_NAME,
27    MOCK_LOCAL_SDK_DIR_PATH,
28    MOCK_OUTPUT_CACHE_PATH,
29    MOCK_OUTPUT_DIR_PATH,
30    MOCK_OUTPUT_FILE_NAME,
31    PANDA_SDK_STDLIB_PATH,
32    RUNTIME_API_PATH,
33    STDLIB_ESCOMPAT_PATH,
34    STDLIB_PATH,
35    STDLIB_STD_PATH,
36} from './path-config';
37
38export interface ArkTSConfigObject {
39    compilerOptions: {
40        package: string;
41        baseUrl: string;
42        paths: Record<string, string[]>;
43        dependencies: string[] | undefined;
44        entry: string;
45    };
46}
47
48export interface CompileFileInfo {
49    fileName: string;
50    filePath: string;
51    dependentFiles: string[];
52    abcFilePath: string;
53    arktsConfigFile: string;
54    stdLibPath: string;
55}
56
57export interface ModuleInfo {
58    isMainModule: boolean;
59    packageName: string;
60    moduleRootPath: string;
61    sourceRoots: string[];
62    entryFile: string;
63    arktsConfigFile: string;
64    compileFileInfos: CompileFileInfo[];
65    dependencies?: string[];
66}
67
68export interface DependentModule {
69    packageName: string;
70    moduleName: string;
71    moduleType: string;
72    modulePath: string;
73    sourceRoots: string[];
74    entryFile: string;
75}
76
77export type ModuleType = 'har' | string; // TODO: module type unclear
78
79export interface DependentModule {
80    packageName: string;
81    moduleName: string;
82    moduleType: ModuleType;
83    modulePath: string;
84    sourceRoots: string[];
85    entryFile: string;
86}
87
88export interface BuildConfig {
89    packageName: string;
90    compileFiles: string[];
91    loaderOutPath: string;
92    cachePath: string;
93    pandaSdkPath: string;
94    buildSdkPath: string;
95    sourceRoots: string[];
96    moduleRootPath: string;
97    dependentModuleList: DependentModule[];
98}
99
100export interface ArktsConfigBuilder {
101    buildConfig: BuildConfig;
102    entryFiles: Set<string>;
103    outputDir: string;
104    cacheDir: string;
105    pandaSdkPath: string;
106    buildSdkPath: string;
107    packageName: string;
108    sourceRoots: string[];
109    moduleRootPath: string;
110    dependentModuleList: DependentModule[];
111    moduleInfos: Map<string, ModuleInfo>;
112    mergedAbcFile: string;
113    // logger: Logger; // TODO
114    isDebug: boolean;
115}
116
117function writeArkTSConfigFile(
118    moduleInfo: ModuleInfo,
119    pathSection: Record<string, string[]>,
120    dependenciesSection: string[]
121): void {
122    if (!moduleInfo.sourceRoots || moduleInfo.sourceRoots.length == 0) {
123        throw new Error('Write Arkts config: does not have sourceRoots.');
124    }
125
126    const baseUrl: string = path.resolve(moduleInfo.moduleRootPath, moduleInfo.sourceRoots[0]);
127    pathSection[moduleInfo.packageName] = [baseUrl];
128    const arktsConfig: ArkTSConfigObject = {
129        compilerOptions: {
130            package: moduleInfo.packageName,
131            baseUrl: baseUrl,
132            paths: pathSection,
133            dependencies: dependenciesSection.length === 0 ? undefined : dependenciesSection,
134            entry: moduleInfo.entryFile,
135        },
136    };
137
138    ensurePathExists(moduleInfo.arktsConfigFile);
139    fs.writeFileSync(moduleInfo.arktsConfigFile, JSON.stringify(arktsConfig, null, 2), 'utf-8');
140}
141
142function getDependentModules(moduleInfo: ModuleInfo, moduleInfoMap: Map<string, ModuleInfo>): Map<string, ModuleInfo> {
143    if (moduleInfo.isMainModule) {
144        return moduleInfoMap;
145    }
146
147    const depModules: Map<string, ModuleInfo> = new Map<string, ModuleInfo>();
148    if (moduleInfo.dependencies) {
149        moduleInfo.dependencies.forEach((packageName: string) => {
150            const depModuleInfo: ModuleInfo | undefined = moduleInfoMap.get(packageName);
151            if (!depModuleInfo) {
152                throw new Error(`Dependent Module: Module ${packageName} not found in moduleInfos`);
153            } else {
154                depModules.set(packageName, depModuleInfo);
155            }
156        });
157    }
158    return depModules;
159}
160
161function traverse(currentDir: string, pathSection: Record<string, string[]>) {
162    const items = fs.readdirSync(currentDir);
163    for (const item of items) {
164        const itemPath = path.join(currentDir, item);
165        const stat = fs.statSync(itemPath);
166
167        if (stat.isFile()) {
168            const basename = path.basename(item, '.d.ets');
169            pathSection[basename] = [changeFileExtension(itemPath, '', '.d.ets')];
170        } else if (stat.isDirectory()) {
171            traverse(itemPath, pathSection);
172        }
173    }
174}
175
176function traverseSDK(currentDir: string, pathSection: Record<string, string[]>, prefix?: string) {
177    const items = fs.readdirSync(currentDir);
178
179    for (const item of items) {
180        const itemPath = path.join(currentDir, item);
181        const stat = fs.statSync(itemPath);
182
183        if (stat.isFile() && !itemPath.endsWith('.d.ets')) {
184            continue;
185        }
186
187        if (stat.isFile() && itemPath.endsWith('.d.ets')) {
188            const basename = path.basename(item, '.d.ets');
189            const name = prefix && prefix !== 'arkui.runtime-api' ? `${prefix}.${basename}` : basename;
190            pathSection[name] = [changeFileExtension(itemPath, '', '.d.ets')];
191        } else if (stat.isDirectory()) {
192            const basename = path.basename(itemPath);
193            const name = prefix && prefix !== 'arkui.runtime-api' ? `${prefix}.${basename}` : basename;
194            traverseSDK(itemPath, pathSection, name);
195        }
196    }
197}
198
199function mockBuildConfig(): BuildConfig {
200    return {
201        packageName: 'mock',
202        compileFiles: [path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, MOCK_ENTRY_FILE_NAME)],
203        loaderOutPath: path.resolve(getRootPath(), MOCK_OUTPUT_DIR_PATH),
204        cachePath: path.resolve(getRootPath(), MOCK_OUTPUT_CACHE_PATH),
205        pandaSdkPath: global.PANDA_SDK_PATH,
206        buildSdkPath: global.API_PATH,
207        sourceRoots: [getRootPath()],
208        moduleRootPath: path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH),
209        dependentModuleList: [],
210    };
211}
212
213class MockArktsConfigBuilder implements ArktsConfigBuilder {
214    buildConfig: BuildConfig;
215    entryFiles: Set<string>;
216    outputDir: string;
217    cacheDir: string;
218    pandaSdkPath: string;
219    buildSdkPath: string;
220    packageName: string;
221    sourceRoots: string[];
222    moduleRootPath: string;
223    dependentModuleList: DependentModule[];
224    moduleInfos: Map<string, ModuleInfo>;
225    mergedAbcFile: string;
226    isDebug: boolean;
227
228    constructor(buildConfig?: BuildConfig) {
229        const _buildConfig: BuildConfig = buildConfig ?? mockBuildConfig();
230        this.buildConfig = _buildConfig;
231        this.entryFiles = new Set<string>(_buildConfig.compileFiles as string[]);
232        this.outputDir = _buildConfig.loaderOutPath as string;
233        this.cacheDir = _buildConfig.cachePath as string;
234        this.pandaSdkPath = path.resolve(_buildConfig.pandaSdkPath as string);
235        this.buildSdkPath = path.resolve(_buildConfig.buildSdkPath as string);
236        this.packageName = _buildConfig.packageName as string;
237        this.sourceRoots = _buildConfig.sourceRoots as string[];
238        this.moduleRootPath = path.resolve(_buildConfig.moduleRootPath as string);
239        this.dependentModuleList = _buildConfig.dependentModuleList as DependentModule[];
240        this.isDebug = true;
241
242        this.moduleInfos = new Map<string, ModuleInfo>();
243        this.mergedAbcFile = path.resolve(this.outputDir, MOCK_OUTPUT_FILE_NAME);
244
245        this.generateModuleInfos();
246        this.generateArkTSConfigForModules();
247    }
248
249    private generateModuleInfos(): void {
250        if (!this.packageName || !this.moduleRootPath || !this.sourceRoots) {
251            throw new Error('Main module: packageName, moduleRootPath, and sourceRoots are required');
252        }
253        const mainModuleInfo: ModuleInfo = {
254            isMainModule: true,
255            packageName: this.packageName,
256            moduleRootPath: this.moduleRootPath,
257            sourceRoots: this.sourceRoots,
258            entryFile: '',
259            arktsConfigFile: path.resolve(this.cacheDir, this.packageName, ARKTS_CONFIG_FILE_PATH),
260            compileFileInfos: [],
261        };
262        this.moduleInfos.set(this.moduleRootPath, mainModuleInfo);
263        this.dependentModuleList.forEach((module: DependentModule) => {
264            if (!module.packageName || !module.modulePath || !module.sourceRoots || !module.entryFile) {
265                throw new Error('Dependent module: packageName, modulePath, sourceRoots, and entryFile are required');
266            }
267            const moduleInfo: ModuleInfo = {
268                isMainModule: false,
269                packageName: module.packageName,
270                moduleRootPath: module.modulePath,
271                sourceRoots: module.sourceRoots,
272                entryFile: module.entryFile,
273                arktsConfigFile: path.resolve(this.cacheDir, module.packageName, ARKTS_CONFIG_FILE_PATH),
274                compileFileInfos: [],
275            };
276            this.moduleInfos.set(module.modulePath, moduleInfo);
277        });
278        this.entryFiles.forEach((file: string) => {
279            const _file = path.resolve(file);
280            for (const [modulePath, moduleInfo] of this.moduleInfos) {
281                if (_file.startsWith(modulePath)) {
282                    const filePathFromModuleRoot: string = path.relative(modulePath, _file);
283                    const filePathInCache: string = path.join(
284                        this.cacheDir,
285                        moduleInfo.packageName,
286                        filePathFromModuleRoot
287                    );
288                    const abcFilePath: string = path.resolve(changeFileExtension(filePathInCache, ABC_SUFFIX));
289
290                    const fileInfo: CompileFileInfo = {
291                        fileName: getFileName(_file),
292                        filePath: _file,
293                        dependentFiles: [],
294                        abcFilePath: abcFilePath,
295                        arktsConfigFile: moduleInfo.arktsConfigFile,
296                        stdLibPath: path.resolve(this.pandaSdkPath, PANDA_SDK_STDLIB_PATH, STDLIB_PATH),
297                    };
298                    moduleInfo.compileFileInfos.push(fileInfo);
299                    return;
300                }
301            }
302            throw new Error('Entry File does not belong to any module in moduleInfos.');
303        });
304    }
305
306    private generateArkTSConfigForModules(): void {
307        const pathSection: Record<string, string[]> = {};
308        pathSection['std'] = [path.resolve(this.pandaSdkPath, PANDA_SDK_STDLIB_PATH, STDLIB_STD_PATH)];
309        pathSection['escompat'] = [path.resolve(this.pandaSdkPath, PANDA_SDK_STDLIB_PATH, STDLIB_ESCOMPAT_PATH)];
310        traverseSDK(this.buildSdkPath, pathSection);
311
312        this.moduleInfos.forEach((moduleInfo: ModuleInfo, moduleRootPath: string) => {
313            pathSection[moduleInfo.packageName] = [path.resolve(moduleRootPath, moduleInfo.sourceRoots[0])];
314        });
315
316        this.moduleInfos.forEach((moduleInfo: ModuleInfo, moduleRootPath: string) => {
317            const dependenciesSection: string[] = [];
318            this.generateDependenciesSection(moduleInfo, dependenciesSection);
319            writeArkTSConfigFile(moduleInfo, pathSection, dependenciesSection);
320        });
321    }
322
323    private generateDependenciesSection(moduleInfo: ModuleInfo, dependenciesSection: string[]): void {
324        const depModules: Map<string, ModuleInfo> = getDependentModules(moduleInfo, this.moduleInfos);
325        depModules.forEach((depModuleInfo: ModuleInfo) => {
326            if (depModuleInfo.isMainModule) {
327                return;
328            }
329            dependenciesSection.push(depModuleInfo.arktsConfigFile);
330        });
331    }
332}
333
334export { mockBuildConfig, MockArktsConfigBuilder };
335