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