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