@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
224 lines • 9.59 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FlowrAnalyzerDependenciesContext = void 0;
const abstract_flowr_analyzer_context_1 = require("./abstract-flowr-analyzer-context");
const flowr_analyzer_package_versions_plugin_1 = require("../plugins/package-version-plugins/flowr-analyzer-package-versions-plugin");
const package_1 = require("../plugins/package-version-plugins/package");
const identifier_1 = require("../../dataflow/environments/identifier");
const assert_1 = require("../../util/assert");
const config_1 = require("../../config");
const r_version_1 = require("../../util/r-version");
/**
* Manages the project's dependencies, their versions, and their interplay with {@link FlowrAnalyzerPackageVersionsPlugin}s.
*/
class FlowrAnalyzerDependenciesContext extends abstract_flowr_analyzer_context_1.AbstractFlowrAnalyzerContext {
name = 'flowr-analyzer-dependencies-context';
functionsContext;
dependencies = new Map();
/** what {@link getDependency} resolved for a package the project does not depend on, so a lookup never makes it one */
resolvedOnly = new Map();
staticsLoaded = false;
/** resolvers consulted lazily to fill in exports; `existing` carries version info from other plugins */
lazyResolvers = [];
resolvedMisses = new Set();
reset() {
this.dependencies = new Map();
this.resolvedOnly = new Map();
this.staticsLoaded = false;
this.lazyResolvers = [];
this.resolvedMisses = new Set();
}
/** Register a resolver consulted by {@link getDependency} to fill in a package's exports lazily. */
addLazyResolver(resolver) {
this.lazyResolvers.push(resolver);
}
loadedSignatureDatabases() {
if (!(0, config_1.isSigDbEnabled)(this.ctx.config)) {
return [];
}
return this.plugins.flatMap(p => p.loadedDatabases());
}
availableSignatureDatabases() {
return [...new Set(this.loadedSignatureDatabases().map(d => d.scope))];
}
hasSignatureDatabase() {
return (0, config_1.isSigDbEnabled)(this.ctx.config) && this.plugins.some(p => p.loadedDatabases().length > 0);
}
packagesExporting(name) {
if (!(0, config_1.isSigDbEnabled)(this.ctx.config)) {
return [];
}
const out = new Set();
for (const p of this.plugins) {
for (const pkg of p.packagesExporting(name)) {
out.add(pkg);
}
}
return [...out];
}
signatureSources() {
if (!(0, config_1.isSigDbEnabled)(this.ctx.config)) {
return [];
}
return this.plugins.flatMap(p => [...p.signatureSources(this.ctx.config)]);
}
signatureOf(id, version) {
const pkg = identifier_1.Identifier.getNamespace(id);
if (pkg === undefined) {
return undefined; // a qualified `pkg::fn` identifier is required to know which package to look in
}
const name = identifier_1.Identifier.getName(id);
const ver = version ?? this.getDependency(pkg)?.resolvedVersion;
for (const src of this.signatureSources()) {
if (!src.has(pkg)) {
continue; // avoids touching a package this source lacks
}
// decode only the one function (not the whole package), falling back to the source's latest version
const fn = src.functionByName(pkg, name, ver) ?? src.functionByName(pkg, name);
if (fn) {
return fn;
}
}
return undefined;
}
/** Whether any version plugin can resolve the base-R packages (a versioned signature source is available). */
hasBaseRSource() {
return (0, config_1.isSigDbEnabled)(this.ctx.config) && this.plugins.some(p => p.providesBaseRPackages());
}
/** Cheap fingerprint of only the base-R-providing databases, so base-R-derived caches survive unrelated database changes. */
baseRSourceFingerprint() {
if (!(0, config_1.isSigDbEnabled)(this.ctx.config)) {
return '';
}
return this.plugins.filter(p => p.providesBaseRPackages())
.flatMap(p => p.loadedDatabases()).map(d => d.hash).join(',');
}
/** Mount an additional signature database/source by path (a plain `.sigs.ndjson`, a `.br`, or a manifest). */
async addDatabaseSource(source) {
for (const p of this.plugins) {
await p.addDatabaseSource(source);
}
}
/** Eagerly mount every version plugin's signature database up front (see `solver.sigdb.eagerlyLoad`). */
eagerlyLoadSignatureDatabases() {
for (const p of this.plugins) {
p.preloadDatabasesSync();
}
}
receive(event) {
const type = event.type;
switch (type) {
case "full" /* InvalidationEventType.Full */:
this.reset();
break;
case "single-file-invalidate" /* InvalidationEventType.SingleFileInvalidate */:
// nothing to do
break;
default:
(0, assert_1.assertUnreachable)(type);
}
}
constructor(functionsContext, plugins) {
super(functionsContext.getAttachedContext(), flowr_analyzer_package_versions_plugin_1.FlowrAnalyzerPackageVersionsPlugin.defaultPlugin(), plugins);
this.functionsContext = functionsContext;
}
resolveStaticDependencies() {
this.applyPlugins(undefined);
this.staticsLoaded = true;
}
/** Runs the static plugins once. They fill this context and the project metadata, so both gate their reads on it. */
ensureStaticsLoaded() {
if (!this.staticsLoaded) {
this.resolveStaticDependencies();
}
}
/**
* Register a dependency declared by a project metadata file (`DESCRIPTION`, `renv.lock`, `rv.lock`, `uvr.lock`). Gated by
* `solver.sigdb.loadProjectDependencies`: when project-dependency loading is disabled this is a no-op, so the
* declared deps never enter the context (unlike {@link addDependency}, used for on-demand signature-database
* resolution).
*/
addDeclaredDependency(pkg) {
if (!this.ctx.config.solver.sigdb.loadProjectDependencies) {
return this;
}
return this.addDependency(pkg);
}
addDependency(pkg) {
mergeIntoMap(this.dependencies, pkg);
return this;
}
getDependency(name, version) {
if (!this.staticsLoaded) {
this.resolveStaticDependencies();
}
if (version !== undefined) {
return this.resolvePinnedDependency(name, typeof version === 'string' ? r_version_1.RRange.parse('=' + version) : version);
}
const existing = this.dependencies.get(name) ?? this.resolvedOnly.get(name);
// a package already carrying exports is complete; a version-only one is still enriched below
if (existing?.namespaceInfo || (!existing && this.resolvedMisses.has(name))) {
return existing;
}
if (!this.resolvedMisses.has(name)) {
for (const resolve of this.lazyResolvers) {
const resolved = resolve(name, existing);
if (resolved) {
// merges exports into any existing version info, of a dependency or of a mere lookup
const into = this.dependencies.has(name) ? this.dependencies : this.resolvedOnly;
mergeIntoMap(into, resolved);
return into.get(name);
}
}
this.resolvedMisses.add(name);
}
return existing;
}
/** Like {@link getDependency}, but for a package the code pulls in (`library(p)`): the result joins the project's {@link getDependencies|dependencies}. */
loadDependency(name) {
const found = this.getDependency(name);
const cached = this.resolvedOnly.get(name);
if (cached !== undefined) {
this.resolvedOnly.delete(name);
mergeIntoMap(this.dependencies, cached);
}
return this.dependencies.get(name) ?? found;
}
/** Resolve `name` constrained to `range` via the plugins (uncached); falls back to the cached dependency. */
resolvePinnedDependency(name, range) {
const pin = new package_1.Package({ name, versionConstraints: range ? [range] : [] });
for (const resolve of this.lazyResolvers) {
const resolved = resolve(name, pin);
if (resolved) {
return resolved;
}
}
return this.getDependency(name);
}
inferredRange(name) {
const pkg = this.getDependency(name);
return pkg?.hasSatisfiableVersion() ? pkg.derivedRange : undefined;
}
getDependencies() {
if (!this.staticsLoaded) {
this.resolveStaticDependencies();
}
return Array.from(this.dependencies.values());
}
declaredPackageNames() {
this.ensureStaticsLoaded();
const declared = Object.values(this.ctx.meta.getDeclaredPackages());
return [...new Set(declared.flatMap(group => group?.map(p => p.name) ?? []))];
}
}
exports.FlowrAnalyzerDependenciesContext = FlowrAnalyzerDependenciesContext;
function mergeIntoMap(map, pkg) {
const existing = map.get(pkg.name);
if (existing) {
existing.mergeInPlace(pkg);
}
else {
map.set(pkg.name, pkg);
}
}
//# sourceMappingURL=flowr-analyzer-dependencies-context.js.map