diff --git a/compiler/compile_plugin.js b/compiler/compile_plugin.js index 675dae30d032c92bd3a0bdf3ddaf5808ce654a26..73297fc827bb12308af49e2f16592309729388dc 100644 --- a/compiler/compile_plugin.js +++ b/compiler/compile_plugin.js @@ -28,7 +28,24 @@ const { generateConsumerObConfigFile } = require('./lib/fast_build/ark_compiler/ const { etsStandaloneChecker } = require('./lib/ets_checker'); const { memoryMonitor } = require('./lib/fast_build/meomry_monitor/rollup-plugin-memory-monitor'); +let initConfigForInterop = (interopConfig) => { + return {}; +}; + +try { + ({ initConfigForInterop } = require('./lib/fast_build/ark_compiler/interop/interop_manager')); +} catch (err) { + if (err.code !== 'MODULE_NOT_FOUND') { + throw err; + } +} + +function initInteropConfig(interopConfig) { + return initConfigForInterop(interopConfig); +} + exports.initConfig = initConfig; +exports.initInteropConfig = initInteropConfig; exports.getCleanConfig = getCleanConfig; exports.generateConsumerObConfigFile = generateConsumerObConfigFile; exports.etsStandaloneChecker = etsStandaloneChecker; diff --git a/compiler/src/interop/main.js b/compiler/src/interop/main.js index 36cbe76f28673da6b9afabad92ea85eac42b6b16..c76d06e6092707d88a4fea3cb1c089d05a6b2dfa 100644 --- a/compiler/src/interop/main.js +++ b/compiler/src/interop/main.js @@ -44,14 +44,14 @@ const { } = require('log4js'); const { - entryFileLanguageInfo, + setEntryFileLanguage, isMixCompile, processAbilityPagesFullPath, transformAbilityPages } = require('./lib/fast_build/ark_compiler/interop/interop_manager'); const { - ARKTS_1_2 + ARKTS_MODE } = require('./lib/fast_build/ark_compiler/interop/pre_define'); configure({ @@ -123,6 +123,8 @@ function initProjectConfig(projectConfig) { projectConfig.allowEmptyBundleName = false; projectConfig.uiTransformOptimization = false; projectConfig.ignoreCrossplatformCheck = false; + projectConfig.isolatedDeclarations = false; + projectConfig.noCheck = false; } function initProjectPathConfig(projectConfig) { @@ -505,16 +507,16 @@ function readAbilityEntrance(moduleJson) { if (moduleJson.module) { const moduleSrcEntrance = moduleJson.module.srcEntrance; const moduleSrcEntry = moduleJson.module.srcEntry; - const isStatic = moduleJson.module?.abilityStageCodeLanguage === ARKTS_1_2; + const isStatic = moduleJson.module?.arkTSMode === ARKTS_MODE.STATIC; if (moduleSrcEntry) { abilityPages.push(moduleSrcEntry); abilityPagesFullPath.add(getAbilityFullPath(projectConfig.projectPath, moduleSrcEntry)); - entryFileLanguageInfo.set(moduleSrcEntry, isStatic); + setEntryFileLanguage(moduleSrcEntry, isStatic); } else if (moduleSrcEntrance) { abilityPages.push(moduleSrcEntrance); abilityPagesFullPath.add(getAbilityFullPath(projectConfig.projectPath, moduleSrcEntrance)); - entryFileLanguageInfo.set(moduleSrcEntrance, isStatic); + setEntryFileLanguage(moduleSrcEntrance, isStatic); } if (moduleJson.module.abilities && moduleJson.module.abilities.length > 0) { setEntrance(moduleJson.module.abilities, abilityPages); @@ -530,14 +532,14 @@ function readAbilityEntrance(moduleJson) { function setEntrance(abilityConfig, abilityPages) { if (abilityConfig && abilityConfig.length > 0) { abilityConfig.forEach(ability => { - const isStatic = ability.codeLanguage === ARKTS_1_2; + const isStatic = ability.arkTSMode === ARKTS_MODE.STATIC; if (ability.srcEntry) { abilityPages.push(ability.srcEntry); - entryFileLanguageInfo.set(ability.srcEntry, isStatic); + setEntryFileLanguage(ability.srcEntry, isStatic); abilityPagesFullPath.add(getAbilityFullPath(projectConfig.projectPath, ability.srcEntry)); } else if (ability.srcEntrance) { abilityPages.push(ability.srcEntrance); - entryFileLanguageInfo.set(ability.srcEntrance, isStatic); + setEntryFileLanguage(ability.srcEntrance, isStatic); abilityPagesFullPath.add(getAbilityFullPath(projectConfig.projectPath, ability.srcEntrance)); } }); diff --git a/compiler/src/interop/src/fast_build/ark_compiler/interop/interop_manager.ts b/compiler/src/interop/src/fast_build/ark_compiler/interop/interop_manager.ts index 3fe736afd4403ffe6e8ef43e1a952537263f39c0..4ec3efcc8d9dee4c0057eb1c10c37528a0fe2a38 100644 --- a/compiler/src/interop/src/fast_build/ark_compiler/interop/interop_manager.ts +++ b/compiler/src/interop/src/fast_build/ark_compiler/interop/interop_manager.ts @@ -17,14 +17,23 @@ import fs from 'fs'; import path from 'path'; import { + globalModulePaths, + initBuildInfo, + loadEntryObj, + loadModuleInfo, + loadWorker, projectConfig, + readAppResource, + readPatchConfig, + readWorkerFile, sdkConfigs } from '../../../../main'; import { toUnixPath } from '../../../utils'; import { ArkTSEvolutionModule, FileInfo, - AliasConfig + AliasConfig, + InteropConfig } from './type'; import { hasExistingPaths, @@ -46,8 +55,15 @@ import { ARKTS_1_2, ARKTS_HYBRID } from './pre_define'; +import { readFirstLineSync } from './utils'; -export let entryFileLanguageInfo = new Map(); +let entryFileLanguageInfo = new Map(); +export let workerFile = null; +export let mixCompile = undefined; + +export function setEntryFileLanguage(filePath: string, language: string): void { + entryFileLanguageInfo.set(filePath, language); +} export class FileManager { private static instance: FileManager | undefined = undefined; @@ -60,7 +76,7 @@ export class FileManager { static mixCompile: boolean = false; static glueCodeFileInfos: Map = new Map(); static isInteropSDKEnabled: boolean = false; - static sharedObj: Object | undefined = undefined; + interopConfig: InteropConfig | undefined = undefined; private constructor() { } @@ -101,14 +117,18 @@ export class FileManager { return FileManager.instance; } - public static setRollUpObj(shared: Object): void { - FileManager.sharedObj = shared; - } - public static setMixCompile(mixCompile: boolean): void { FileManager.mixCompile = mixCompile; } + public setInteropConfig(interopConfig: InteropConfig): void { + this.interopConfig = interopConfig; + } + + public getInteropConfig(): InteropConfig { + return this.interopConfig; + } + private static initLanguageVersionFromDependentModuleMap( dependentModuleMap: Map ): void { @@ -217,7 +237,6 @@ export class FileManager { FileManager.glueCodeFileInfos?.clear(); FileManager.aliasConfig?.clear(); FileManager.mixCompile = false; - entryFileLanguageInfo.clear(); } getLanguageVersionByFilePath(filePath: string): { @@ -302,11 +321,7 @@ export class FileManager { } private static logError(error: LogData): void { - if (FileManager.sharedObj) { - CommonLogger.getInstance(FileManager.sharedObj).printErrorAndExit(error); - } else { - console.error(error.toString()); - } + console.error(error.toString()); } private static matchSDKPath(path: string): { @@ -369,22 +384,22 @@ export class FileManager { } } -export function initFileManagerInRollup(share: Object): void { - if (!share.projectConfig.mixCompile) { +export function initFileManagerInRollup(InteropConfig: InteropConfig): void { + if (!isMixCompile()) { return; } FileManager.mixCompile = true; - const sdkInfo = collectSDKInfo(share); + const sdkInfo = collectSDKInfo(InteropConfig); FileManager.init( - share.projectConfig.dependentModuleMap, - share.projectConfig.aliasPaths, + InteropConfig.projectConfig.dependentModuleMap, + InteropConfig.projectConfig.aliasPaths, sdkInfo.dynamicSDKPath, sdkInfo.staticSDKInteropDecl, sdkInfo.staticSDKGlueCodePath ); - FileManager.setRollUpObj(share); + FileManager.getInstance().setInteropConfig(InteropConfig); } export function collectSDKInfo(share: Object): { @@ -433,15 +448,6 @@ export function collectSDKInfo(share: Object): { }; } -function readFirstLineSync(filePath: string): string { - const buffer = fs.readFileSync(filePath, 'utf-8'); - const newlineIndex = buffer.indexOf('\n'); - if (newlineIndex === -1) { - return buffer.trim(); - } - return buffer.substring(0, newlineIndex).trim(); -} - export function isBridgeCode(filePath: string, projectConfig: Object): boolean { if (!projectConfig?.mixCompile) { return false; @@ -455,6 +461,9 @@ export function isBridgeCode(filePath: string, projectConfig: Object): boolean { } export function isMixCompile(): boolean { + if (typeof mixCompile === 'boolean') { + return mixCompile; + } return process.env.mixCompile === 'true'; } @@ -493,7 +502,7 @@ export function processAbilityPagesFullPath(abilityPagesFullPath: Set): export function transformAbilityPages(abilityPath: string): boolean { - const entryBridgeCodePath = process.env.entryBridgeCodePath; + const entryBridgeCodePath = getBrdigeCodeRootPath(abilityPath, FileManager.getInstance().getInteropConfig()); if (!entryBridgeCodePath) { const errInfo = LogDataFactory.newInstance( ErrorCode.ETS2BUNDLE_INTERNAL_MISSING_BRIDGECODE_PATH_INFO, @@ -516,9 +525,12 @@ export function transformAbilityPages(abilityPath: string): boolean { return false; } -function transformModuleNameToRelativePath(moduleName): string { +export function transformModuleNameToRelativePath(filePath: string): string { let defaultSourceRoot = 'src/main'; - const normalizedModuleName = moduleName.replace(/\\/g, '/'); + if (FileManager.getInstance().getInteropConfig()?.projectConfig?.isOhosTest) { + defaultSourceRoot = 'src/ohosTest'; + } + const normalizedModuleName = filePath.replace(/\\/g, '/'); const normalizedRoot = defaultSourceRoot.replace(/\\/g, '/'); const rootIndex = normalizedModuleName.indexOf(`/${normalizedRoot}/`); @@ -527,15 +539,16 @@ function transformModuleNameToRelativePath(moduleName): string { ErrorCode.ETS2BUNDLE_INTERNAL_WRONG_MODULE_NAME_FROM_ACEMODULEJSON, ArkTSInternalErrorDescription, `defaultSourceRoot '${defaultSourceRoot}' not found ` + - `when process moduleName '${moduleName}'` + `when process moduleName '${filePath}'` ); throw Error(errInfo.toString()); } - const relativePath = normalizedModuleName.slice(rootIndex + normalizedRoot.length + 1); + const relativePath = normalizedModuleName.slice(rootIndex + normalizedRoot.length + 1).replace(/^\/+/, ''); return './' + relativePath; } + export function getApiPathForInterop(apiDirs: string[], languageVersion: string): void { if (languageVersion !== ARKTS_1_2) { return; @@ -545,7 +558,7 @@ export function getApiPathForInterop(apiDirs: string[], languageVersion: string) apiDirs.unshift(...staticPaths); } -export function rebuildEntryObj(projectConfig: Object): void { +export function rebuildEntryObj(projectConfig: Object, interopConfig: InteropConfig): void { const entryObj = projectConfig.entryObj; const removeExt = (p: string): string => p.replace(/\.[^/.]+$/, ''); @@ -562,7 +575,7 @@ export function rebuildEntryObj(projectConfig: Object): void { if (!firstLine.includes('use static')) { newEntry[newKey] = rawPath; } else if (rawPath.startsWith(projectConfig.projectRootPath)) { - const bridgePath = process.env.entryBridgeCodePath; + const bridgePath = getBrdigeCodeRootPath(rawPath, interopConfig); if (!bridgePath) { const errInfo = LogDataFactory.newInstance( ErrorCode.ETS2BUNDLE_INTERNAL_MISSING_BRIDGECODE_PATH_INFO, @@ -580,3 +593,64 @@ export function rebuildEntryObj(projectConfig: Object): void { return newEntry; }, {} as Record); } + + +/** + * corresponds to compiler/src/fast_build/common/init_config.ts - initConfig() + * As the entry for mix compile,so mixCompile status will be set true + */ +export function initConfigForInterop(interopConfig: InteropConfig): Object { + initFileManagerInRollup(interopConfig); + + function getEntryObj(): void { + loadEntryObj(projectConfig); + initBuildInfo(); + readPatchConfig(); + loadModuleInfo(projectConfig); + workerFile = readWorkerFile(); + if (!projectConfig.isPreview) { + loadWorker(projectConfig, workerFile); + } + if (isMixCompile()) { + rebuildEntryObj(projectConfig, interopConfig); + return; + } + projectConfig.entryObj = Object.keys(projectConfig.entryObj).reduce((newEntry, key) => { + const newKey: string = key.replace(/^\.\//, ''); + newEntry[newKey] = projectConfig.entryObj[key].replace('?entry', ''); + return newEntry; + }, {}); + } + mixCompile = true; + getEntryObj(); + if (process.env.appResource) { + readAppResource(process.env.appResource); + } + return { + entryObj: Object.assign({}, projectConfig.entryObj, projectConfig.otherCompileFiles), + cardEntryObj: projectConfig.cardEntryObj, + workerFile: workerFile, + globalModulePaths: globalModulePaths + }; +} + +export function getBrdigeCodeRootPath(filePath: string, interopConfig: InteropConfig): string | undefined { + if (!interopConfig) { + return process.env.entryBridgeCodePath; + } + + for (const [moduleRootPath, InteropInfo] of interopConfig.interopModuleInfo) { + if (isSubPathOf(filePath, moduleRootPath)) { + return InteropInfo.declgenBridgeCodePath; + } + } + + return undefined; +} + +export function destroyInterop(): void { + FileManager.cleanFileManagerObject(); + entryFileLanguageInfo.clear(); + mixCompile = false; +} + diff --git a/compiler/src/interop/src/fast_build/ark_compiler/interop/pre_define.ts b/compiler/src/interop/src/fast_build/ark_compiler/interop/pre_define.ts index eaa908d4149f56a25a56bee5b66a7200743d75d5..82aaebb55ce173949d74fc49d0930e0e0b16a2b5 100644 --- a/compiler/src/interop/src/fast_build/ark_compiler/interop/pre_define.ts +++ b/compiler/src/interop/src/fast_build/ark_compiler/interop/pre_define.ts @@ -16,4 +16,11 @@ export const ARKTS_1_2: string = '1.2'; export const ARKTS_1_1: string = '1.1'; export const ARKTS_1_0: string = '1.0'; -export const ARKTS_HYBRID: string = 'hybrid'; \ No newline at end of file +export const ARKTS_HYBRID: string = 'hybrid'; + +export const DECLGEN_CACHE_FILE = 'declgen_cache.json'; + +export enum ARKTS_MODE { + STATIC = 'static', + DYNAMIC = 'dynamic' +} \ No newline at end of file diff --git a/compiler/src/interop/src/fast_build/ark_compiler/interop/type.ts b/compiler/src/interop/src/fast_build/ark_compiler/interop/type.ts index 3a3dc2a11c4c1e7e197241e5f46c4bcaa55c8624..cb4b3a29ea850cd50e2b8e6bab823639be24949f 100644 --- a/compiler/src/interop/src/fast_build/ark_compiler/interop/type.ts +++ b/compiler/src/interop/src/fast_build/ark_compiler/interop/type.ts @@ -112,4 +112,13 @@ export interface FileInfo { baseUrl: string; abstractPath: string; } -export const DECLGEN_CACHE_FILE = 'declgen_cache.json'; + +export interface InteropInfo { + declgenBridgeCodePath: string; + declgenV1OutPath: string; +} + +export interface InteropConfig { + interopModuleInfo: Map; + projectConfig: Object; +} diff --git a/compiler/src/interop/src/fast_build/ark_compiler/interop/utils.ts b/compiler/src/interop/src/fast_build/ark_compiler/interop/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..757c30f8d08628637e1ab8e830e4e9fb881e3e04 --- /dev/null +++ b/compiler/src/interop/src/fast_build/ark_compiler/interop/utils.ts @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from 'fs'; + +export function readFirstLineSync(filePath: string): string { + const buffer = fs.readFileSync(filePath, 'utf-8'); + const newlineIndex = buffer.indexOf('\n'); + if (newlineIndex === -1) { + return buffer.trim(); + } + return buffer.substring(0, newlineIndex).trim(); +} \ No newline at end of file diff --git a/compiler/test/ark_compiler_ut/interop/interop_manager.test.ts b/compiler/test/ark_compiler_ut/interop/interop_manager.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..5f3569ad7629b1266c3ca160ea5c19c310048c83 --- /dev/null +++ b/compiler/test/ark_compiler_ut/interop/interop_manager.test.ts @@ -0,0 +1,422 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use rollupObject file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; +import mocha from 'mocha'; +import path from "path"; + +import { + FileManager, + collectSDKInfo, + isBridgeCode, + getBrdigeCodeRootPath, + isMixCompile, + initConfigForInterop, + destroyInterop, + transformModuleNameToRelativePath + } from '../../../lib/fast_build/ark_compiler/interop/interop_manager'; +import { ARKTS_1_1, ARKTS_1_2, ARKTS_HYBRID } from '../../../lib/fast_build/ark_compiler/interop/pre_define'; +import { sdkConfigs } from '../../../main'; +import { toUnixPath } from '../../../lib/utils'; +import RollUpPluginMock from '../mock/rollup_mock/rollup_plugin_mock'; + +export interface ArkTSEvolutionModule { + language: string; + packageName: string; + moduleName: string; + modulePath: string; + declgenV1OutPath?: string; + declgenV2OutPath?: string; + declgenBridgeCodePath?: string; + declFilesPath?: string; + dynamicFiles: string[]; + staticFiles: string[]; + cachePath: string; + byteCodeHarInfo?: Object; +} + +mocha.describe('test interop_manager file api', function () { + mocha.before(function () { + const dependentModuleMap: Map = new Map(); + const dynamicSDKPath: Set = new Set([ + '/sdk/default/openharmony/ets/ets1.1/api', + '/sdk/default/openharmony/ets/ets1.1/arkts', + '/sdk/default/openharmony/ets/ets1.1/kits', + '/sdk/default/openharmony/ets/ets1.1/build-tools/ets-loader/declarations', + '/sdk/default/openharmony/ets/ets1.1/build-tools/ets-loader/component', + '/sdk/default/openharmony/ets/ets1.1/build-tools/components' + ]); + const staticSDKDeclPath: Set = new Set([ + '/sdk/default/openharmony/ets/ets1.2interop/declarations/kit', + '/sdk/default/openharmony/ets/ets1.2interop/declarations/api', + '/sdk/default/openharmony/ets/ets1.2interop/declarations/arkts' + ]); + const staticSDKGlueCodePath: Set = new Set([ + '/sdk/default/openharmony/ets/ets1.2interop/bridge/kit', + '/sdk/default/openharmony/ets/ets1.2interop/bridge/api', + '/sdk/default/openharmony/ets/ets1.2interop/bridge/arkts' + ]); + dependentModuleMap.set('application', { + language: ARKTS_1_1, + packageName: 'application', + moduleName: 'application', + modulePath: '/MyApplication16/application', + declgenV1OutPath: '/MyApplication16/application/build/default/intermediates/declgen/default/declgenV1', + declgenV2OutPath: '/MyApplication16/application/build/default/intermediates/declgen/default/declgenV2', + declgenBridgeCodePath: '/MyApplication16/application/build/default/intermediates/declgen/default/declgenBridgeCode', + declFilesPath: '/MyApplication16/application/build/default/intermediates/declgen/default/decl-fileInfo.json', + dynamicFiles: [], + staticFiles: [], + cachePath: '/MyApplication16/application/build/cache', + byteCodeHarInfo: {} + }); + + dependentModuleMap.set('harv2', { + language: ARKTS_1_2, + packageName: 'harv2', + moduleName: 'harv2', + modulePath: '/MyApplication16/harv2', + declgenV1OutPath: '/MyApplication16/harv2/build/default/intermediates/declgen/default/declgenV1', + declgenV2OutPath: '/MyApplication16/harv2/build/default/intermediates/declgen/default/declgenV2', + declgenBridgeCodePath: '/MyApplication16/harv2/build/default/intermediates/declgen/default/declgenBridgeCode', + declFilesPath: '/MyApplication16/harv2/build/default/intermediates/declgen/default/decl-fileInfo.json', + dynamicFiles: [], + staticFiles: [], + cachePath: '/MyApplication16/harv2/build/cache', + byteCodeHarInfo: {} + }); + + dependentModuleMap.set('dynamic1', { + language: ARKTS_1_1, + packageName: 'dynamic1', + moduleName: 'dynamic1', + modulePath: '/MyApplication16/dynamic1', + declgenV1OutPath: '/MyApplication16/dynamic1/build/default/intermediates/declgen/default/declgenV1', + declgenV2OutPath: '/MyApplication16/dynamic1/build/default/intermediates/declgen/default/declgenV2', + declgenBridgeCodePath: '/MyApplication16/dynamic1/build/default/intermediates/declgen/default/declgenBridgeCode', + declFilesPath: '/MyApplication16/dynamic1/build/default/intermediates/declgen/default/decl-fileInfo.json', + dynamicFiles: [], + staticFiles: [], + cachePath: '/MyApplication16/dynamic1/build/cache', + byteCodeHarInfo: {} + }); + + dependentModuleMap.set('hybrid', { + language: ARKTS_HYBRID, + packageName: 'hybrid', + moduleName: 'hybrid', + modulePath: '/MyApplication16/hybrid', + declgenV1OutPath: '/MyApplication16/hybrid/build/default/intermediates/declgen/default/declgenV1', + declgenV2OutPath: '/MyApplication16/hybrid/build/default/intermediates/declgen/default/declgenV2', + declgenBridgeCodePath: '/MyApplication16/hybrid/build/default/intermediates/declgen/default/declgenBridgeCode', + declFilesPath: '/MyApplication16/hybrid/build/default/intermediates/declgen/default/decl-fileInfo.json', + dynamicFiles: ['/MyApplication16/hybrid/fileV1.ets'], + staticFiles: ['/MyApplication16/hybrid/fileV2.ets'], + cachePath: '/MyApplication16/hybrid/build/cache', + byteCodeHarInfo: {} + }); + FileManager.cleanFileManagerObject(); + FileManager.initForTest( + dependentModuleMap, + undefined, + dynamicSDKPath, + staticSDKDeclPath, + staticSDKGlueCodePath); + }); + + mocha.after(() => { + FileManager.cleanFileManagerObject(); + }); + + mocha.it('1-1: test SDK path', function() { + const filePath = '/sdk/default/openharmony/ets/ets1.1/api/TestAPI.ets'; + const result = FileManager.getInstance().getLanguageVersionByFilePath(filePath); + expect(result?.languageVersion).to.equal(ARKTS_1_1); + expect(result?.pkgName).to.equal('SDK'); + }); + + mocha.it('1-2: test ets-loader/declarations path', function() { + const filePath = '/sdk/default/openharmony/ets/ets1.1/build-tools/ets-loader/declarations/TestAPI.ets'; + const result = FileManager.getInstance().getLanguageVersionByFilePath(filePath); + expect(result?.languageVersion).to.equal(ARKTS_1_1); + expect(result?.pkgName).to.equal('SDK'); + }); + + mocha.it('1-3: test ets-loader/component path', function() { + const filePath = '/sdk/default/openharmony/ets/ets1.1/build-tools/ets-loader/component/TestAPI.d.ts'; + const result = FileManager.getInstance().getLanguageVersionByFilePath(filePath); + expect(result?.languageVersion).to.equal(ARKTS_1_1); + expect(result?.pkgName).to.equal('SDK'); + }); + + mocha.it('1-4: test SDK glue code path', function() { + const filePath = '/sdk/default/openharmony/ets/ets1.2interop/bridge/arkts/TestAPI.d.ts'; + const result = FileManager.getInstance().getLanguageVersionByFilePath(filePath); + expect(result?.languageVersion).to.equal(ARKTS_1_2); + expect(result?.pkgName).to.equal('SDK'); + }); + + mocha.it('1-5: test SDK interop decl path', function() { + const filePath = '/sdk/default/openharmony/ets/ets1.2interop/declarations/kit/TestAPI.d.ts'; + const result = FileManager.getInstance().getLanguageVersionByFilePath(filePath); + expect(result?.languageVersion).to.equal(ARKTS_1_2); + expect(result?.pkgName).to.equal('SDK'); + }); + + mocha.it('1-6: test source code from 1.1 module', function() { + const filePath = '/MyApplication16/application/sourceCode.ets'; + const result = FileManager.getInstance().getLanguageVersionByFilePath(filePath); + expect(result?.languageVersion).to.equal(ARKTS_1_1); + expect(result?.pkgName).to.equal('application'); + }); + + mocha.it('1-7: test glue code file from 1.2 module', function() { + const filePath = '/MyApplication16/harv2/build/default/intermediates/declgen/default/declgenBridgeCode/sourceCode.ets'; + const result = FileManager.getInstance().getLanguageVersionByFilePath(filePath); + expect(result?.languageVersion).to.equal(ARKTS_1_2); + expect(result?.pkgName).to.equal('harv2'); + }); + + mocha.it('1-8: test decl file from 1.2 module', function() { + const filePath = '/MyApplication16/harv2/build/default/intermediates/declgen/default/declgenV1/sourceCode.ets'; + const result = FileManager.getInstance().getLanguageVersionByFilePath(filePath); + expect(result?.languageVersion).to.equal(ARKTS_1_2); + expect(result?.pkgName).to.equal('harv2'); + }); + + mocha.it('1-9: test source code file from hybrid module', function() { + const filePath = '/MyApplication16/hybrid/fileV1.ets'; + const result = FileManager.getInstance().getLanguageVersionByFilePath(filePath); + expect(result?.languageVersion).to.equal(ARKTS_1_1); + expect(result?.pkgName).to.equal('hybrid'); + }); + + mocha.it('1-10: test source code file from hybrid module', function() { + const filePath = '/MyApplication16/hybrid/build/default/intermediates/declgen/default/declgenV1/file1'; + const result = FileManager.getInstance().getLanguageVersionByFilePath(filePath); + expect(result?.languageVersion).to.equal(ARKTS_1_2); + expect(result?.pkgName).to.equal('hybrid'); + }); + + mocha.it('2-1: test matchModulePath api with 1.1 module', function() { + const filePath = '/MyApplication16/dynamic1/sourceCode.ets'; + const moduleInfo = FileManager.matchModulePath(filePath); + expect(moduleInfo.languageVersion).to.equal(ARKTS_1_1); + expect(moduleInfo.pkgName).to.equal('dynamic1'); + }) + + mocha.it('2-2: test matchModulePath api with 1.2 module', function() { + const filePath = '/MyApplication16/harv2/sourceCode.ets'; + const moduleInfo = FileManager.matchModulePath(filePath); + expect(moduleInfo.languageVersion).to.equal(ARKTS_1_2); + expect(moduleInfo.pkgName).to.equal('harv2'); + }) + + mocha.it('2-3: test matchModulePath api with isHybrid module', function() { + const dymanicFilePath = '/MyApplication16/hybrid/fileV1.ets'; + const staticFilePath = '/MyApplication16/hybrid/fileV2.ets'; + + const moduleInfoV1 = FileManager.matchModulePath(dymanicFilePath); + expect(moduleInfoV1.languageVersion).to.equal(ARKTS_1_1); + expect(moduleInfoV1.pkgName).to.equal('hybrid'); + + const moduleInfoV2 = FileManager.matchModulePath(staticFilePath); + expect(moduleInfoV2.languageVersion).to.equal(ARKTS_1_2); + expect(moduleInfoV2.pkgName).to.equal('hybrid'); + }) + + mocha.it('3-1: test init SDK', function () { + const share = { + projectConfig: { + etsLoaderPath: '/mock/ets-loader', + } + }; + + const result = collectSDKInfo(share); + const expectedDynamicSDKPath = new Set([ + '/mock/ets-loader/declarations', + '/mock/ets-loader/components', + '/component', + '/mock/ets-loader' + ]); + sdkConfigs.forEach(({ apiPath }) => { + apiPath.forEach(path => { + expectedDynamicSDKPath.add(toUnixPath(path)); + }); + }); + const expectedStaticInteropDecl = new Set([ + '/ets1.2/build-tools/interop/declarations/kits', + '/ets1.2/build-tools/interop/declarations/api', + '/ets1.2/build-tools/interop/declarations/arkts' + ]); + + const expectedStaticGlueCode = new Set([ + '/ets1.2/build-tools/interop/bridge/kits', + '/ets1.2/build-tools/interop/bridge/api', + '/ets1.2/build-tools/interop/bridge/arkts' + ]); + expect([...result.dynamicSDKPath]).to.have.deep.members([...expectedDynamicSDKPath]); + expect([...result.staticSDKInteropDecl]).to.have.deep.members([...expectedStaticInteropDecl]); + expect([...result.staticSDKGlueCodePath]).to.have.deep.members([...expectedStaticGlueCode]); + }); +}); + +mocha.describe('isBridgeCode', function () { + const mockConfig = { + mixCompile: true, + dependentModuleMap: new Map([ + ['pkgA', { declgenBridgeCodePath: path.resolve('project/bridge/pkgA') }], + ['pkgB', { declgenBridgeCodePath: path.resolve('project/bridge/pkgB') }], + ]), + }; + + mocha.it('1-1: should return true when filePath is inside a declgenBridgeCodePath', function () { + const filePath = path.resolve('project/bridge/pkgA/utils/helper.ts'); + expect(isBridgeCode(filePath, mockConfig)).to.be.true; + }); + + mocha.it('1-2: should return false when filePath is outside all bridge code paths', function () { + const filePath = path.resolve('project/otherpkg/index.ts'); + expect(isBridgeCode(filePath, mockConfig)).to.be.false; + }); + + mocha.it('1-3: should return false when mixCompile is false', function () { + const config = { ...mockConfig, mixCompile: false }; + const filePath = path.resolve('project/bridge/pkgA/utils/helper.ts'); + expect(isBridgeCode(filePath, config)).to.be.false; + }); + + mocha.it('1-4: should return false when dependentModuleMap is empty', function () { + const config = { mixCompile: true, dependentModuleMap: new Map() }; + const filePath = path.resolve('project/bridge/pkgA/file.ts'); + expect(isBridgeCode(filePath, config)).to.be.false; + }); + + mocha.it('1-5: should return true for multiple matches, stop at first match', function () { + const config = { + mixCompile: true, + dependentModuleMap: new Map([ + ['pkg1', { declgenBridgeCodePath: path.resolve('path/one') }], + ['pkg2', { declgenBridgeCodePath: path.resolve('path/two') }], + ]), + }; + const filePath = path.resolve('path/one/module.ts'); + expect(isBridgeCode(filePath, config)).to.be.true; + }); +}); + +mocha.describe('test getBrdigeCodeRootPath api', function () { + mocha.it('1-1: should return bridgeCodePath from interopConfig when filePath matches moduleRootPath', function () { + const filePath = '/a/b/c/file.ts'; + const mockConfig: InteropConfig = { + mixCompile: false, + interopModuleInfo: new Map([ + ['/a/b', { + declgenBridgeCodePath: '/bridge/a/b', + declgenV1OutPath: '/v1' + }] + ]) + }; + + const result = getBrdigeCodeRootPath(filePath, mockConfig); + expect(result).to.equal('/bridge/a/b'); + }); + + mocha.it('1-2: should return undefined when filePath does not match any moduleRootPath', function () { + const filePath = '/x/y/z/file.ts'; + const mockConfig: InteropConfig = { + mixCompile: false, + interopModuleInfo: new Map([ + ['/a/b', { + declgenBridgeCodePath: '/bridge/a/b', + declgenV1OutPath: '/v1' + }] + ]) + }; + + const result = getBrdigeCodeRootPath(filePath, mockConfig); + expect(result).to.be.undefined; + }); + + mocha.it('1-3: should return process.env.entryBridgeCodePath when interopConfig is null', function () { + process.env.entryBridgeCodePath = '/default/bridge/path'; + const filePath = '/any/file.ts'; + + const result = getBrdigeCodeRootPath(filePath, undefined as any); + expect(result).to.equal('/default/bridge/path'); + }); +}); + +mocha.describe('test mixCompile', function () { + mocha.it('1-1 test mixCompile is false', function () { + expect(isMixCompile()).to.be.false; + }) + + mocha.it('1-2 test mixCompile is true', function () { + this.rollup = new RollUpPluginMock(); + this.rollup.build(); + this.rollup.share.projectConfig.mixCompile = true; + initConfigForInterop(this.rollup.share); + expect(isMixCompile()).to.be.true; + }) + + mocha.it('1-3 test mixCompile is false when destroy interop', function () { + this.rollup = new RollUpPluginMock(); + this.rollup.build(); + this.rollup.share.projectConfig.mixCompile = true; + initConfigForInterop(this.rollup.share); + destroyInterop() + expect(isMixCompile()).to.be.false; + }) +}) + +mocha.describe('test transformModuleNameToRelativePath api', function () { + mocha.before(function () { + this.rollup = new RollUpPluginMock(); + this.rollup.build(); + this.rollup.share.projectConfig.mixCompile = true; + initConfigForInterop(this.rollup.share); + }); + + mocha.it('1-1 test transformModuleNameToRelativePath when compile common module', function () { + this.rollup.share.projectConfig.isOhosTest = false; + + const moduleName = '/a/b/c/src/main/ets/module/file.ets'; + const result = transformModuleNameToRelativePath(moduleName); + + expect(result).to.equal('./ets/module/file.ets'); + }); + + mocha.it('1-2 test transformModuleNameToRelativePath when compile ohostest', function () { + this.rollup.share.projectConfig.isOhosTest = true; + initConfigForInterop(this.rollup.share); + const moduleName = '/a/b/c/src/ohosTest/ets/module/testfile.ets'; + const result = transformModuleNameToRelativePath(moduleName); + + expect(result).to.equal('./ets/module/testfile.ets'); + }); + + mocha.it('1-3 test transformModuleNameToRelativePath throws when sourceRoot not in path', function () { + this.rollup.share.projectConfig.isOhosTest = false; + + const invalidModuleName = '/a/b/c/invalidroot/ets/module/file.ets'; + + expect(() => transformModuleNameToRelativePath(invalidModuleName)).to.throw(Error); + }); + + mocha.after(() => { + delete this.rollup; + }); +}) diff --git a/compiler/test/ark_compiler_ut/interop/process_arkts_evolution.test.ts b/compiler/test/ark_compiler_ut/interop/process_arkts_evolution.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..6119d6bd35800320841a97d4e14d14a13dccafa9 --- /dev/null +++ b/compiler/test/ark_compiler_ut/interop/process_arkts_evolution.test.ts @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use rollupObject file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; +import mocha from 'mocha'; +import sinon from 'sinon'; + +import { + collectArkTSEvolutionModuleInfo, + addDeclFilesConfig, + pkgDeclFilesConfig, + arkTSModuleMap +} from '../../../lib/fast_build/ark_compiler/interop/process_arkts_evolution'; +import RollUpPluginMock from '../mock/rollup_mock/rollup_plugin_mock'; +import { + BUNDLE_NAME_DEFAULT, + HAR_DECLGENV2OUTPATH +} from '../mock/rollup_mock/common'; +import { CommonLogger } from '../../../lib/fast_build/ark_compiler/logger'; +import { ErrorCode } from '../../../lib/fast_build/ark_compiler/error_code'; + +mocha.describe('process arkts evolution tests', function () { + mocha.before(function () { + this.rollup = new RollUpPluginMock(); + }); + + mocha.after(() => { + delete this.rollup; + }); + + mocha.it('1-1: test error message of collectArkTSEvolutionModuleInfo (useNormalizedOHMUrl is false)', function () { + this.rollup.build(); + this.rollup.share.projectConfig.useNormalizedOHMUrl = false; + this.rollup.share.projectConfig.dependentModuleMap.set('evohar', { language: '1.2' }); + const throwArkTsCompilerErrorStub = sinon.stub(CommonLogger.getInstance(this.rollup), 'printErrorAndExit'); + try { + collectArkTSEvolutionModuleInfo(this.rollup.share); + } catch (e) { + } + expect(throwArkTsCompilerErrorStub.getCall(0).args[0].code === ErrorCode.ETS2BUNDLE_EXTERNAL_COLLECT_INTEROP_INFO_FAILED).to.be.true; + throwArkTsCompilerErrorStub.restore(); + }); + + mocha.it('1-2: test error message of collectArkTSEvolutionModuleInfo (1.2 module information is incorrect)', function () { + this.rollup.build(); + this.rollup.share.projectConfig.useNormalizedOHMUrl = true; + this.rollup.share.projectConfig.dependentModuleMap.set('evohar', { language: '1.2' }); + const throwArkTsCompilerErrorStub = sinon.stub(this.rollup.share, 'throwArkTsCompilerError'); + try { + collectArkTSEvolutionModuleInfo(this.rollup.share); + } catch(e) { + } + const errMsg: string = 'ArkTS:INTERNAL ERROR: Failed to collect arkTs evolution module info.\n' + + `Error Message: Failed to collect arkTs evolution module "evohar" info from rollup.`; + expect(throwArkTsCompilerErrorStub.getCall(0).args[1] === errMsg).to.be.true; + throwArkTsCompilerErrorStub.restore(); + }); + + mocha.it('1-3: test error message of collectArkTSEvolutionModuleInfo (1.1 module information is incorrect)', function () { + this.rollup.build(); + this.rollup.share.projectConfig.useNormalizedOHMUrl = true; + this.rollup.share.projectConfig.dependentModuleMap.set('har', { language: '1.1' }); + const throwArkTsCompilerErrorStub = sinon.stub(this.rollup.share, 'throwArkTsCompilerError'); + try { + collectArkTSEvolutionModuleInfo(this.rollup.share); + } catch(e) { + } + const errMsg: string = 'ArkTS:INTERNAL ERROR: Failed to collect arkTs evolution module info.\n' + + `Error Message: Failed to collect arkTs evolution module "har" info from rollup.`; + expect(throwArkTsCompilerErrorStub.getCall(0).args[1] === errMsg).to.be.true; + throwArkTsCompilerErrorStub.restore(); + }); + + mocha.it('2-1: test generate declFilesInfo in mixed compilation', function () { + pkgDeclFilesConfig['har'] = { + packageName: 'har', + files: {} + }; + const filePath = '/har/Index.ets' + const projectConfig = { + mainModuleName: 'entry', + bundleName: BUNDLE_NAME_DEFAULT, + pkgContextInfo: { + 'har': { + packageName: 'har', + version: '1.0.0', + isSO: false + } + } + } + arkTSModuleMap.set('har', { + language: '1.1', + pkgName: 'har', + declgenV2OutPath: HAR_DECLGENV2OUTPATH + }) + addDeclFilesConfig(filePath, projectConfig, undefined, '/har', 'har'); + const expectDeclPath: string = `${HAR_DECLGENV2OUTPATH}/Index.d.ets`; + const expectOhmUrl: string = `@normalized:N&entry&${BUNDLE_NAME_DEFAULT}&har/Index&1.0.0`; + expect(pkgDeclFilesConfig['har'].files.length !== 0).to.be.true; + expect(pkgDeclFilesConfig['har'].files['Index'].length !== 0).to.be.true; + expect(pkgDeclFilesConfig['har'].files['Index'].declPath === expectDeclPath).to.be.true; + expect(pkgDeclFilesConfig['har'].files['Index'].ohmUrl === expectOhmUrl).to.be.true; + arkTSModuleMap.clear(); + }); +}); \ No newline at end of file