arkanalyzer 1.0.90 → 1.0.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/arkanalyzer.json +0 -1
- package/lib/Config.d.ts +3 -0
- package/lib/Config.d.ts.map +1 -1
- package/lib/Scene.d.ts +99 -0
- package/lib/Scene.d.ts.map +1 -1
- package/lib/Scene.js +229 -13
- package/lib/callgraph/common/Statistics.js +2 -2
- package/lib/callgraph/model/CallGraph.d.ts +2 -2
- package/lib/callgraph/model/CallGraph.d.ts.map +1 -1
- package/lib/callgraph/model/CallGraph.js +6 -6
- package/lib/callgraph/model/CallSite.d.ts +1 -1
- package/lib/callgraph/model/CallSite.d.ts.map +1 -1
- package/lib/callgraph/model/CallSite.js +2 -2
- package/lib/core/base/Ref.d.ts.map +1 -1
- package/lib/core/base/Ref.js +6 -1
- package/lib/core/common/ArkIRTransformer.d.ts.map +1 -1
- package/lib/core/common/ArkIRTransformer.js +5 -0
- package/lib/core/common/ArkValueTransformer.d.ts.map +1 -1
- package/lib/core/common/ArkValueTransformer.js +2 -1
- package/lib/core/common/Const.d.ts +3 -0
- package/lib/core/common/Const.d.ts.map +1 -1
- package/lib/core/common/Const.js +5 -1
- package/lib/core/common/DummyMainCreater.d.ts +25 -12
- package/lib/core/common/DummyMainCreater.d.ts.map +1 -1
- package/lib/core/common/DummyMainCreater.js +90 -76
- package/lib/core/common/EtsConst.d.ts +2 -0
- package/lib/core/common/EtsConst.d.ts.map +1 -1
- package/lib/core/common/EtsConst.js +133 -125
- package/lib/core/common/SdkUtils.d.ts +1 -0
- package/lib/core/common/SdkUtils.d.ts.map +1 -1
- package/lib/core/common/SdkUtils.js +29 -0
- package/lib/core/graph/BaseExplicitGraph.d.ts +2 -0
- package/lib/core/graph/BaseExplicitGraph.d.ts.map +1 -1
- package/lib/core/graph/BaseExplicitGraph.js +12 -1
- package/lib/core/graph/BaseImplicitGraph.d.ts +16 -0
- package/lib/core/graph/BaseImplicitGraph.d.ts.map +1 -1
- package/lib/core/graph/BaseImplicitGraph.js +21 -0
- package/lib/core/graph/FileDepGraph.d.ts +75 -0
- package/lib/core/graph/FileDepGraph.d.ts.map +1 -0
- package/lib/core/graph/FileDepGraph.js +170 -0
- package/lib/core/graph/GraphTraits.d.ts +2 -0
- package/lib/core/graph/GraphTraits.d.ts.map +1 -1
- package/lib/core/graph/GraphTraits.js +1 -1
- package/lib/core/graph/ModuleDepGraph.d.ts +175 -0
- package/lib/core/graph/ModuleDepGraph.d.ts.map +1 -0
- package/lib/core/graph/ModuleDepGraph.js +421 -0
- package/lib/core/graph/Scc.d.ts +1 -3
- package/lib/core/graph/Scc.d.ts.map +1 -1
- package/lib/core/graph/Scc.js +6 -19
- package/lib/core/graph/builder/CfgBuilder.d.ts.map +1 -1
- package/lib/core/graph/builder/CfgBuilder.js +7 -1
- package/lib/core/graph/builder/TrapBuilder.d.ts +1 -0
- package/lib/core/graph/builder/TrapBuilder.d.ts.map +1 -1
- package/lib/core/graph/builder/TrapBuilder.js +27 -7
- package/lib/core/model/ArkBody.d.ts +1 -0
- package/lib/core/model/ArkBody.d.ts.map +1 -1
- package/lib/core/model/ArkBody.js +7 -0
- package/lib/core/model/ArkClass.d.ts +1 -0
- package/lib/core/model/ArkClass.d.ts.map +1 -1
- package/lib/core/model/ArkClass.js +37 -0
- package/lib/core/model/ArkExport.d.ts +1 -0
- package/lib/core/model/ArkExport.d.ts.map +1 -1
- package/lib/core/model/ArkExport.js +6 -0
- package/lib/core/model/ArkField.d.ts +1 -0
- package/lib/core/model/ArkField.d.ts.map +1 -1
- package/lib/core/model/ArkField.js +5 -0
- package/lib/core/model/ArkFile.d.ts +5 -0
- package/lib/core/model/ArkFile.d.ts.map +1 -1
- package/lib/core/model/ArkFile.js +30 -0
- package/lib/core/model/ArkImport.d.ts +1 -0
- package/lib/core/model/ArkImport.d.ts.map +1 -1
- package/lib/core/model/ArkImport.js +5 -0
- package/lib/core/model/ArkMethod.d.ts +8 -0
- package/lib/core/model/ArkMethod.d.ts.map +1 -1
- package/lib/core/model/ArkMethod.js +22 -0
- package/lib/core/model/ArkModule.d.ts +136 -0
- package/lib/core/model/ArkModule.d.ts.map +1 -0
- package/lib/core/model/ArkModule.js +250 -0
- package/lib/core/model/ArkNamespace.d.ts +1 -0
- package/lib/core/model/ArkNamespace.d.ts.map +1 -1
- package/lib/core/model/ArkNamespace.js +18 -0
- package/lib/core/model/builder/ArkFileBuilder.d.ts +19 -0
- package/lib/core/model/builder/ArkFileBuilder.d.ts.map +1 -1
- package/lib/core/model/builder/ArkFileBuilder.js +155 -51
- package/lib/frontend/FrontendBuilder.d.ts +116 -0
- package/lib/frontend/FrontendBuilder.d.ts.map +1 -1
- package/lib/frontend/FrontendBuilder.js +256 -0
- package/lib/frontend/arktsFrontend/ArktsFrontend.d.ts +11 -0
- package/lib/frontend/arktsFrontend/ArktsFrontend.d.ts.map +1 -1
- package/lib/frontend/arktsFrontend/ArktsFrontend.js +15 -0
- package/lib/frontend/common/MemoryMonitor.d.ts +62 -0
- package/lib/frontend/common/MemoryMonitor.d.ts.map +1 -0
- package/lib/frontend/common/MemoryMonitor.js +108 -0
- package/lib/frontend/common/ModuleAnalysisConfig.d.ts +92 -0
- package/lib/frontend/common/ModuleAnalysisConfig.d.ts.map +1 -0
- package/lib/frontend/common/ModuleAnalysisConfig.js +144 -0
- package/lib/frontend/common/ModuleBuilder.d.ts +497 -0
- package/lib/frontend/common/ModuleBuilder.d.ts.map +1 -0
- package/lib/frontend/common/ModuleBuilder.js +1458 -0
- package/lib/frontend/common/ModuleCache.d.ts +119 -0
- package/lib/frontend/common/ModuleCache.d.ts.map +1 -0
- package/lib/frontend/common/ModuleCache.js +197 -0
- package/lib/frontend/common/ModuleDepth.d.ts +28 -0
- package/lib/frontend/common/ModuleDepth.d.ts.map +1 -0
- package/lib/frontend/common/ModuleDepth.js +45 -0
- package/lib/frontend/cppFrontend/CppFrontend.d.ts +5 -0
- package/lib/frontend/cppFrontend/CppFrontend.d.ts.map +1 -1
- package/lib/frontend/cppFrontend/CppFrontend.js +11 -1
- package/lib/frontend/cppFrontend/model/builder/ArkFileBuilder.d.ts +8 -0
- package/lib/frontend/cppFrontend/model/builder/ArkFileBuilder.d.ts.map +1 -1
- package/lib/frontend/cppFrontend/model/builder/ArkFileBuilder.js +105 -2
- package/lib/index.d.ts +9 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +22 -3
- package/lib/utils/Canonicalizer.d.ts +105 -0
- package/lib/utils/Canonicalizer.d.ts.map +1 -0
- package/lib/utils/Canonicalizer.js +520 -0
- package/lib/utils/ModuleUtils.js +4 -2
- package/lib/utils/entryMethodUtils.d.ts +23 -0
- package/lib/utils/entryMethodUtils.d.ts.map +1 -1
- package/lib/utils/entryMethodUtils.js +53 -1
- package/package.json +1 -1
|
@@ -0,0 +1,1458 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Huawei Device Co., Ltd.
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.ModuleBuilder = void 0;
|
|
44
|
+
const fs_1 = __importDefault(require("fs"));
|
|
45
|
+
const path_1 = __importDefault(require("path"));
|
|
46
|
+
const SparseBitVector_1 = require("../../utils/SparseBitVector");
|
|
47
|
+
const Canonicalizer_1 = require("../../utils/Canonicalizer");
|
|
48
|
+
const ArkModule_1 = require("../../core/model/ArkModule");
|
|
49
|
+
const ModuleDepGraph_1 = require("../../core/graph/ModuleDepGraph");
|
|
50
|
+
const FileDepGraph_1 = require("../../core/graph/FileDepGraph");
|
|
51
|
+
const EtsConst_1 = require("../../core/common/EtsConst");
|
|
52
|
+
const Const_1 = require("../../core/common/Const");
|
|
53
|
+
const json5parser_1 = require("../../utils/json5parser");
|
|
54
|
+
const logger_1 = __importStar(require("../../utils/logger"));
|
|
55
|
+
const ModuleDepth_1 = require("./ModuleDepth");
|
|
56
|
+
const MemoryMonitor_1 = require("./MemoryMonitor");
|
|
57
|
+
const ModuleCache_1 = require("./ModuleCache");
|
|
58
|
+
const FrontendBuilder_1 = require("../FrontendBuilder");
|
|
59
|
+
const ArkFile_1 = require("../../core/model/ArkFile");
|
|
60
|
+
const Inference_1 = require("../../core/inference/Inference");
|
|
61
|
+
const FileUtils_1 = require("../../utils/FileUtils");
|
|
62
|
+
const getAllFiles_1 = require("../../utils/getAllFiles");
|
|
63
|
+
const SdkUtils_1 = require("../../core/common/SdkUtils");
|
|
64
|
+
const ArktsFrontend_1 = require("../arktsFrontend/ArktsFrontend");
|
|
65
|
+
const ModelUtils_1 = require("../../core/common/ModelUtils");
|
|
66
|
+
const ModuleUtils_1 = require("../../utils/ModuleUtils");
|
|
67
|
+
const Scene_1 = require("../../Scene");
|
|
68
|
+
const logger = logger_1.default.getLogger(logger_1.LOG_MODULE_TYPE.ARKANALYZER, 'ModuleBuilder');
|
|
69
|
+
/**
|
|
70
|
+
* ModuleBuilder holds the build logic for module-level analysis embedded in {@link Scene}.
|
|
71
|
+
*
|
|
72
|
+
* It is responsible for the build side of the module lifecycle: registration, SDK build, module
|
|
73
|
+
* preparation, dependency graph construction and SCC analysis, and module loading. Persistent
|
|
74
|
+
* state (the Canonicalizer, pathToId map, dependency graph, and progress flags) lives on the
|
|
75
|
+
* owning {@link Scene}; this class reads and writes that state through the Scene accessors.
|
|
76
|
+
*
|
|
77
|
+
* Each module is identified by its absolute path and assigned a dense integer {@link ModuleID} via
|
|
78
|
+
* the Scene's Canonicalizer using the objectIdentity strategy. The `pathToId` map on the Scene is
|
|
79
|
+
* a persistent cache that survives graph construction, allowing runtime path-based lookups.
|
|
80
|
+
*
|
|
81
|
+
* @category core/model
|
|
82
|
+
*/
|
|
83
|
+
class ModuleBuilder {
|
|
84
|
+
constructor(scene) {
|
|
85
|
+
/** Persistent map from absolute module path to ModuleID (survives graph construction). */
|
|
86
|
+
this.pathToId = new Map();
|
|
87
|
+
/** Topological order of the current call, used for protected-set computation and Phase 2/3 reverse-topo iteration. */
|
|
88
|
+
this.topoOrder = [];
|
|
89
|
+
/** Load level to which protected dependency modules are downgraded during eviction Phase 2. */
|
|
90
|
+
this.dependencyLoadLevel = ModuleDepth_1.ModuleDepthLevel.SIGNATURES;
|
|
91
|
+
this.scene = scene;
|
|
92
|
+
// Initialize pathToId from existing modules (supports idempotent calls)
|
|
93
|
+
for (const module of scene.getModules()) {
|
|
94
|
+
this.pathToId.set(module.getModulePath(), scene.getModuleId(module));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// --- Module query methods (moved from Scene) ---
|
|
98
|
+
/** Get a registered module by its ModuleID. Returns undefined when the id is out of bounds. */
|
|
99
|
+
getModule(id) {
|
|
100
|
+
return this.scene.getModule(id);
|
|
101
|
+
}
|
|
102
|
+
/** Look up a module by its absolute path using the pathToId cache. */
|
|
103
|
+
getModuleByPath(modulePath) {
|
|
104
|
+
const id = this.pathToId.get(modulePath);
|
|
105
|
+
if (id === undefined) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
return this.scene.getModule(id);
|
|
109
|
+
}
|
|
110
|
+
/** Total number of registered modules. */
|
|
111
|
+
getModuleCount() {
|
|
112
|
+
return this.scene.getModuleCount();
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Iterate over all registered modules.
|
|
116
|
+
* Iteration order follows ModuleID assignment order (0, 1, 2, ...).
|
|
117
|
+
*/
|
|
118
|
+
modulesIterator() {
|
|
119
|
+
const cursor = { value: 0 };
|
|
120
|
+
const iterator = {
|
|
121
|
+
next: () => this.nextModule(cursor),
|
|
122
|
+
[Symbol.iterator]() {
|
|
123
|
+
return this;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
return iterator;
|
|
127
|
+
}
|
|
128
|
+
/** Find the next module starting from the cursor. */
|
|
129
|
+
nextModule(cursor) {
|
|
130
|
+
while (cursor.value < this.scene.getModuleCount()) {
|
|
131
|
+
const m = this.scene.getModule(cursor.value);
|
|
132
|
+
cursor.value++;
|
|
133
|
+
if (m) {
|
|
134
|
+
return { value: m, done: false };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return { value: undefined, done: true };
|
|
138
|
+
}
|
|
139
|
+
/** Topologically sorted module IDs (empty before dependency analysis completes). */
|
|
140
|
+
getTopoOrder() {
|
|
141
|
+
const graph = this.scene.getModuleDepGraph();
|
|
142
|
+
return graph ? graph.getTopoOrder() : [];
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Resolve a dependency alias within the scope of the given module.
|
|
146
|
+
* Looks up the alias in the module's per-module alias map, then resolves the
|
|
147
|
+
* resulting ModuleID back to the depended-on ArkModule.
|
|
148
|
+
*/
|
|
149
|
+
resolveAlias(moduleId, alias) {
|
|
150
|
+
const module = this.getModule(moduleId);
|
|
151
|
+
if (!module) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
const depId = module.resolveDependencyAlias(alias);
|
|
155
|
+
if (depId === undefined) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
return this.getModule(depId);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Resolve the final set of target module IDs from the config's three selection dimensions.
|
|
162
|
+
* Excluded IDs take precedence; type filter and explicit include IDs are unioned.
|
|
163
|
+
*/
|
|
164
|
+
resolveTargetModuleIds(config) {
|
|
165
|
+
const targets = new SparseBitVector_1.SparseBitVector();
|
|
166
|
+
const excluded = config.getExcludedModuleIds();
|
|
167
|
+
const included = config.getTargetModuleIds();
|
|
168
|
+
for (const module of this.modulesIterator()) {
|
|
169
|
+
const id = this.scene.getModuleId(module);
|
|
170
|
+
if (excluded.test(id)) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (config.isTypeIncluded(module.getModuleType()) || included.test(id)) {
|
|
174
|
+
targets.set(id);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return targets;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Compute transitive closure of module IDs reachable from the given target IDs via
|
|
181
|
+
* dependency edges (BFS over {@link ModuleDepGraph.getSuccModuleIds}).
|
|
182
|
+
* SDK modules are never included in the closure.
|
|
183
|
+
*/
|
|
184
|
+
computeModuleClosureByIds(targetIds) {
|
|
185
|
+
const closure = new SparseBitVector_1.SparseBitVector();
|
|
186
|
+
const queue = [];
|
|
187
|
+
for (const id of targetIds) {
|
|
188
|
+
const module = this.scene.getModule(id);
|
|
189
|
+
if (!module || module.getModuleType() === ArkModule_1.ModuleType.SDK) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (!closure.test(id)) {
|
|
193
|
+
closure.set(id);
|
|
194
|
+
queue.push(id);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
while (queue.length > 0) {
|
|
198
|
+
const currentId = queue.shift();
|
|
199
|
+
const graph = this.scene.getModuleDepGraph();
|
|
200
|
+
const succIds = graph ? graph.getSuccModuleIds(currentId) : [];
|
|
201
|
+
for (const depId of succIds) {
|
|
202
|
+
if (closure.test(depId)) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const depModule = this.scene.getModule(depId);
|
|
206
|
+
if (!depModule || depModule.getModuleType() === ArkModule_1.ModuleType.SDK) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
closure.set(depId);
|
|
210
|
+
queue.push(depId);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return closure;
|
|
214
|
+
}
|
|
215
|
+
/** Filter the topological order to only include module IDs present in the given closure. */
|
|
216
|
+
getFilteredTopoOrder(closure) {
|
|
217
|
+
const graph = this.scene.getModuleDepGraph();
|
|
218
|
+
const topo = graph ? graph.getTopoOrder() : [];
|
|
219
|
+
return topo.filter(id => closure.test(id));
|
|
220
|
+
}
|
|
221
|
+
/** Get the ModuleID assigned to a registered ArkModule. */
|
|
222
|
+
getModuleId(module) {
|
|
223
|
+
return this.scene.getModuleId(module);
|
|
224
|
+
}
|
|
225
|
+
/** Create a {@link ModuleDepGraph} sharing the scene's module canonicalizer. */
|
|
226
|
+
createModuleDepGraph() {
|
|
227
|
+
return this.scene.createModuleDepGraph();
|
|
228
|
+
}
|
|
229
|
+
// --- Module registration ---
|
|
230
|
+
/**
|
|
231
|
+
* Register a module by its absolute path. If the same path has already been registered, the
|
|
232
|
+
* existing {@link ArkModule} is returned unchanged. A new ModuleID is allocated via the
|
|
233
|
+
* Scene's Canonicalizer for first-time registrations, and cached on the module.
|
|
234
|
+
*
|
|
235
|
+
* @param modulePath - Absolute path of the module (primary identifier).
|
|
236
|
+
* @param moduleName - Optional module name (auxiliary field, e.g. "@ohos/entry").
|
|
237
|
+
* @returns The registered ArkModule (newly created or previously registered).
|
|
238
|
+
*/
|
|
239
|
+
registerModule(modulePath, moduleName = '') {
|
|
240
|
+
const existingId = this.pathToId.get(modulePath);
|
|
241
|
+
if (existingId !== undefined) {
|
|
242
|
+
return this.scene.getModule(existingId);
|
|
243
|
+
}
|
|
244
|
+
const module = new ArkModule_1.ArkModule(this.scene);
|
|
245
|
+
module.setModulePath(modulePath);
|
|
246
|
+
module.setModuleName(moduleName);
|
|
247
|
+
const id = this.scene.getModuleId(module);
|
|
248
|
+
this.pathToId.set(modulePath, id);
|
|
249
|
+
return module;
|
|
250
|
+
}
|
|
251
|
+
// --- SDK and module preparation ---
|
|
252
|
+
/**
|
|
253
|
+
* Register and build SDK modules in one fused step. Each project-level SDK is treated as an
|
|
254
|
+
* {@link ArkModule} (moduleType=SDK): its files are collected, parsed, and registered into the
|
|
255
|
+
* module's `filesMap` as well as the scene's `sdkArkFilesMap`. SDK type inference and global
|
|
256
|
+
* API merge run after all SDK files are built.
|
|
257
|
+
*
|
|
258
|
+
* This method merges the former `prepareSdkModules` (registration-only) and `SDKBuilder.buildSdks`
|
|
259
|
+
* (building-only) into a single step — SDK modules are never just registered without being built.
|
|
260
|
+
*
|
|
261
|
+
* Module-level SDKs (those with `moduleName` set) are skipped entirely.
|
|
262
|
+
*
|
|
263
|
+
* Idempotent: guarded by {@link SceneBuildStage.SDK_INFERRED}.
|
|
264
|
+
*/
|
|
265
|
+
buildSdkModules() {
|
|
266
|
+
if (this.scene.getBuildStage() >= Scene_1.SceneBuildStage.SDK_INFERRED) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
this.setEsVersionFromBuildProfile();
|
|
270
|
+
const sceneConfig = this.scene.getSceneConfig();
|
|
271
|
+
if (!sceneConfig) {
|
|
272
|
+
logger.warn('SceneConfig is not set; skip SDK build.');
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const sdks = sceneConfig.getSdksObj();
|
|
276
|
+
const options = this.scene.getOptions();
|
|
277
|
+
// Handle enableBuiltIn: prepend built-in SDK if not already present
|
|
278
|
+
if (options.enableBuiltIn && !sdks.find(sdk => sdk.name === SdkUtils_1.SdkUtils.BUILT_IN_NAME)) {
|
|
279
|
+
sdks.unshift(SdkUtils_1.SdkUtils.getBuiltInSdk());
|
|
280
|
+
}
|
|
281
|
+
// Register and build each SDK as a module (fused registration + building).
|
|
282
|
+
// moduleName is ignored — all SDKs are treated as modules regardless.
|
|
283
|
+
for (const sdk of sdks) {
|
|
284
|
+
const sdkPath = path_1.default.normalize(sdk.path);
|
|
285
|
+
const module = this.registerModule(sdkPath, sdk.name);
|
|
286
|
+
module.setModuleType(ArkModule_1.ModuleType.SDK);
|
|
287
|
+
this.buildSdkModuleFiles(module, sdk.name, sdkPath);
|
|
288
|
+
this.scene.getProjectSdkMap().set(sdk.name, sdk);
|
|
289
|
+
}
|
|
290
|
+
// SDK type inference + global API merge
|
|
291
|
+
const sdkArkFiles = this.scene.getSdkArkFiles();
|
|
292
|
+
const sdkGlobalMap = this.scene.getSdkGlobalMap();
|
|
293
|
+
for (const file of sdkArkFiles) {
|
|
294
|
+
if (file.getLanguage() === ArkFile_1.Language.CXX) {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
Inference_1.InferenceManager.getInstance().getInference(file.getLanguage()).doInfer(file);
|
|
298
|
+
SdkUtils_1.SdkUtils.mergeGlobalAPI(file, sdkGlobalMap);
|
|
299
|
+
}
|
|
300
|
+
for (const file of sdkArkFiles) {
|
|
301
|
+
SdkUtils_1.SdkUtils.postInferredSdk(file, sdkGlobalMap);
|
|
302
|
+
}
|
|
303
|
+
this.scene.setBuildStage(Scene_1.SceneBuildStage.SDK_INFERRED);
|
|
304
|
+
SdkUtils_1.SdkUtils.extendArkUI(this.scene);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Collect and build all source files for a single SDK module. Each file is parsed via
|
|
308
|
+
* {@link ArktsFrontend.buildArkFileFromSdkPath} (using the SDK name as projectName), default
|
|
309
|
+
* method bodies are built and all BodyBuilders freed, then the file is registered into both
|
|
310
|
+
* the module's `filesMap` and the scene's `sdkArkFilesMap`. C++ SDK files are skipped.
|
|
311
|
+
*/
|
|
312
|
+
buildSdkModuleFiles(module, sdkName, sdkPath) {
|
|
313
|
+
const allFiles = this.collectSdkFiles(sdkName, sdkPath);
|
|
314
|
+
for (const file of allFiles) {
|
|
315
|
+
if (FileUtils_1.FileUtils.getFileLanguage(file, this.scene.getFileLanguages()) === ArkFile_1.Language.CXX) {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
this.parseAndRegisterSdkFile(module, file, sdkPath, sdkName);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Collect source file paths for an SDK.
|
|
323
|
+
*
|
|
324
|
+
* Built-in SDK uses {@link SdkUtils.fetchBuiltInFiles} (reference-based DFS over `lib.*.d.ts`);
|
|
325
|
+
* other SDKs scan the directory with the scene's supported extensions and ignore patterns.
|
|
326
|
+
*/
|
|
327
|
+
collectSdkFiles(sdkName, sdkPath) {
|
|
328
|
+
var _a, _b, _c;
|
|
329
|
+
if (sdkName === SdkUtils_1.SdkUtils.BUILT_IN_NAME) {
|
|
330
|
+
const builtInFiles = SdkUtils_1.SdkUtils.fetchBuiltInFiles(sdkPath);
|
|
331
|
+
if (builtInFiles.length > 0) {
|
|
332
|
+
(_a = this.scene.getOptions().sdkGlobalFolders) === null || _a === void 0 ? void 0 : _a.push(sdkPath);
|
|
333
|
+
}
|
|
334
|
+
return builtInFiles;
|
|
335
|
+
}
|
|
336
|
+
SdkUtils_1.SdkUtils.loadSystemComponentsFromSdk(sdkPath);
|
|
337
|
+
const options = this.scene.getOptions();
|
|
338
|
+
const supportFileExts = (_b = options.supportFileExts) !== null && _b !== void 0 ? _b : ['.ets', '.ts'];
|
|
339
|
+
const ignoreFileNames = (_c = options.ignoreFileNames) !== null && _c !== void 0 ? _c : [];
|
|
340
|
+
return (0, getAllFiles_1.getAllFiles)(sdkPath, supportFileExts, ignoreFileNames);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Parse a single SDK source file into an ArkFile, build default method bodies, free all
|
|
344
|
+
* BodyBuilders, and register the file in both the module's `filesMap` and the scene's
|
|
345
|
+
* `sdkArkFilesMap`.
|
|
346
|
+
*/
|
|
347
|
+
parseAndRegisterSdkFile(module, file, sdkPath, sdkName) {
|
|
348
|
+
logger.trace('=== parse sdk file:', file);
|
|
349
|
+
try {
|
|
350
|
+
const arkFile = new ArkFile_1.ArkFile(FileUtils_1.FileUtils.getFileLanguage(file, this.scene.getFileLanguages()));
|
|
351
|
+
arkFile.setScene(this.scene);
|
|
352
|
+
ArktsFrontend_1.ArktsFrontend.buildArkFileFromSdkPath(file, sdkPath, arkFile, sdkName);
|
|
353
|
+
ModelUtils_1.ModelUtils.getAllClassesInFile(arkFile).forEach(cls => {
|
|
354
|
+
var _a, _b;
|
|
355
|
+
(_a = cls.getDefaultArkMethod()) === null || _a === void 0 ? void 0 : _a.buildBody();
|
|
356
|
+
(_b = cls.getDefaultArkMethod()) === null || _b === void 0 ? void 0 : _b.freeBodyBuilder();
|
|
357
|
+
});
|
|
358
|
+
// Release all method builders in SDK file to avoid retaining AST/build context in memory.
|
|
359
|
+
ModelUtils_1.ModelUtils.getAllMethodsInFile(arkFile).forEach(method => {
|
|
360
|
+
var _a;
|
|
361
|
+
if (((_a = method.getDeclaringArkFile()) === null || _a === void 0 ? void 0 : _a.getLanguage()) === ArkFile_1.Language.CXX) {
|
|
362
|
+
method.freeCxxBodyBuilder();
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
method.freeBodyBuilder();
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
module.addFile(arkFile);
|
|
369
|
+
this.scene.addSdkArkFile(arkFile);
|
|
370
|
+
SdkUtils_1.SdkUtils.buildSdkImportMap(arkFile);
|
|
371
|
+
SdkUtils_1.SdkUtils.loadGlobalAPI(arkFile, this.scene.getSdkGlobalMap());
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
logger.error('Error parsing file:', file, error);
|
|
375
|
+
this.scene.getUnhandledSdkFilePaths().push(file);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Read `build-profile.json5` from the project root and call
|
|
380
|
+
* {@link SdkUtils.setEsVersion} so that {@link SdkUtils.fetchBuiltInFiles} selects the correct
|
|
381
|
+
* `lib.*.d.ts` entry.
|
|
382
|
+
*/
|
|
383
|
+
setEsVersionFromBuildProfile() {
|
|
384
|
+
const buildProfilePath = path_1.default.join(this.scene.getRealProjectDir(), EtsConst_1.BUILD_PROFILE_JSON5);
|
|
385
|
+
if (!fs_1.default.existsSync(buildProfilePath)) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
let configurationsText;
|
|
389
|
+
try {
|
|
390
|
+
configurationsText = fs_1.default.readFileSync(buildProfilePath, 'utf-8');
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
logger.error(`Error reading build-profile.json5: ${error}`);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const buildProfileJson = (0, json5parser_1.parseJsonText)(configurationsText);
|
|
397
|
+
SdkUtils_1.SdkUtils.setEsVersion(buildProfileJson);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Prepare modules by discovering and registering project modules and oh_modules dependencies.
|
|
401
|
+
*
|
|
402
|
+
* Reads build-profile.json5 to discover project modules, then scans oh_modules directories
|
|
403
|
+
* to discover third-party dependencies. Only basic info (paths) is registered —
|
|
404
|
+
* oh-package.json5 is NOT read and no dependency graph is built.
|
|
405
|
+
*
|
|
406
|
+
* Idempotent: if module preparation has already been completed via
|
|
407
|
+
* `Scene.setModulesRegistered(true)`, this method returns immediately.
|
|
408
|
+
* Note: this method does NOT set modulesRegistered — that is done by
|
|
409
|
+
* `Scene.analyseByModule` after both prepareModules and analyzeModuleDependencies complete.
|
|
410
|
+
*/
|
|
411
|
+
prepareModules() {
|
|
412
|
+
if (this.scene.isModulesRegistered()) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
this.registerProjectModules();
|
|
416
|
+
this.registerOhModulesModules();
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Read build-profile.json5 from the project root and register each module as
|
|
420
|
+
* ArkModule(moduleType=PROJECT). The module's srcPath is resolved to an absolute path
|
|
421
|
+
* via path.resolve(projectDir, srcPath). oh-package.json5 is NOT read.
|
|
422
|
+
*/
|
|
423
|
+
registerProjectModules() {
|
|
424
|
+
const projectDir = this.scene.getRealProjectDir();
|
|
425
|
+
const buildProfilePath = path_1.default.join(projectDir, EtsConst_1.BUILD_PROFILE_JSON5);
|
|
426
|
+
if (!fs_1.default.existsSync(buildProfilePath)) {
|
|
427
|
+
logger.warn('There is no build-profile.json5 for this project.');
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
let configurationsText;
|
|
431
|
+
try {
|
|
432
|
+
configurationsText = fs_1.default.readFileSync(buildProfilePath, 'utf-8');
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
logger.error(`Error reading build-profile.json5: ${error}`);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const buildProfileJson = (0, json5parser_1.parseJsonText)(configurationsText);
|
|
439
|
+
const modules = buildProfileJson.modules;
|
|
440
|
+
if (modules instanceof Array) {
|
|
441
|
+
for (const module of modules) {
|
|
442
|
+
const modulePath = path_1.default.resolve(projectDir, module.srcPath);
|
|
443
|
+
this.registerModule(modulePath, module.name);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Scan oh_modules directories and register third-party dependencies as
|
|
449
|
+
* ArkModule(moduleType=OH_MODULES).
|
|
450
|
+
*
|
|
451
|
+
* Scans two types of oh_modules directories:
|
|
452
|
+
* 1. The project-level oh_modules/ under the project root.
|
|
453
|
+
* 2. The oh_modules/ under each registered PROJECT module's directory.
|
|
454
|
+
*
|
|
455
|
+
* A Set is used to avoid scanning the same directory twice.
|
|
456
|
+
*/
|
|
457
|
+
registerOhModulesModules() {
|
|
458
|
+
const projectDir = this.scene.getRealProjectDir();
|
|
459
|
+
const scannedDirs = new Set();
|
|
460
|
+
// 1. Scan project-level oh_modules/
|
|
461
|
+
const projectOhModulesDir = path_1.default.join(projectDir, EtsConst_1.OH_MODULES);
|
|
462
|
+
if (fs_1.default.existsSync(projectOhModulesDir)) {
|
|
463
|
+
scannedDirs.add(projectOhModulesDir);
|
|
464
|
+
this.scanOhModulesDirectory(projectOhModulesDir);
|
|
465
|
+
}
|
|
466
|
+
// 2. Scan each PROJECT module's oh_modules/
|
|
467
|
+
for (const module of this.modulesIterator()) {
|
|
468
|
+
if (module.getModuleType() !== ArkModule_1.ModuleType.PROJECT) {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
const moduleOhModulesDir = path_1.default.join(module.getModulePath(), EtsConst_1.OH_MODULES);
|
|
472
|
+
if (fs_1.default.existsSync(moduleOhModulesDir) && !scannedDirs.has(moduleOhModulesDir)) {
|
|
473
|
+
scannedDirs.add(moduleOhModulesDir);
|
|
474
|
+
this.scanOhModulesDirectory(moduleOhModulesDir);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Recursively scan a single oh_modules directory, registering discovered packages as
|
|
480
|
+
* ArkModule(moduleType=OH_MODULES).
|
|
481
|
+
*
|
|
482
|
+
* Directories starting with '@' (scoped packages) are recursed into.
|
|
483
|
+
* Regular directories have their symlinks resolved via fs.realpathSync() and are
|
|
484
|
+
* registered if not already present (deduplication by resolved real path).
|
|
485
|
+
*
|
|
486
|
+
* @param ohModulesDir - Absolute path of the oh_modules directory to scan.
|
|
487
|
+
*/
|
|
488
|
+
scanOhModulesDirectory(ohModulesDir) {
|
|
489
|
+
let entries;
|
|
490
|
+
try {
|
|
491
|
+
entries = fs_1.default.readdirSync(ohModulesDir, { withFileTypes: true });
|
|
492
|
+
}
|
|
493
|
+
catch (error) {
|
|
494
|
+
logger.error(`Error reading oh_modules directory: ${ohModulesDir}`, error);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
for (const entry of entries) {
|
|
498
|
+
const entryPath = path_1.default.join(ohModulesDir, entry.name);
|
|
499
|
+
// Accept both real directories and symlinks (which may point to directories).
|
|
500
|
+
// Dirent.isDirectory() returns false for symlinks, so check isSymbolicLink() too.
|
|
501
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) {
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (entry.name.startsWith('@')) {
|
|
505
|
+
// Scoped package (e.g. @ohos/library): recurse into the scope directory
|
|
506
|
+
this.scanOhModulesDirectory(entryPath);
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
// Regular package: resolve symlinks and register
|
|
510
|
+
const realPath = fs_1.default.realpathSync(entryPath);
|
|
511
|
+
if (this.getModuleByPath(realPath)) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
const module = this.registerModule(realPath, entry.name);
|
|
515
|
+
module.setModuleType(ArkModule_1.ModuleType.OH_MODULES);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
// --- Dependency analysis ---
|
|
520
|
+
/**
|
|
521
|
+
* Main entry point for module dependency analysis.
|
|
522
|
+
*
|
|
523
|
+
* Reads oh-package.json5 to update module names, builds the dependency graph,
|
|
524
|
+
* runs SCC detection with post-processing refinement. The topological order and SCC groups
|
|
525
|
+
* are stored inside the dependency graph on the Scene, accessed via `ModuleBuilder.getTopoOrder()`.
|
|
526
|
+
*
|
|
527
|
+
* Idempotent: if the dependency graph already exists on the Scene, this method returns immediately.
|
|
528
|
+
*/
|
|
529
|
+
analyzeModuleDependencies() {
|
|
530
|
+
if (this.scene.getModuleDepGraph()) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
this.updateModuleNamesFromOhPkg();
|
|
534
|
+
this.buildDependencyGraph();
|
|
535
|
+
const graph = this.scene.getModuleDepGraph();
|
|
536
|
+
graph.refineSCCGroups(graph.getMaxSCCGroupSize());
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Build the module dependency graph by reading each module's oh-package.json5.
|
|
540
|
+
*
|
|
541
|
+
* Creates a new {@link ModuleDepGraph} with the shared canonicalizer, adds all
|
|
542
|
+
* registered modules as nodes, then resolves dependencies and adds edges.
|
|
543
|
+
* Resolved dependencies create graph edges and update the module's alias map;
|
|
544
|
+
* unresolved dependencies are recorded as unresolved dependencies. The built graph is
|
|
545
|
+
* stored on the Scene via `Scene.setModuleDepGraph()`.
|
|
546
|
+
*/
|
|
547
|
+
buildDependencyGraph() {
|
|
548
|
+
const graph = this.scene.createModuleDepGraph();
|
|
549
|
+
// 1. Add all registered modules as graph nodes
|
|
550
|
+
for (const module of this.modulesIterator()) {
|
|
551
|
+
graph.addModule(module);
|
|
552
|
+
}
|
|
553
|
+
// 2. Read project-level overrides and overrideDependencyMap from the project root oh-package.json5
|
|
554
|
+
const { overrides, overrideDependencyMap } = this.readProjectOverrides();
|
|
555
|
+
// 3. Process inter-module dependencies via oh-package.json5
|
|
556
|
+
for (const module of this.modulesIterator()) {
|
|
557
|
+
const deps = module.readOhPkgContent();
|
|
558
|
+
const depEntries = this.extractDependenciesWithValues(deps);
|
|
559
|
+
for (const [alias, depValue, depType] of depEntries) {
|
|
560
|
+
const depModule = this.resolveDepModule(alias, depValue, module.getModulePath(), overrides, overrideDependencyMap);
|
|
561
|
+
if (depModule) {
|
|
562
|
+
const srcId = this.scene.getModuleId(module);
|
|
563
|
+
const dstId = this.scene.getModuleId(depModule);
|
|
564
|
+
graph.addDependencyEdge(srcId, dstId, depType);
|
|
565
|
+
module.addDependencyAlias(alias, dstId);
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
module.addUnresolvedDependency(alias, depValue);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
this.scene.setModuleDepGraph(graph);
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Read the project root oh-package.json5 and extract `overrides` and `overrideDependencyMap`.
|
|
576
|
+
*
|
|
577
|
+
* Both fields are optional dependency override configurations. `overrides` maps a dependency
|
|
578
|
+
* alias to an override path (local path, file: path, or @module: reference).
|
|
579
|
+
* `overrideDependencyMap` maps a dependency alias to an override file path. When a dependency
|
|
580
|
+
* alias is present in either map, the override path is used instead of the original dependency
|
|
581
|
+
* value during resolution (checked before normal resolution).
|
|
582
|
+
*
|
|
583
|
+
* @returns An object with `overrides` and `overrideDependencyMap` string maps (empty when absent).
|
|
584
|
+
*/
|
|
585
|
+
readProjectOverrides() {
|
|
586
|
+
const empty = {};
|
|
587
|
+
const projectDir = this.scene.getRealProjectDir();
|
|
588
|
+
const ohPkgPath = path_1.default.join(projectDir, EtsConst_1.OH_PACKAGE_JSON5);
|
|
589
|
+
if (!fs_1.default.existsSync(ohPkgPath)) {
|
|
590
|
+
return { overrides: empty, overrideDependencyMap: empty };
|
|
591
|
+
}
|
|
592
|
+
let ohPkgContent;
|
|
593
|
+
try {
|
|
594
|
+
ohPkgContent = (0, json5parser_1.fetchDependenciesFromFile)(ohPkgPath);
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
logger.error(`Error reading project oh-package.json5: ${error}`);
|
|
598
|
+
return { overrides: empty, overrideDependencyMap: empty };
|
|
599
|
+
}
|
|
600
|
+
const overrides = this.toStringMap(ohPkgContent.overrides);
|
|
601
|
+
const overrideDependencyMap = this.toStringMap(ohPkgContent.overrideDependencyMap);
|
|
602
|
+
return { overrides, overrideDependencyMap };
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Coerce a record value to a `{ [k: string]: string }` map, dropping non-string entries.
|
|
606
|
+
*/
|
|
607
|
+
toStringMap(value) {
|
|
608
|
+
const result = {};
|
|
609
|
+
if (value instanceof Object) {
|
|
610
|
+
for (const [k, v] of Object.entries(value)) {
|
|
611
|
+
if (typeof v === 'string') {
|
|
612
|
+
result[k] = v;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return result;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Extract dependency entries from oh-package.json5 content.
|
|
620
|
+
*
|
|
621
|
+
* Traverses dependencies (→ DEPENDENCIES), devDependencies (→ DEV_DEPENDENCIES),
|
|
622
|
+
* and dynamicDependencies (→ DYNAMIC), returning a [alias, value, DependencyType]
|
|
623
|
+
* triple for each entry. The DependencyType is used during SCC post-processing
|
|
624
|
+
* to determine edge removal priority when splitting oversized groups.
|
|
625
|
+
*/
|
|
626
|
+
extractDependenciesWithValues(ohPkgContent) {
|
|
627
|
+
const result = [];
|
|
628
|
+
const depKeyToType = [
|
|
629
|
+
[Const_1.OH_PKG_DEPENDENCIES, ModuleDepGraph_1.DependencyType.DEPENDENCIES],
|
|
630
|
+
[Const_1.OH_PKG_DEV_DEPENDENCIES, ModuleDepGraph_1.DependencyType.DEV_DEPENDENCIES],
|
|
631
|
+
[Const_1.OH_PKG_DYNAMIC_DEPENDENCIES, ModuleDepGraph_1.DependencyType.DYNAMIC],
|
|
632
|
+
];
|
|
633
|
+
for (const [key, depType] of depKeyToType) {
|
|
634
|
+
const deps = ohPkgContent[key];
|
|
635
|
+
if (deps instanceof Object) {
|
|
636
|
+
for (const [k, v] of Object.entries(deps)) {
|
|
637
|
+
if (typeof v === 'string') {
|
|
638
|
+
result.push([k, v, depType]);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return result;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Resolve a dependency declaration to its target {@link ArkModule}.
|
|
647
|
+
*
|
|
648
|
+
* Resolution priority:
|
|
649
|
+
* 0. overrides / overrideDependencyMap — when the alias is present in either project-level
|
|
650
|
+
* override map, the override path replaces the original dependency value before resolution.
|
|
651
|
+
* 1. {@link MODULE_PREFIX} prefix — find by moduleName among registered modules
|
|
652
|
+
* 2. "./", "../", or "file:" prefix — resolve as local path relative to
|
|
653
|
+
* scopeModulePath, look up in pathToId
|
|
654
|
+
* 3. Version number — look in oh_modules directories in priority order:
|
|
655
|
+
* a. current module's oh_modules (scopeModulePath/oh_modules/alias)
|
|
656
|
+
* b. project-level oh_modules (projectDir/oh_modules/alias)
|
|
657
|
+
* c. .ohpm cache (projectDir/oh_modules/.ohpm/<name>@<version>/oh_modules/<name>)
|
|
658
|
+
* Each candidate is verified as a directory, resolved via fs.realpathSync(), then looked
|
|
659
|
+
* up in pathToId. For .har dependencies inside the .ohpm cache, the current module's
|
|
660
|
+
* oh_modules base is recomputed from the enclosing oh_modules ancestor.
|
|
661
|
+
*
|
|
662
|
+
* @param alias - The dependency alias (key in oh-package.json5 dependencies).
|
|
663
|
+
* @param depValue - The dependency value (path, version, or @module: reference).
|
|
664
|
+
* @param scopeModulePath - The absolute path of the source module (for relative path resolution).
|
|
665
|
+
* @param overrides - Optional project-level overrides map (alias -> override path).
|
|
666
|
+
* @param overrideDependencyMap - Optional project-level overrideDependencyMap (alias -> override path).
|
|
667
|
+
* @returns The target ArkModule if resolved, undefined otherwise.
|
|
668
|
+
*/
|
|
669
|
+
resolveDepModule(alias, depValue, scopeModulePath, overrides, overrideDependencyMap) {
|
|
670
|
+
// 0. Override handling: check overrides and overrideDependencyMap before normal resolution
|
|
671
|
+
if (overrides && overrides[alias] !== undefined) {
|
|
672
|
+
depValue = overrides[alias];
|
|
673
|
+
}
|
|
674
|
+
else if (overrideDependencyMap && overrideDependencyMap[alias] !== undefined) {
|
|
675
|
+
depValue = overrideDependencyMap[alias];
|
|
676
|
+
}
|
|
677
|
+
// 1. Module reference dependency: "@module:Foo"
|
|
678
|
+
if (depValue.startsWith(EtsConst_1.MODULE_PREFIX)) {
|
|
679
|
+
const refName = depValue.slice(EtsConst_1.MODULE_PREFIX.length);
|
|
680
|
+
for (const mod of this.modulesIterator()) {
|
|
681
|
+
if (mod.getModuleName() === refName) {
|
|
682
|
+
return mod;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return undefined;
|
|
686
|
+
}
|
|
687
|
+
// 2. Local path dependency: "./...", "../...", "file:..."
|
|
688
|
+
if (depValue.startsWith('./') || depValue.startsWith('../') || depValue.startsWith('file:')) {
|
|
689
|
+
const pathPart = depValue.startsWith('file:') ? depValue.slice('file:'.length) : depValue;
|
|
690
|
+
const resolvedPath = path_1.default.resolve(scopeModulePath, pathPart);
|
|
691
|
+
const id = this.pathToId.get(resolvedPath);
|
|
692
|
+
if (id !== undefined) {
|
|
693
|
+
return this.scene.getModule(id);
|
|
694
|
+
}
|
|
695
|
+
return undefined;
|
|
696
|
+
}
|
|
697
|
+
// 3. oh_modules dependency: version number like "^1.0.0"
|
|
698
|
+
const projectDir = this.scene.getRealProjectDir();
|
|
699
|
+
// For .har dependencies inside the .ohpm cache, recompute the oh_modules base from the
|
|
700
|
+
// enclosing oh_modules ancestor (mirrors processDependency in ModuleUtils).
|
|
701
|
+
const moduleBase = depValue.endsWith('.har') && scopeModulePath.includes(EtsConst_1.OHPM)
|
|
702
|
+
? scopeModulePath.substring(0, scopeModulePath.lastIndexOf(EtsConst_1.OH_MODULES))
|
|
703
|
+
: scopeModulePath;
|
|
704
|
+
const candidates = [
|
|
705
|
+
path_1.default.resolve(moduleBase, EtsConst_1.OH_MODULES, alias),
|
|
706
|
+
path_1.default.resolve(projectDir, EtsConst_1.OH_MODULES, alias),
|
|
707
|
+
this.findModulePathInOHPM(projectDir, alias, depValue),
|
|
708
|
+
];
|
|
709
|
+
for (const candidate of candidates) {
|
|
710
|
+
if (!candidate || !FileUtils_1.FileUtils.isDirectory(candidate)) {
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
const realPath = fs_1.default.realpathSync(candidate);
|
|
714
|
+
const id = this.pathToId.get(realPath);
|
|
715
|
+
if (id !== undefined) {
|
|
716
|
+
return this.scene.getModule(id);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return undefined;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Find a module path in the .ohpm cache directory.
|
|
723
|
+
*
|
|
724
|
+
* Mirrors {@link ModuleUtils.findModulePathInOHPM}: the cache lives at
|
|
725
|
+
* `<projectDir>/oh_modules/.ohpm` and stores packages as
|
|
726
|
+
* `<moduleName.replace('/', '+')>@<version>/oh_modules/<moduleName>`. Exact versions are
|
|
727
|
+
* matched directly; `^`-prefixed ranges pick the first directory whose version is not less
|
|
728
|
+
* than the requested minimum.
|
|
729
|
+
*
|
|
730
|
+
* @param projectDir - The project root directory.
|
|
731
|
+
* @param moduleName - The dependency alias / module name.
|
|
732
|
+
* @param version - The dependency version value (exact or `^`-prefixed).
|
|
733
|
+
* @returns The candidate directory path, or '' when not found.
|
|
734
|
+
*/
|
|
735
|
+
findModulePathInOHPM(projectDir, moduleName, version) {
|
|
736
|
+
const ohpmPath = path_1.default.resolve(projectDir, EtsConst_1.OH_MODULES, EtsConst_1.OHPM);
|
|
737
|
+
const prefix = `${moduleName.replace('/', '+')}@`;
|
|
738
|
+
// exact version match
|
|
739
|
+
if (/^\d+(?:\.\d+){2}(?:-[0-9A-Za-z-]+)?$/.test(version)) {
|
|
740
|
+
return path_1.default.resolve(ohpmPath, prefix + version, EtsConst_1.OH_MODULES, moduleName);
|
|
741
|
+
}
|
|
742
|
+
// try to find the best version
|
|
743
|
+
try {
|
|
744
|
+
const dirs = fs_1.default.readdirSync(ohpmPath, { withFileTypes: true }).filter(dir => dir.isDirectory() && dir.name.startsWith(prefix));
|
|
745
|
+
for (const dir of dirs) {
|
|
746
|
+
const dirName = dir.name;
|
|
747
|
+
if (version.startsWith('^') && dirName.split('@')[1] < version.substring(1)) {
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
return path_1.default.resolve(ohpmPath, dirName, EtsConst_1.OH_MODULES, moduleName);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
catch (e) {
|
|
754
|
+
logger.warn(`Cannot find module: ${moduleName} in ${ohpmPath}`);
|
|
755
|
+
}
|
|
756
|
+
return '';
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Read oh-package.json5 for each registered module and update moduleName.
|
|
760
|
+
*
|
|
761
|
+
* The "name" field in oh-package.json5 (e.g. "@ohos/entry") is the canonical
|
|
762
|
+
* module name used for "@module:" reference resolution. This method populates
|
|
763
|
+
* {@link ArkModule.moduleName} from oh-package.json5, overriding the short name
|
|
764
|
+
* from build-profile.json5 set during registration.
|
|
765
|
+
*/
|
|
766
|
+
updateModuleNamesFromOhPkg() {
|
|
767
|
+
for (const module of this.modulesIterator()) {
|
|
768
|
+
const ohPkgContent = module.readOhPkgContent();
|
|
769
|
+
const name = ohPkgContent.name;
|
|
770
|
+
if (typeof name !== 'string') {
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
module.setModuleName(name);
|
|
774
|
+
this.registerModulePath(module, name, ohPkgContent);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
registerModulePath(module, name, ohPkgContent) {
|
|
778
|
+
if (ModuleUtils_1.ModuleUtils.MODULES.has(name)) {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const modulePath = module.getModulePath();
|
|
782
|
+
let entry = ohPkgContent.types || ohPkgContent.main || '';
|
|
783
|
+
if (entry.endsWith('.js')) {
|
|
784
|
+
entry = entry.substring(0, entry.length - '.js'.length);
|
|
785
|
+
}
|
|
786
|
+
const mainPath = entry ? path_1.default.resolve(modulePath, entry) : modulePath;
|
|
787
|
+
ModuleUtils_1.ModuleUtils.MODULES.set(name, new ModuleUtils_1.ModulePath(modulePath, mainPath));
|
|
788
|
+
}
|
|
789
|
+
// --- Module loading ---
|
|
790
|
+
/**
|
|
791
|
+
* Map a {@link ModuleDepthLevel} to the corresponding {@link ModuleLoadState}.
|
|
792
|
+
* Used by {@link loadModule} to translate the configured depth level into a load state.
|
|
793
|
+
*/
|
|
794
|
+
depthLevelToLoadState(level) {
|
|
795
|
+
switch (level) {
|
|
796
|
+
case ModuleDepth_1.ModuleDepthLevel.META:
|
|
797
|
+
return ArkModule_1.ModuleLoadState.META;
|
|
798
|
+
case ModuleDepth_1.ModuleDepthLevel.IMPORTS:
|
|
799
|
+
return ArkModule_1.ModuleLoadState.IMPORTS;
|
|
800
|
+
case ModuleDepth_1.ModuleDepthLevel.SIGNATURES:
|
|
801
|
+
return ArkModule_1.ModuleLoadState.SIGNATURES;
|
|
802
|
+
case ModuleDepth_1.ModuleDepthLevel.BODIES:
|
|
803
|
+
return ArkModule_1.ModuleLoadState.BODIES;
|
|
804
|
+
default:
|
|
805
|
+
return ArkModule_1.ModuleLoadState.META;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Reverse mapping of {@link depthLevelToLoadState}. Used to look up the heapUsed table
|
|
810
|
+
* entry for a module's current load state.
|
|
811
|
+
*/
|
|
812
|
+
loadStateToDepthLevel(loadState) {
|
|
813
|
+
switch (loadState) {
|
|
814
|
+
case ArkModule_1.ModuleLoadState.META:
|
|
815
|
+
return ModuleDepth_1.ModuleDepthLevel.META;
|
|
816
|
+
case ArkModule_1.ModuleLoadState.IMPORTS:
|
|
817
|
+
return ModuleDepth_1.ModuleDepthLevel.IMPORTS;
|
|
818
|
+
case ArkModule_1.ModuleLoadState.SIGNATURES:
|
|
819
|
+
return ModuleDepth_1.ModuleDepthLevel.SIGNATURES;
|
|
820
|
+
case ArkModule_1.ModuleLoadState.BODIES:
|
|
821
|
+
return ModuleDepth_1.ModuleDepthLevel.BODIES;
|
|
822
|
+
default:
|
|
823
|
+
return ModuleDepth_1.ModuleDepthLevel.META;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Run type inference on the module's files, following the same pattern as
|
|
828
|
+
* {@link Scene.inferTypes} but scoped to a single module.
|
|
829
|
+
*
|
|
830
|
+
* Reuses the file topological order already computed by {@link analyzeFileDependencies}
|
|
831
|
+
* (stored in the module's {@link FileDepGraph}) so that depended-on files are inferred first.
|
|
832
|
+
* Falls back to unsorted iteration when no file dependency graph is available (e.g. when this
|
|
833
|
+
* method is called directly without a prior {@link loadModule}).
|
|
834
|
+
*
|
|
835
|
+
* At SIGNATURES level (no method bodies), only the `preInfer` phase is effective (generic
|
|
836
|
+
* types, parameter types, signature return types, import/export resolution). At BODIES level,
|
|
837
|
+
* full type inference runs (including stmt-level propagation and return type aggregation).
|
|
838
|
+
*
|
|
839
|
+
* Does NOT call {@link Scene.getMethodsMap}(true): rebuilding the global index would clear
|
|
840
|
+
* caches for other modules. Does NOT set {@link Scene.buildStage} to TYPE_INFERRED: that is a
|
|
841
|
+
* whole-scene flag. Does NOT call {@link SdkUtils.dispose} / {@link ModuleUtils.dispose} /
|
|
842
|
+
* {@link ValueUtil.dispose}: the global caches are shared across modules and should be
|
|
843
|
+
* released by the caller after all modules are processed.
|
|
844
|
+
*
|
|
845
|
+
* @param module - The module whose files are to be type-inferred.
|
|
846
|
+
* @param times - Number of inference iterations (clamped to 1–5). Default 1.
|
|
847
|
+
*/
|
|
848
|
+
inferModuleTypes(module, times = 1) {
|
|
849
|
+
if (times < 1) {
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
if (times > 5) {
|
|
853
|
+
times = 5;
|
|
854
|
+
}
|
|
855
|
+
// Reuse the file topological order computed by analyzeFileDependencies
|
|
856
|
+
const fileDepGraph = module.getFileDepGraph();
|
|
857
|
+
let sortedFiles;
|
|
858
|
+
if (fileDepGraph && fileDepGraph.getTopoOrder().length > 0) {
|
|
859
|
+
sortedFiles = fileDepGraph
|
|
860
|
+
.getTopoOrder()
|
|
861
|
+
.map(id => fileDepGraph.tryGetNode(id))
|
|
862
|
+
.filter((f) => f !== undefined);
|
|
863
|
+
}
|
|
864
|
+
else {
|
|
865
|
+
// Fallback: no file dep graph available (e.g. direct call without loadModule)
|
|
866
|
+
sortedFiles = Array.from(module.getFilesMap().values());
|
|
867
|
+
}
|
|
868
|
+
while (times > 0) {
|
|
869
|
+
for (const file of sortedFiles) {
|
|
870
|
+
Inference_1.InferenceManager.getInstance().getInference(file.getLanguage()).doInfer(file);
|
|
871
|
+
}
|
|
872
|
+
times--;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Load module data at the specified depth level.
|
|
877
|
+
*
|
|
878
|
+
* Dependencies are NOT recursively loaded here — the caller (e.g.
|
|
879
|
+
* {@link Scene.analyseByModule}) must iterate modules in topological order so that
|
|
880
|
+
* depended-on modules are loaded before their dependents.
|
|
881
|
+
*
|
|
882
|
+
* When cache management has been initialized via {@link initCacheManagement}, this method
|
|
883
|
+
* automatically checks memory before loading (evicting cached modules if over threshold)
|
|
884
|
+
* and registers the module in the cache after loading.
|
|
885
|
+
*
|
|
886
|
+
* Flow:
|
|
887
|
+
* 1. SDK modules are skipped — their file content is built by {@link buildSdkModules}, not here.
|
|
888
|
+
* 2. Idempotent: skip if the module's loadState already reaches the target load state.
|
|
889
|
+
* 3. Build module data to the effective level via {@link buildModuleToLevel}, which integrates
|
|
890
|
+
* intra-module file dependency analysis and topological-order parsing.
|
|
891
|
+
* 4. When the effective level reaches BODIES, build method bodies (ArkBody/CFG/Stmt/Expr) via
|
|
892
|
+
* {@link FrontendBuilder.buildModuleMethodBody}, following the same two-phase pattern as
|
|
893
|
+
* {@link Scene.genArkFiles}.
|
|
894
|
+
* 5. When the effective level reaches SIGNATURES, run type inference on the module's files
|
|
895
|
+
* via {@link inferModuleTypes}, referencing the logic of {@link Scene.inferTypes}.
|
|
896
|
+
* 6. Set the load state to the target level (after successful build).
|
|
897
|
+
*
|
|
898
|
+
* @param moduleId - ID of the module to load.
|
|
899
|
+
* @param depthLevel - The depth level to load the module to.
|
|
900
|
+
*/
|
|
901
|
+
loadModule(moduleId, depthLevel = ModuleDepth_1.ModuleDepthLevel.META) {
|
|
902
|
+
var _a, _b, _c, _d, _e;
|
|
903
|
+
const module = this.getModule(moduleId);
|
|
904
|
+
if (!module) {
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
// Cache management: evict cached modules if memory is over target
|
|
908
|
+
this.evictIfNeeded(moduleId, depthLevel);
|
|
909
|
+
// SDK modules: file content is built by buildSdkModules, not loadModule
|
|
910
|
+
if (module.getModuleType() === ArkModule_1.ModuleType.SDK) {
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
const targetLoadState = this.depthLevelToLoadState(depthLevel);
|
|
914
|
+
// Idempotent: skip if already loaded to the target level, but ensure in cache
|
|
915
|
+
if (module.getLoadState() >= targetLoadState) {
|
|
916
|
+
(_a = this.cache) === null || _a === void 0 ? void 0 : _a.register(moduleId);
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
// Record heapUsed before actual build (for heapUsed table population)
|
|
920
|
+
const recordHeapUsed = (_c = (_b = this.monitor) === null || _b === void 0 ? void 0 : _b.isEnabled()) !== null && _c !== void 0 ? _c : false;
|
|
921
|
+
const heapUsedBefore = recordHeapUsed ? this.monitor.getCurrentHeapUsed() : 0;
|
|
922
|
+
// Phase 1: build ArkFile objects to the effective level, with intra-module file
|
|
923
|
+
// dependency analysis integrated (topological-order parsing for level > IMPORTS).
|
|
924
|
+
this.buildModuleToLevel(module, depthLevel);
|
|
925
|
+
// Phase 2: build method bodies when BODIES level was requested
|
|
926
|
+
if (depthLevel >= ModuleDepth_1.ModuleDepthLevel.BODIES) {
|
|
927
|
+
FrontendBuilder_1.FrontendBuilder.buildModuleMethodBody(module);
|
|
928
|
+
}
|
|
929
|
+
// Phase 3: type inference when level >= SIGNATURES
|
|
930
|
+
if (depthLevel >= ModuleDepth_1.ModuleDepthLevel.SIGNATURES) {
|
|
931
|
+
this.inferModuleTypes(module);
|
|
932
|
+
}
|
|
933
|
+
// Set load state after successful build
|
|
934
|
+
module.setLoadState(targetLoadState);
|
|
935
|
+
// Record heapUsed increment in cache table (first load only — recordHeapUsed is a no-op if entry exists)
|
|
936
|
+
if (recordHeapUsed) {
|
|
937
|
+
const heapUsedAfter = this.monitor.getCurrentHeapUsed();
|
|
938
|
+
const heapUsedIncrement = Math.max(0, heapUsedAfter - heapUsedBefore);
|
|
939
|
+
(_d = this.cache) === null || _d === void 0 ? void 0 : _d.recordHeapUsed(moduleId, depthLevel, heapUsedIncrement);
|
|
940
|
+
}
|
|
941
|
+
// Register in cache after successful load
|
|
942
|
+
(_e = this.cache) === null || _e === void 0 ? void 0 : _e.register(moduleId);
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Unload a module's data, releasing heavy data and resetting to NOT_LOADED state.
|
|
946
|
+
*
|
|
947
|
+
* Clears the module's filesMap (releasing ArkFile/ArkClass/ArkMethod objects), resets the
|
|
948
|
+
* file dependency graph (so that the next {@link loadModule} rebuilds IMPORTS from scratch),
|
|
949
|
+
* and sets the load state to NOT_LOADED. After unloading, {@link loadModule} can rebuild
|
|
950
|
+
* the module from scratch.
|
|
951
|
+
*
|
|
952
|
+
* SDK modules are not unloaded (their data is built by {@link buildSdkModules} and shared
|
|
953
|
+
* globally).
|
|
954
|
+
*
|
|
955
|
+
* @param moduleId - ID of the module to unload.
|
|
956
|
+
*/
|
|
957
|
+
unload(moduleId) {
|
|
958
|
+
const module = this.getModule(moduleId);
|
|
959
|
+
if (!module) {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
if (module.getModuleType() === ArkModule_1.ModuleType.SDK) {
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
this.scene.disposeModule(module);
|
|
966
|
+
this.breakInternalReferences(module);
|
|
967
|
+
module.clearFilesMap();
|
|
968
|
+
module.setFileDepGraph(undefined);
|
|
969
|
+
module.setLoadState(ArkModule_1.ModuleLoadState.NOT_LOADED);
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Break all internal references between IR objects in a module's ArkFiles.
|
|
973
|
+
* Called after {@link Scene.disposeModule} to ensure that IR objects (ArkClass, ArkMethod,
|
|
974
|
+
* ArkBody, ArkField, ExportInfo, ImportInfo) do not retain each other through cross-references,
|
|
975
|
+
* allowing V8 GC to reclaim them individually.
|
|
976
|
+
*/
|
|
977
|
+
breakInternalReferences(module) {
|
|
978
|
+
for (const arkFile of module.getFilesMap().values()) {
|
|
979
|
+
arkFile.clearAllReferences();
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
// --- Cache management ---
|
|
983
|
+
/**
|
|
984
|
+
* Initialize or update cache management for module loading.
|
|
985
|
+
*
|
|
986
|
+
* On the first call, creates a {@link ModuleCache} and {@link MemoryMonitor} and stores
|
|
987
|
+
* them persistently in {@link Scene}. On subsequent calls, reuses the persistent instances
|
|
988
|
+
* and only updates the per-call topological order.
|
|
989
|
+
*
|
|
990
|
+
* @param options - Scene options containing memoryLimitMB.
|
|
991
|
+
* @param topoOrder - Topological order of modules to be loaded (dependees before dependents).
|
|
992
|
+
* @param dependencyLoadLevel - Load level to downgrade protected dependencies to during eviction.
|
|
993
|
+
*/
|
|
994
|
+
initCacheManagement(options, topoOrder, dependencyLoadLevel = ModuleDepth_1.ModuleDepthLevel.SIGNATURES) {
|
|
995
|
+
if (!this.scene.getModuleCache()) {
|
|
996
|
+
this.scene.setModuleCache(new ModuleCache_1.ModuleCache());
|
|
997
|
+
this.scene.setMemoryMonitor(new MemoryMonitor_1.MemoryMonitor(options));
|
|
998
|
+
}
|
|
999
|
+
this.monitor = this.scene.getMemoryMonitor();
|
|
1000
|
+
this.cache = this.scene.getModuleCache();
|
|
1001
|
+
this.topoOrder = topoOrder;
|
|
1002
|
+
this.dependencyLoadLevel = dependencyLoadLevel;
|
|
1003
|
+
this.topoOrderIndex = new Map();
|
|
1004
|
+
for (let i = 0; i < topoOrder.length; i++) {
|
|
1005
|
+
this.topoOrderIndex.set(topoOrder[i], i);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Check memory and evict cached modules if needed, before loading a new module.
|
|
1010
|
+
*
|
|
1011
|
+
* Estimates the heapUsed increment of the upcoming load (from the heapUsed table if available,
|
|
1012
|
+
* otherwise from HEAPUSED_PER_FILE), computes a heapUsed upper limit (heapUsedLimit - estimated
|
|
1013
|
+
* increment), takes one actual heapUsed measurement, and only proceeds with eviction if
|
|
1014
|
+
* heapUsed exceeds the upper limit.
|
|
1015
|
+
*
|
|
1016
|
+
* During eviction, the heapUsed estimate is tracked via {@link HeapUsedEstimateState},
|
|
1017
|
+
* decremented by the heapUsed table value of each unloaded/downgraded module.
|
|
1018
|
+
*/
|
|
1019
|
+
evictIfNeeded(moduleId, depthLevel = ModuleDepth_1.ModuleDepthLevel.META) {
|
|
1020
|
+
if (!this.monitor || !this.cache || !this.topoOrderIndex) {
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
if (!this.monitor.isEnabled()) {
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
const idx = this.topoOrderIndex.get(moduleId);
|
|
1027
|
+
if (idx === undefined) {
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
const module = this.getModule(moduleId);
|
|
1031
|
+
if (!module || module.getModuleType() === ArkModule_1.ModuleType.SDK) {
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
const estInc = this.estimateLoadHeapUsed(moduleId, module, depthLevel);
|
|
1035
|
+
const heapUsedUpperLimit = Math.max(0, this.monitor.getHeapUsedLimitBytes() - estInc);
|
|
1036
|
+
const heapUsedStart = this.monitor.getCurrentHeapUsed();
|
|
1037
|
+
if (heapUsedStart <= heapUsedUpperLimit) {
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
const heapUsedState = { estimate: heapUsedStart, limit: heapUsedUpperLimit };
|
|
1041
|
+
const depGraph = this.scene.getModuleDepGraph();
|
|
1042
|
+
const { hardProtected, softProtected } = this.computeProtectedSets(moduleId, idx, depGraph);
|
|
1043
|
+
const protectedDeps = new Set([...hardProtected, ...softProtected]);
|
|
1044
|
+
protectedDeps.delete(moduleId);
|
|
1045
|
+
const reverseTopo = [...this.topoOrder].reverse();
|
|
1046
|
+
this.runPhase1Eviction(hardProtected, softProtected, depGraph, heapUsedState);
|
|
1047
|
+
this.runPhase2Downgrade(protectedDeps, reverseTopo, heapUsedState);
|
|
1048
|
+
this.runPhase3Eviction(protectedDeps, reverseTopo, heapUsedState);
|
|
1049
|
+
}
|
|
1050
|
+
runPhase1Eviction(hardProtected, softProtected, depGraph, heapUsedState) {
|
|
1051
|
+
var _a, _b;
|
|
1052
|
+
(_a = this.cache) === null || _a === void 0 ? void 0 : _a.evict(hardProtected, softProtected, this.monitor, id => this.unload(id), (_b = depGraph === null || depGraph === void 0 ? void 0 : depGraph.getSCCGroups) === null || _b === void 0 ? void 0 : _b.call(depGraph), heapUsedState, id => this.estimateUnloadHeapUsedDecrease(id));
|
|
1053
|
+
}
|
|
1054
|
+
runPhase2Downgrade(protectedDeps, reverseTopo, heapUsedState) {
|
|
1055
|
+
var _a;
|
|
1056
|
+
if (heapUsedState.estimate <= heapUsedState.limit) {
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
const downgradeTarget = Math.max(this.dependencyLoadLevel, ModuleDepth_1.ModuleDepthLevel.SIGNATURES);
|
|
1060
|
+
const targetLoadState = this.depthLevelToLoadState(downgradeTarget);
|
|
1061
|
+
for (const id of reverseTopo) {
|
|
1062
|
+
if (heapUsedState.estimate <= heapUsedState.limit) {
|
|
1063
|
+
break;
|
|
1064
|
+
}
|
|
1065
|
+
if (!protectedDeps.has(id) || !((_a = this.cache) === null || _a === void 0 ? void 0 : _a.has(id))) {
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
const mod = this.getModule(id);
|
|
1069
|
+
if (!mod || mod.getLoadState() <= targetLoadState) {
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
const decrease = this.estimateDowngradeHeapUsedDecrease(id, downgradeTarget);
|
|
1073
|
+
this.downgradeModule(id, downgradeTarget);
|
|
1074
|
+
heapUsedState.estimate -= decrease;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
runPhase3Eviction(protectedDeps, reverseTopo, heapUsedState) {
|
|
1078
|
+
var _a;
|
|
1079
|
+
if (heapUsedState.estimate <= heapUsedState.limit) {
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
for (const id of reverseTopo) {
|
|
1083
|
+
if (heapUsedState.estimate <= heapUsedState.limit) {
|
|
1084
|
+
break;
|
|
1085
|
+
}
|
|
1086
|
+
if (protectedDeps.has(id) && ((_a = this.cache) === null || _a === void 0 ? void 0 : _a.has(id))) {
|
|
1087
|
+
const decrease = this.estimateUnloadHeapUsedDecrease(id);
|
|
1088
|
+
this.unload(id);
|
|
1089
|
+
this.cache.unregister(id);
|
|
1090
|
+
heapUsedState.estimate -= decrease;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Estimate the memory cost of loading a module at the given depth level.
|
|
1096
|
+
* Used as a fallback when the heapUsed table has no entry for this module+level.
|
|
1097
|
+
*/
|
|
1098
|
+
estimateLoadCost(module, depthLevel) {
|
|
1099
|
+
var _a;
|
|
1100
|
+
const fileCount = this.getFileCount(module);
|
|
1101
|
+
const bytesPerFile = (_a = ModuleBuilder.HEAPUSED_PER_FILE.get(depthLevel)) !== null && _a !== void 0 ? _a : 0;
|
|
1102
|
+
return fileCount * bytesPerFile;
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Estimate the heapUsed increment for loading a module at the given depth level.
|
|
1106
|
+
* Uses the heapUsed table value if available (from a prior first-load measurement),
|
|
1107
|
+
* otherwise falls back to {@link estimateLoadCost} (fileCount × HEAPUSED_PER_FILE).
|
|
1108
|
+
*/
|
|
1109
|
+
estimateLoadHeapUsed(moduleId, module, depthLevel) {
|
|
1110
|
+
var _a;
|
|
1111
|
+
const tableHeapUsed = (_a = this.cache) === null || _a === void 0 ? void 0 : _a.getHeapUsed(moduleId, depthLevel);
|
|
1112
|
+
if (tableHeapUsed !== undefined) {
|
|
1113
|
+
return tableHeapUsed;
|
|
1114
|
+
}
|
|
1115
|
+
return this.estimateLoadCost(module, depthLevel);
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Estimate the heapUsed decrease from unloading a module (full unload).
|
|
1119
|
+
* Uses the heapUsed table value for the module's current load level,
|
|
1120
|
+
* or falls back to HEAPUSED_PER_FILE estimate.
|
|
1121
|
+
*/
|
|
1122
|
+
estimateUnloadHeapUsedDecrease(moduleId) {
|
|
1123
|
+
var _a;
|
|
1124
|
+
const module = this.getModule(moduleId);
|
|
1125
|
+
if (!module) {
|
|
1126
|
+
return 0;
|
|
1127
|
+
}
|
|
1128
|
+
const currentLevel = this.loadStateToDepthLevel(module.getLoadState());
|
|
1129
|
+
const tableHeapUsed = (_a = this.cache) === null || _a === void 0 ? void 0 : _a.getHeapUsed(moduleId, currentLevel);
|
|
1130
|
+
if (tableHeapUsed !== undefined) {
|
|
1131
|
+
return tableHeapUsed;
|
|
1132
|
+
}
|
|
1133
|
+
return this.estimateLoadCost(module, currentLevel);
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Estimate the heapUsed decrease from downgrading a module from its current level
|
|
1137
|
+
* to a target level. Uses a ratio-based approach: downgrade releases a fixed
|
|
1138
|
+
* proportion of the current-level heapUsed, avoiding the need for target-level
|
|
1139
|
+
* table entries (which are never populated since downgradeModule doesn't write
|
|
1140
|
+
* to the heapUsed table).
|
|
1141
|
+
*/
|
|
1142
|
+
estimateDowngradeHeapUsedDecrease(moduleId, _targetLevel) {
|
|
1143
|
+
var _a, _b;
|
|
1144
|
+
const module = this.getModule(moduleId);
|
|
1145
|
+
if (!module) {
|
|
1146
|
+
return 0;
|
|
1147
|
+
}
|
|
1148
|
+
const currentLevel = this.loadStateToDepthLevel(module.getLoadState());
|
|
1149
|
+
const currentHeapUsed = (_b = (_a = this.cache) === null || _a === void 0 ? void 0 : _a.getHeapUsed(moduleId, currentLevel)) !== null && _b !== void 0 ? _b : this.estimateLoadCost(module, currentLevel);
|
|
1150
|
+
return Math.max(0, Math.floor(currentHeapUsed * ModuleBuilder.DOWNGRADE_RELEASE_RATIO));
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Downgrade a module's loaded data to the given target level (min SIGNATURES), releasing the
|
|
1154
|
+
* heavy method-body/view-tree/AST data while keeping signatures (ArkClass/method signatures,
|
|
1155
|
+
* filesMap, Scene index maps) so dependents can still resolve types. No-op if already at or
|
|
1156
|
+
* below the target level. Used by eviction Phase 2.
|
|
1157
|
+
*/
|
|
1158
|
+
downgradeModule(moduleId, targetLevel) {
|
|
1159
|
+
const module = this.getModule(moduleId);
|
|
1160
|
+
if (!module || module.getModuleType() === ArkModule_1.ModuleType.SDK) {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
const targetLoadState = this.depthLevelToLoadState(targetLevel);
|
|
1164
|
+
if (module.getLoadState() <= targetLoadState) {
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
for (const arkFile of module.getFilesMap().values()) {
|
|
1168
|
+
for (const cls of ModelUtils_1.ModelUtils.getAllClassesInFile(arkFile)) {
|
|
1169
|
+
for (const mtd of cls.getMethods(true)) {
|
|
1170
|
+
mtd.clearBodyAndSupplementary();
|
|
1171
|
+
}
|
|
1172
|
+
cls.viewTree = undefined;
|
|
1173
|
+
for (const field of cls.getFields()) {
|
|
1174
|
+
field.setInitializer([]);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
arkFile.setAST(null);
|
|
1178
|
+
arkFile.clearSourceCode();
|
|
1179
|
+
}
|
|
1180
|
+
module.setLoadState(targetLoadState);
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Get the number of source files in a module. If the module's filesMap is empty
|
|
1184
|
+
* (not yet loaded), scans the module directory.
|
|
1185
|
+
*/
|
|
1186
|
+
getFileCount(module) {
|
|
1187
|
+
var _a, _b;
|
|
1188
|
+
if (module.getFilesMap().size > 0) {
|
|
1189
|
+
return module.getFilesMap().size;
|
|
1190
|
+
}
|
|
1191
|
+
const options = this.scene.getOptions();
|
|
1192
|
+
const supportFileExts = (_a = options === null || options === void 0 ? void 0 : options.supportFileExts) !== null && _a !== void 0 ? _a : ['.ets', '.ts'];
|
|
1193
|
+
const ignoreFileNames = [...((_b = options === null || options === void 0 ? void 0 : options.ignoreFileNames) !== null && _b !== void 0 ? _b : []), EtsConst_1.OH_MODULES];
|
|
1194
|
+
return (0, getAllFiles_1.getAllFiles)(module.getModulePath(), supportFileExts, ignoreFileNames).length;
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Compute the hard and soft protected sets for eviction.
|
|
1198
|
+
* - Hard: the current module and its direct dependencies (never evicted).
|
|
1199
|
+
* - Soft: direct dependencies of not-yet-loaded modules, plus already-cached modules
|
|
1200
|
+
* at later topo positions (to avoid reload overhead).
|
|
1201
|
+
*/
|
|
1202
|
+
computeProtectedSets(moduleId, idx, depGraph) {
|
|
1203
|
+
const hardProtected = new Set([moduleId]);
|
|
1204
|
+
const softProtected = new Set();
|
|
1205
|
+
if (!depGraph || !this.cache) {
|
|
1206
|
+
return { hardProtected, softProtected };
|
|
1207
|
+
}
|
|
1208
|
+
// Hard-protect current module's direct dependencies
|
|
1209
|
+
for (const depId of depGraph.getSuccModuleIds(moduleId)) {
|
|
1210
|
+
if (this.cache.has(depId)) {
|
|
1211
|
+
hardProtected.add(depId);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
// Soft-protect direct dependencies of not-yet-loaded modules
|
|
1215
|
+
// and already-cached modules at later positions (avoid reload)
|
|
1216
|
+
for (let j = idx + 1; j < this.topoOrder.length; j++) {
|
|
1217
|
+
for (const depId of depGraph.getSuccModuleIds(this.topoOrder[j])) {
|
|
1218
|
+
if (this.cache.has(depId)) {
|
|
1219
|
+
softProtected.add(depId);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
if (this.cache.has(this.topoOrder[j])) {
|
|
1223
|
+
softProtected.add(this.topoOrder[j]);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
return { hardProtected, softProtected };
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Build module data to the specified depth level, integrating intra-module file dependency
|
|
1230
|
+
* analysis into the build flow.
|
|
1231
|
+
*
|
|
1232
|
+
* Two-phase build for level > IMPORTS:
|
|
1233
|
+
* 1. Ensure ArkFile shells exist (create if filesMap is empty, reuse otherwise).
|
|
1234
|
+
* 2. Build all files to IMPORTS level (lightweight import/export parsing, no dependency on
|
|
1235
|
+
* other files in the module).
|
|
1236
|
+
* 3. Analyze intra-module file dependencies → compute topological order (moved here from
|
|
1237
|
+
* {@link loadModule} so it is part of the build-to-level flow).
|
|
1238
|
+
* 4. For level > IMPORTS: upgrade each file to the target level (SIGNATURES/BODIES) in
|
|
1239
|
+
* topological order, so that depended-on files are parsed first.
|
|
1240
|
+
*
|
|
1241
|
+
* For level == META: only shells are created (no content).
|
|
1242
|
+
* For level == IMPORTS: steps 1-3 (topoOrder available for later type inference).
|
|
1243
|
+
* For level >= SIGNATURES: steps 1-4 (signatures built in topoOrder).
|
|
1244
|
+
*
|
|
1245
|
+
* The {@link ArkModule.hasFileTopoOrder} flag is used to detect whether IMPORTS was already
|
|
1246
|
+
* built (e.g. when upgrading from IMPORTS to SIGNATURES), avoiding redundant re-parsing.
|
|
1247
|
+
*
|
|
1248
|
+
* @param module - The module to build.
|
|
1249
|
+
* @param level - The target depth level.
|
|
1250
|
+
*/
|
|
1251
|
+
buildModuleToLevel(module, level) {
|
|
1252
|
+
// 1. Ensure ArkFile shells exist (reuse if already created, e.g. from a prior META load)
|
|
1253
|
+
let arkFiles;
|
|
1254
|
+
if (module.getFilesMap().size === 0) {
|
|
1255
|
+
arkFiles = FrontendBuilder_1.FrontendBuilder.createModuleFileShells(this.scene, module);
|
|
1256
|
+
}
|
|
1257
|
+
else {
|
|
1258
|
+
arkFiles = Array.from(module.getFilesMap().values());
|
|
1259
|
+
}
|
|
1260
|
+
// META: no content to build
|
|
1261
|
+
if (level <= ModuleDepth_1.ModuleDepthLevel.META) {
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
// 2. Build IMPORTS level (if not already done) + analyze file dependencies
|
|
1265
|
+
// Use fileDepGraph existence as indicator that IMPORTS was already built.
|
|
1266
|
+
if (level >= ModuleDepth_1.ModuleDepthLevel.IMPORTS && !module.hasFileTopoOrder()) {
|
|
1267
|
+
for (const arkFile of arkFiles) {
|
|
1268
|
+
try {
|
|
1269
|
+
FrontendBuilder_1.FrontendBuilder.buildImports(arkFile, arkFile.getLanguage());
|
|
1270
|
+
}
|
|
1271
|
+
catch (error) {
|
|
1272
|
+
logger.error(`Error building imports for ${arkFile.getFilePath()}: ${error}`);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
this.analyzeFileDependencies(module);
|
|
1276
|
+
}
|
|
1277
|
+
// 3. For level > IMPORTS: upgrade to target level in topological order
|
|
1278
|
+
if (level > ModuleDepth_1.ModuleDepthLevel.IMPORTS) {
|
|
1279
|
+
const fileDepGraph = module.getFileDepGraph();
|
|
1280
|
+
let sortedFiles;
|
|
1281
|
+
if (fileDepGraph && fileDepGraph.getTopoOrder().length > 0) {
|
|
1282
|
+
sortedFiles = fileDepGraph
|
|
1283
|
+
.getTopoOrder()
|
|
1284
|
+
.map(id => fileDepGraph.tryGetNode(id))
|
|
1285
|
+
.filter((f) => f !== undefined);
|
|
1286
|
+
}
|
|
1287
|
+
else {
|
|
1288
|
+
// Fallback: no file dep graph available (e.g. empty module or analyzeFileDependencies produced no edges)
|
|
1289
|
+
sortedFiles = arkFiles;
|
|
1290
|
+
}
|
|
1291
|
+
for (const arkFile of sortedFiles) {
|
|
1292
|
+
try {
|
|
1293
|
+
FrontendBuilder_1.FrontendBuilder.upgradeArkFileToLevel(this.scene, arkFile, arkFile.getLanguage(), level);
|
|
1294
|
+
}
|
|
1295
|
+
catch (error) {
|
|
1296
|
+
logger.error(`Error upgrading ArkFile ${arkFile.getFilePath()}: ${error}`);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
// --- Intra-module file dependency analysis ---
|
|
1302
|
+
/**
|
|
1303
|
+
* Analyze file-to-file dependencies within a module using the import/export `from` specifiers
|
|
1304
|
+
* populated at IMPORTS level.
|
|
1305
|
+
*
|
|
1306
|
+
* Builds a {@link FileDepGraph}, resolves relative `from` specifiers to file paths within the
|
|
1307
|
+
* same module, adds dependency edges, computes a topological order via SCC detection, and
|
|
1308
|
+
* stores the graph on the {@link ArkModule}. The topological order is retained inside the
|
|
1309
|
+
* FileDepGraph and queried via {@link ArkModule.hasFileTopoOrder}.
|
|
1310
|
+
*
|
|
1311
|
+
* - Only relative `from` specifiers (`./`, `../`) are resolved; bare specifiers are ignored.
|
|
1312
|
+
* - Resolution matches the specifier against already-existing ArkFile paths in the module's filesMap.
|
|
1313
|
+
* - Only files within the module (present in the module's filesMap) get edges; external files are ignored.
|
|
1314
|
+
*
|
|
1315
|
+
* @param module - The module whose files are analyzed.
|
|
1316
|
+
*/
|
|
1317
|
+
analyzeFileDependencies(module) {
|
|
1318
|
+
const filesMap = module.getFilesMap();
|
|
1319
|
+
if (filesMap.size === 0) {
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
// Build a path-to-ArkFile lookup for this module
|
|
1323
|
+
const pathToFile = new Map();
|
|
1324
|
+
for (const arkFile of filesMap.values()) {
|
|
1325
|
+
pathToFile.set(arkFile.getFilePath(), arkFile);
|
|
1326
|
+
}
|
|
1327
|
+
// Create a canonicalizer for ArkFile <-> NodeID mapping
|
|
1328
|
+
const fileCanonicalizer = new Canonicalizer_1.Canonicalizer();
|
|
1329
|
+
const fileDepGraph = new FileDepGraph_1.FileDepGraph(fileCanonicalizer);
|
|
1330
|
+
// Add all files as graph nodes
|
|
1331
|
+
for (const arkFile of filesMap.values()) {
|
|
1332
|
+
fileDepGraph.addFile(arkFile);
|
|
1333
|
+
}
|
|
1334
|
+
// Process import/export from specifiers to add dependency edges
|
|
1335
|
+
for (const arkFile of filesMap.values()) {
|
|
1336
|
+
const srcId = fileCanonicalizer.getId(arkFile);
|
|
1337
|
+
const fromSpecifiers = this.collectFromSpecifiers(arkFile);
|
|
1338
|
+
for (const from of fromSpecifiers) {
|
|
1339
|
+
// Only resolve relative specifiers
|
|
1340
|
+
if (!from.startsWith('./') && !from.startsWith('../')) {
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1343
|
+
const resolvedPath = this.resolveFromSpecifier(from, arkFile, pathToFile);
|
|
1344
|
+
if (!resolvedPath) {
|
|
1345
|
+
continue;
|
|
1346
|
+
}
|
|
1347
|
+
const dstFile = pathToFile.get(resolvedPath);
|
|
1348
|
+
if (!dstFile) {
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1351
|
+
const dstId = fileCanonicalizer.getId(dstFile);
|
|
1352
|
+
fileDepGraph.addDependencyEdge(srcId, dstId);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
// Compute topological order (stored inside the FileDepGraph)
|
|
1356
|
+
fileDepGraph.computeTopoOrder();
|
|
1357
|
+
module.setFileDepGraph(fileDepGraph);
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Collect all `from` specifiers from an ArkFile's import and export infos.
|
|
1361
|
+
* Duplicate specifiers are deduplicated.
|
|
1362
|
+
*/
|
|
1363
|
+
collectFromSpecifiers(arkFile) {
|
|
1364
|
+
const fromSet = new Set();
|
|
1365
|
+
for (const importInfo of arkFile.getImportInfos()) {
|
|
1366
|
+
const from = importInfo.getFrom();
|
|
1367
|
+
if (from) {
|
|
1368
|
+
fromSet.add(from);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
for (const exportInfo of arkFile.getExportInfos()) {
|
|
1372
|
+
const from = exportInfo.getFrom();
|
|
1373
|
+
if (from) {
|
|
1374
|
+
fromSet.add(from);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return fromSet;
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Resolve a relative `from` specifier (e.g. `./b`, `../utils/helper`) to an absolute file path
|
|
1381
|
+
* that exists as an already-built ArkFile in the module.
|
|
1382
|
+
*
|
|
1383
|
+
* Resolution matches against the keys of {@link pathToFile} (the module's already-generated
|
|
1384
|
+
* ArkFile paths) instead of probing the filesystem:
|
|
1385
|
+
*
|
|
1386
|
+
* 1. The specifier as-is (may already include an extension).
|
|
1387
|
+
* 2. The specifier with each unique extension found among existing ArkFile paths appended.
|
|
1388
|
+
* 3. The specifier as a directory: look for an index file whose path is in {@link pathToFile}.
|
|
1389
|
+
*
|
|
1390
|
+
* @param from - The relative from specifier (starts with `./` or `../`).
|
|
1391
|
+
* @param arkFile - The file containing the import/export (base for relative resolution).
|
|
1392
|
+
* @param pathToFile - Map of already-built ArkFile paths in the module.
|
|
1393
|
+
* @returns The resolved absolute file path, or undefined if not found.
|
|
1394
|
+
*/
|
|
1395
|
+
resolveFromSpecifier(from, arkFile, pathToFile) {
|
|
1396
|
+
const baseDir = path_1.default.dirname(arkFile.getFilePath());
|
|
1397
|
+
const resolvedPath = path_1.default.resolve(baseDir, from);
|
|
1398
|
+
// 1. Try as-is (specifier may already include an extension)
|
|
1399
|
+
if (pathToFile.has(resolvedPath)) {
|
|
1400
|
+
return resolvedPath;
|
|
1401
|
+
}
|
|
1402
|
+
// 2. Try appending extensions collected from existing files in the module
|
|
1403
|
+
const exts = new Set();
|
|
1404
|
+
for (const filePath of pathToFile.keys()) {
|
|
1405
|
+
const ext = path_1.default.extname(filePath);
|
|
1406
|
+
if (ext) {
|
|
1407
|
+
exts.add(ext);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
for (const ext of exts) {
|
|
1411
|
+
const candidate = resolvedPath + ext;
|
|
1412
|
+
if (pathToFile.has(candidate)) {
|
|
1413
|
+
return candidate;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
// 3. Try as a directory with an index file
|
|
1417
|
+
try {
|
|
1418
|
+
const indexFileName = FileUtils_1.FileUtils.getIndexFileName(resolvedPath);
|
|
1419
|
+
if (indexFileName) {
|
|
1420
|
+
const indexPath = path_1.default.join(resolvedPath, indexFileName);
|
|
1421
|
+
if (pathToFile.has(indexPath)) {
|
|
1422
|
+
return indexPath;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
catch (_a) {
|
|
1427
|
+
// resolvedPath is not a readable directory; skip index file resolution
|
|
1428
|
+
}
|
|
1429
|
+
return undefined;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
exports.ModuleBuilder = ModuleBuilder;
|
|
1433
|
+
/**
|
|
1434
|
+
* Estimated heapUsed bytes per file for each depth level, used for pre-load memory cost estimation.
|
|
1435
|
+
* Calibrated from scene_board_ext measurements (no-eviction, no GC runs):
|
|
1436
|
+
* - BODIES: P75 of per-module heapUsed-per-file at BODIES level = ~1.7MB/file
|
|
1437
|
+
* - SIGNATURES: P75 of per-module heapUsed-per-file at SIGNATURES level = ~700KB/file
|
|
1438
|
+
* - IMPORTS/META: negligible (metadata only).
|
|
1439
|
+
*
|
|
1440
|
+
* No-GC calibration matches production conditions (no forced GC). Without GC,
|
|
1441
|
+
* heapUsed increments include uncollected garbage from parsing and type inference,
|
|
1442
|
+
* which is several times larger than surviving data.
|
|
1443
|
+
*
|
|
1444
|
+
* Used for pre-load estimation (deciding whether eviction is needed before loading)
|
|
1445
|
+
* and as a fallback for unload estimation when the heapUsed table has no entry.
|
|
1446
|
+
*/
|
|
1447
|
+
ModuleBuilder.HEAPUSED_PER_FILE = new Map([
|
|
1448
|
+
[ModuleDepth_1.ModuleDepthLevel.META, 0],
|
|
1449
|
+
[ModuleDepth_1.ModuleDepthLevel.IMPORTS, 0],
|
|
1450
|
+
[ModuleDepth_1.ModuleDepthLevel.SIGNATURES, 700000],
|
|
1451
|
+
[ModuleDepth_1.ModuleDepthLevel.BODIES, 1700000],
|
|
1452
|
+
]);
|
|
1453
|
+
/**
|
|
1454
|
+
* Ratio of memory released by downgrading from BODIES to SIGNATURES, relative to
|
|
1455
|
+
* the BODIES-level heapUsed. Calibrated from GC trace test data:
|
|
1456
|
+
* downgrade releases ~60% of BODIES heapUsed (method bodies + AST + source code).
|
|
1457
|
+
*/
|
|
1458
|
+
ModuleBuilder.DOWNGRADE_RELEASE_RATIO = 0.6;
|