1/* 2 * Copyright (c) 2023 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 cluster from 'cluster'; 17import fs from 'fs'; 18import path from 'path'; 19import ts from 'typescript'; 20import os from 'os'; 21import sourceMap from 'source-map'; 22import * as crypto from 'node:crypto'; 23 24import { 25 DEBUG, 26 ESMODULE, 27 EXTNAME_ETS, 28 EXTNAME_JS, 29 EXTNAME_TS, 30 EXTNAME_JSON, 31 EXTNAME_CJS, 32 EXTNAME_MJS, 33 TEMPORARY 34} from './common/ark_define'; 35import { 36 nodeLargeOrEqualTargetVersion, 37 genTemporaryPath, 38 mkdirsSync, 39 validateFilePathLength, 40 toUnixPath, 41 isPackageModulesFile, 42 isFileInProject 43} from '../../utils'; 44import { 45 tryMangleFileName, 46 writeObfuscatedSourceCode, 47} from '../../ark_utils'; 48import { AOT_FULL, AOT_PARTIAL, AOT_TYPE } from '../../pre_define'; 49import { SourceMapGenerator } from './generate_sourcemap'; 50import { 51 handleObfuscatedFilePath, 52 enableObfuscateFileName, 53 enableObfuscatedFilePathConfig, 54 getRelativeSourcePath 55} from './common/ob_config_resolver'; 56import { 57 CompileEvent, 58 createAndStartEvent, 59 stopEvent 60} from '../../performance'; 61import { BytecodeObfuscator } from './bytecode_obfuscator'; 62 63export let hasTsNoCheckOrTsIgnoreFiles: string[] = []; 64export let compilingEtsOrTsFiles: string[] = []; 65 66export function cleanUpFilesList(): void { 67 hasTsNoCheckOrTsIgnoreFiles = []; 68 compilingEtsOrTsFiles = []; 69} 70 71export function needAotCompiler(projectConfig: Object): boolean { 72 return projectConfig.compileMode === ESMODULE && (projectConfig.anBuildMode === AOT_FULL || 73 projectConfig.anBuildMode === AOT_PARTIAL); 74} 75 76export function isAotMode(projectConfig: Object): boolean { 77 return projectConfig.compileMode === ESMODULE && (projectConfig.anBuildMode === AOT_FULL || 78 projectConfig.anBuildMode === AOT_PARTIAL || projectConfig.anBuildMode === AOT_TYPE); 79} 80 81export function isDebug(projectConfig: Object): boolean { 82 return projectConfig.buildMode.toLowerCase() === DEBUG; 83} 84 85export function isBranchElimination(projectConfig: Object): boolean { 86 return projectConfig.branchElimination; 87} 88 89export function isMasterOrPrimary() { 90 return ((nodeLargeOrEqualTargetVersion(16) && cluster.isPrimary) || 91 (!nodeLargeOrEqualTargetVersion(16) && cluster.isMaster)); 92} 93 94export function changeFileExtension(file: string, targetExt: string, originExt = ''): string { 95 let currentExt = originExt.length === 0 ? path.extname(file) : originExt; 96 let fileWithoutExt = file.substring(0, file.lastIndexOf(currentExt)); 97 return fileWithoutExt + targetExt; 98} 99 100function removeCacheFile(cacheFilePath: string, ext: string): void { 101 let filePath = toUnixPath(changeFileExtension(cacheFilePath, ext)); 102 if (fs.existsSync(filePath)) { 103 fs.rmSync(filePath); 104 } 105} 106 107export function shouldETSOrTSFileTransformToJS(filePath: string, projectConfig: Object, metaInfo?: Object): boolean { 108 let cacheFilePath: string = genTemporaryPath(filePath, projectConfig.projectPath, projectConfig.cachePath, 109 projectConfig, metaInfo); 110 111 if (!projectConfig.processTs) { 112 removeCacheFile(cacheFilePath, EXTNAME_TS); 113 return true; 114 } 115 116 if (compilingEtsOrTsFiles.indexOf(filePath) !== -1) { 117 // file involves in compilation 118 const hasTsNoCheckOrTsIgnore = hasTsNoCheckOrTsIgnoreFiles.indexOf(filePath) !== -1; 119 // Remove cacheFile whose extension is different the target file 120 removeCacheFile(cacheFilePath, hasTsNoCheckOrTsIgnore ? EXTNAME_TS : EXTNAME_JS); 121 return hasTsNoCheckOrTsIgnore; 122 } 123 124 cacheFilePath = updateCacheFilePathIfEnableObuscatedFilePath(filePath, cacheFilePath, projectConfig); 125 cacheFilePath = toUnixPath(changeFileExtension(cacheFilePath, EXTNAME_JS)); 126 return fs.existsSync(cacheFilePath); 127} 128 129// This API is used exclusively to determine whether a file needs to be converted into a JS file without removing the cached files, 130// which differs from API 'shouldETSOrTSFileTransformToJS'. 131export function shouldETSOrTSFileTransformToJSWithoutRemove(filePath: string, projectConfig: Object, metaInfo?: Object): boolean { 132 if (!projectConfig.processTs) { 133 return true; 134 } 135 136 if (compilingEtsOrTsFiles.indexOf(filePath) !== -1) { 137 // file involves in compilation 138 return hasTsNoCheckOrTsIgnoreFiles.indexOf(filePath) !== -1; 139 } 140 141 let cacheFilePath: string = genTemporaryPath(filePath, projectConfig.projectPath, projectConfig.cachePath, 142 projectConfig, metaInfo); 143 cacheFilePath = updateCacheFilePathIfEnableObuscatedFilePath(filePath, cacheFilePath, projectConfig); 144 cacheFilePath = toUnixPath(changeFileExtension(cacheFilePath, EXTNAME_JS)); 145 return fs.existsSync(cacheFilePath); 146} 147 148function updateCacheFilePathIfEnableObuscatedFilePath(filePath: string, cacheFilePath: string, projectConfig: Object): string { 149 const isPackageModules = isPackageModulesFile(filePath, projectConfig); 150 if (enableObfuscatedFilePathConfig(isPackageModules, projectConfig) && enableObfuscateFileName(isPackageModules, projectConfig)) { 151 return handleObfuscatedFilePath(cacheFilePath, isPackageModules, projectConfig); 152 } 153 return cacheFilePath; 154} 155 156export async function writeFileContentToTempDir(id: string, content: string, projectConfig: Object, 157 logger: Object, parentEvent: CompileEvent, metaInfo: Object): Promise<void> { 158 if (isCommonJsPluginVirtualFile(id)) { 159 return; 160 } 161 162 if (!isCurrentProjectFiles(id, projectConfig)) { 163 return; 164 } 165 166 let filePath: string; 167 if (projectConfig.compileHar) { 168 // compileShared: compile shared har of project 169 filePath = genTemporaryPath(id, 170 projectConfig.compileShared ? projectConfig.projectRootPath : projectConfig.moduleRootPath, 171 projectConfig.compileShared ? path.resolve(projectConfig.aceModuleBuild, '../etsFortgz') : projectConfig.cachePath, 172 projectConfig, metaInfo, projectConfig.compileShared); 173 } else { 174 filePath = genTemporaryPath(id, projectConfig.projectPath, projectConfig.cachePath, projectConfig, metaInfo); 175 } 176 177 const eventWriteFileContent = createAndStartEvent(parentEvent, 'write file content'); 178 switch (path.extname(id)) { 179 case EXTNAME_ETS: 180 case EXTNAME_TS: 181 case EXTNAME_JS: 182 case EXTNAME_MJS: 183 case EXTNAME_CJS: 184 await writeFileContent(id, filePath, content, projectConfig, logger, metaInfo); 185 break; 186 case EXTNAME_JSON: 187 const newFilePath: string = tryMangleFileName(filePath, projectConfig, id, projectConfig.isBytecodeObfEnabled); 188 mkdirsSync(path.dirname(newFilePath)); 189 fs.writeFileSync(newFilePath, content ?? ''); 190 break; 191 default: 192 break; 193 } 194 stopEvent(eventWriteFileContent); 195} 196 197async function writeFileContent(sourceFilePath: string, filePath: string, content: string, 198 projectConfig: Object, logger: Object, metaInfo?: Object): Promise<void> { 199 if (!isSpecifiedExt(sourceFilePath, EXTNAME_JS)) { 200 filePath = changeFileExtension(filePath, EXTNAME_JS); 201 } 202 203 if (!isDebug(projectConfig)) { 204 const relativeSourceFilePath: string = getRelativeSourcePath(sourceFilePath, projectConfig.projectRootPath, 205 metaInfo?.belongProjectPath); 206 await writeObfuscatedSourceCode({content: content, buildFilePath: filePath, 207 relativeSourceFilePath: relativeSourceFilePath, originSourceFilePath: sourceFilePath, rollupModuleId: sourceFilePath}, 208 logger, projectConfig, SourceMapGenerator.getInstance().getSourceMaps()); 209 return; 210 } 211 mkdirsSync(path.dirname(filePath)); 212 fs.writeFileSync(filePath, content, 'utf-8'); 213} 214 215export function getEs2abcFileThreadNumber(): number { 216 const fileThreads: number = os.cpus().length < 16 ? os.cpus().length : 16; 217 return fileThreads; 218} 219 220export function isCommonJsPluginVirtualFile(filePath: string): boolean { 221 // rollup uses commonjs plugin to handle commonjs files, 222 // which will automatic generate files like 'jsfile.js?commonjs-exports' 223 return filePath.includes('\x00'); 224} 225 226export function isCurrentProjectFiles(filePath: string, projectConfig: Object): boolean { 227 if (projectConfig.rootPathSet) { 228 for (const projectRootPath of projectConfig.rootPathSet) { 229 if (isFileInProject(filePath, projectRootPath)) { 230 return true; 231 } 232 } 233 return false; 234 } 235 return isFileInProject(filePath, projectConfig.projectRootPath); 236} 237 238export function genTemporaryModuleCacheDirectoryForBundle(projectConfig: Object): string { 239 const buildDirArr: string[] = projectConfig.aceModuleBuild.split(path.sep); 240 const abilityDir: string = buildDirArr[buildDirArr.length - 1]; 241 const temporaryModuleCacheDirPath: string = path.join(projectConfig.cachePath, TEMPORARY, abilityDir); 242 mkdirsSync(temporaryModuleCacheDirPath); 243 244 return temporaryModuleCacheDirPath; 245} 246 247export function isSpecifiedExt(filePath: string, fileExtendName: string) { 248 return path.extname(filePath) === fileExtendName; 249} 250 251export function genCachePath(tailName: string, projectConfig: Object, logger: Object): string { 252 const pathName: string = projectConfig.cachePath !== undefined ? 253 path.join(projectConfig.cachePath, TEMPORARY, tailName) : path.join(projectConfig.aceModuleBuild, tailName); 254 mkdirsSync(path.dirname(pathName)); 255 256 validateFilePathLength(pathName, logger); 257 return pathName; 258} 259 260export function isTsOrEtsSourceFile(file: string): boolean { 261 return /(?<!\.d)\.[e]?ts$/.test(file); 262} 263 264export function isJsSourceFile(file: string): boolean { 265 return /\.[cm]?js$/.test(file); 266} 267 268export function isJsonSourceFile(file: string): boolean { 269 return /\.json$/.test(file); 270} 271 272export async function updateSourceMap(originMap: sourceMap.RawSourceMap, newMap: sourceMap.RawSourceMap): Promise<any> { 273 if (!originMap) { 274 return newMap; 275 } 276 if (!newMap) { 277 return originMap; 278 } 279 const originConsumer: sourceMap.SourceMapConsumer = await new sourceMap.SourceMapConsumer(originMap); 280 const newConsumer: sourceMap.SourceMapConsumer = await new sourceMap.SourceMapConsumer(newMap); 281 const newMappingList: sourceMap.MappingItem[] = []; 282 newConsumer.eachMapping((mapping: sourceMap.MappingItem) => { 283 if (mapping.originalLine == null) { 284 return; 285 } 286 const originalPos = 287 originConsumer.originalPositionFor({ line: mapping.originalLine, column: mapping.originalColumn }); 288 if (originalPos.source == null) { 289 return; 290 } 291 mapping.originalLine = originalPos.line; 292 mapping.originalColumn = originalPos.column; 293 newMappingList.push(mapping); 294 }); 295 const updatedGenerator: sourceMap.SourceMapGenerator = sourceMap.SourceMapGenerator.fromSourceMap(newConsumer); 296 updatedGenerator._file = originMap.file; 297 updatedGenerator._mappings._array = newMappingList; 298 return JSON.parse(updatedGenerator.toString()); 299} 300 301export function hasArkDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration | 302 ts.StructDeclaration | ts.ClassDeclaration | ts.TypeAliasDeclaration, decortorName: string): boolean { 303 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); 304 if (decorators && decorators.length) { 305 for (let i = 0; i < decorators.length; i++) { 306 const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').replace(/\s*/g, '').trim(); 307 return originalDecortor === decortorName; 308 } 309 } 310 return false; 311} 312 313export const utUtils = { 314 writeFileContent 315}; 316 317export function hasExistingPaths(paths: Set<string>): boolean { 318 for (const p of paths) { 319 if (fs.existsSync(toUnixPath(p))) { 320 return true; 321 } 322 } 323 return false; 324} 325 326export function isSubPathOf(targetPath: string, parentDir: string): boolean { 327 const resolvedParent = toUnixPath(path.resolve(parentDir)); 328 const resolvedTarget = toUnixPath(path.resolve(targetPath)); 329 return resolvedTarget === resolvedParent || resolvedTarget.startsWith(resolvedParent + '/'); 330} 331 332export function calculateFileHash(filePath: string, algorithm: string = 'sha256'): string { 333 const fileBuffer = fs.readFileSync(filePath); 334 const hash = crypto.createHash(algorithm); 335 hash.update(fileBuffer); 336 return hash.digest('hex'); 337}