fix(pkgmap+routes): resolve workspace imports + extract SvelteKit routes

Two structural fixes for TS monorepos (pnpm/yarn workspaces) and
SvelteKit projects, measured to take a 885-file SvelteKit+Turborepo repo
from 319 edges to 31802 (100x) with no fixture regressions.

- pkgmap: package.json / composer.json live in IGNORED_JSON_FILES, so the
  manifest-driven pkgmap builders never saw them and every
  '@my/pkg' workspace import was silently dropped. Add a symlink-safe,
  skip-dir-aware filesystem walker (cbm_pkgmap_scan_repo /
  cbm_pkgmap_build_from_repo) that harvests manifests directly from the
  repo regardless of the discovery filter. Sequential and parallel paths
  both feed it now.
- definitions: extract every file's defs (creating Module nodes) BEFORE
  resolving imports, so a workspace import in the first file can resolve
  against the complete in-memory graph (two-phase, cache-backed).
- routes: synthesise Route nodes + HANDLES edges from SvelteKit's
  filesystem layout (+server / +page.server / +layout.server), which has
  no app.get(...) call site for pass_calls to pick up.

Distilled from #369 onto current main. The PR's CBM_DISABLE_LSP_CROSS
escape hatch was rebased onto the v0.7.0 fused cross-LSP architecture:
it now gates run_cross_lsp (NULL all_defs makes the fused resolver no-op
cross-file resolution) instead of wrapping the removed standalone
pass_lsp_cross call. Relates to #271 / #56.
This commit is contained in:
utilia-ai-wox
2026-05-30 18:15:47 +02:00
committed by Martin Vogel
parent 2389d82214
commit 9bcfaabe4c
6 changed files with 489 additions and 19 deletions
+48 -5
View File
@@ -343,6 +343,21 @@ int cbm_pipeline_pass_definitions(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t
int total_imports = 0;
int errors = 0;
/* Sequential pass must extract all defs (which create Module/Function/...
* nodes) BEFORE resolving imports — otherwise a workspace import in the
* first file processed can't find the target Module node, because the
* target file's defs haven't been extracted yet. Result cache is
* required for this two-phase ordering. */
CBMFileResult **local_cache = ctx->result_cache;
bool owns_local_cache = false;
if (!local_cache) {
local_cache = (CBMFileResult **)calloc((size_t)file_count, sizeof(CBMFileResult *));
owns_local_cache = (local_cache != NULL);
}
/* Phase 1: extract every file and create def-derived nodes (Modules,
* Functions, ...) so any file's IMPORTS can resolve against the
* complete in-memory graph in Phase 2. */
for (int i = 0; i < file_count; i++) {
if (cbm_pipeline_check_cancel(ctx)) {
return CBM_NOT_FOUND;
@@ -381,17 +396,45 @@ int cbm_pipeline_pass_definitions(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t
/* Store calls for pass_calls (we save them in the extraction results
* for now — a future optimization would batch these) */
total_calls += result->calls.count;
total_imports += create_import_edges_for_file(ctx, result, rel);
create_channel_edges_for_file(ctx, result, rel);
/* Cache or free the extraction result */
if (ctx->result_cache) {
ctx->result_cache[i] = result;
if (local_cache) {
local_cache[i] = result;
} else {
/* Cache unavailable: imports for this file can still only
* resolve to defs already in the graph, but the file's
* own defs are now persisted before the lookup. */
total_imports += create_import_edges_for_file(ctx, result, rel);
create_channel_edges_for_file(ctx, result, rel);
cbm_free_result(result);
}
}
/* Phase 2: now that all extraction results are cached and Module
* nodes for every file are in the graph, walk the cache again to
* create IMPORTS / channel edges. Imports resolve against the full
* project graph. */
if (local_cache) {
for (int i = 0; i < file_count; i++) {
if (cbm_pipeline_check_cancel(ctx)) {
break;
}
CBMFileResult *result = local_cache[i];
if (!result) {
continue;
}
total_imports += create_import_edges_for_file(ctx, result, files[i].rel_path);
create_channel_edges_for_file(ctx, result, files[i].rel_path);
}
if (owns_local_cache) {
for (int i = 0; i < file_count; i++) {
if (local_cache[i]) {
cbm_free_result(local_cache[i]);
}
}
free(local_cache);
}
}
cbm_log_info("pass.done", "pass", "definitions", "defs", itoa_log(total_defs), "calls",
itoa_log(total_calls), "imports", itoa_log(total_imports), "errors",
itoa_log(errors));
+5
View File
@@ -594,6 +594,11 @@ static void merge_pkg_entries(cbm_pipeline_ctx_t *ctx, cbm_pkg_entries_t *pkg_en
if (!pkg_entries) {
return;
}
/* Supplement with a repo-wide filesystem walk so manifests filtered
* by the main discoverer (package.json, composer.json — in
* IGNORED_JSON_FILES) still feed pkgmap. Append into worker 0's
* array so the existing merge below sees them. */
cbm_pkgmap_scan_repo(ctx->repo_path, &pkg_entries[0]);
cbm_pipeline_set_pkgmap(cbm_pkgmap_build(pkg_entries, worker_count, ctx->project_name));
for (int i = 0; i < worker_count; i++) {
cbm_pkg_entries_free(&pkg_entries[i]);
+136 -9
View File
@@ -12,6 +12,7 @@
*/
#include "pipeline/pipeline.h"
#include "pipeline/pipeline_internal.h"
#include "discover/discover.h"
#include "foundation/compat.h"
#include "foundation/constants.h"
#include "foundation/hash_table.h"
@@ -22,10 +23,12 @@
#include <yyjson/yyjson.h>
#include <dirent.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
/* Read an entire file into a malloc'd buffer. Returns NULL on failure. */
static char *pkgmap_read_file(const char *path, int *out_len) {
@@ -747,6 +750,100 @@ CBMHashTable *cbm_pkgmap_build(cbm_pkg_entries_t *worker_entries, int worker_cou
return map;
}
/* Returns true if basename is a package manifest we know how to parse.
* Used by the filesystem walker; cbm_pkgmap_try_parse is the source of
* truth for which basenames produce entries. */
static bool is_pkgmap_manifest_basename(const char *basename) {
if (!basename) {
return false;
}
if (strcmp(basename, "package.json") == 0 || strcmp(basename, "go.mod") == 0 ||
strcmp(basename, "Cargo.toml") == 0 || strcmp(basename, "pyproject.toml") == 0 ||
strcmp(basename, "composer.json") == 0 || strcmp(basename, "pubspec.yaml") == 0 ||
strcmp(basename, "pom.xml") == 0 || strcmp(basename, "build.gradle") == 0 ||
strcmp(basename, "build.gradle.kts") == 0 || strcmp(basename, "mix.exs") == 0) {
return true;
}
return ends_with(basename, ".gemspec");
}
/* Recursive filesystem walker that finds and parses package manifest
* files independently of the main discovery filter. The main discovery
* filter intentionally hides package.json / composer.json etc. from
* code indexing (they're config, not source), but pass_pkgmap still
* needs to read them to resolve workspace imports. Skips directories
* matched by the shared cbm_should_skip_dir helper so we don't walk
* node_modules, .git, build, etc. Returns the number of manifests
* parsed, accumulated across the whole walk. */
static int pkgmap_walk_dir(const char *abs_dir, const char *rel_dir,
cbm_pkg_entries_t *entries) {
DIR *dir = opendir(abs_dir);
if (!dir) {
return 0;
}
int parsed = 0;
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
const char *name = entry->d_name;
if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) {
continue;
}
char abs_path[PKGMAP_PATH_BUF];
char rel_path[PKGMAP_PATH_BUF];
snprintf(abs_path, sizeof(abs_path), "%s/%s", abs_dir, name);
if (rel_dir && rel_dir[0]) {
snprintf(rel_path, sizeof(rel_path), "%s/%s", rel_dir, name);
} else {
snprintf(rel_path, sizeof(rel_path), "%s", name);
}
struct stat st;
if (lstat(abs_path, &st) != 0) {
continue;
}
if (S_ISLNK(st.st_mode)) {
continue;
}
if (S_ISDIR(st.st_mode)) {
if (cbm_should_skip_dir(name, CBM_MODE_FULL)) {
continue;
}
parsed += pkgmap_walk_dir(abs_path, rel_path, entries);
continue;
}
if (!S_ISREG(st.st_mode)) {
continue;
}
if (!is_pkgmap_manifest_basename(name)) {
continue;
}
int source_len = 0;
char *source = pkgmap_read_file(abs_path, &source_len);
if (!source) {
continue;
}
if (cbm_pkgmap_try_parse(name, rel_path, source, source_len, entries)) {
parsed++;
}
free(source);
}
closedir(dir);
return parsed;
}
/* Scan a repository for package manifest files via the filesystem
* walker above. Always-available companion to the parallel path's
* per-worker manifest parsing, which is bound to whatever `files[]`
* the discoverer produces and therefore misses ignored manifests like
* package.json. NULL-safe; returns 0 entries when repo_path is unset. */
int cbm_pkgmap_scan_repo(const char *repo_path, cbm_pkg_entries_t *entries) {
if (!repo_path || !entries) {
return 0;
}
int parsed = pkgmap_walk_dir(repo_path, "", entries);
cbm_log_info("pkgmap.scan_repo", "manifests", pkgmap_itoa(parsed));
return parsed;
}
/* Build pkgmap for sequential path (reads manifest files directly) */
CBMHashTable *cbm_pkgmap_build_from_files(const cbm_file_info_t *files, int file_count,
const char *project_name) {
@@ -755,15 +852,7 @@ CBMHashTable *cbm_pkgmap_build_from_files(const cbm_file_info_t *files, int file
for (int i = 0; i < file_count; i++) {
const char *basename = path_basename(files[i].rel_path);
/* Quick check: is this a manifest file? */
bool is_manifest =
(strcmp(basename, "package.json") == 0 || strcmp(basename, "go.mod") == 0 ||
strcmp(basename, "Cargo.toml") == 0 || strcmp(basename, "pyproject.toml") == 0 ||
strcmp(basename, "composer.json") == 0 || strcmp(basename, "pubspec.yaml") == 0 ||
strcmp(basename, "pom.xml") == 0 || strcmp(basename, "build.gradle") == 0 ||
strcmp(basename, "build.gradle.kts") == 0 || strcmp(basename, "mix.exs") == 0 ||
ends_with(basename, ".gemspec"));
if (!is_manifest) {
if (!is_pkgmap_manifest_basename(basename)) {
continue;
}
@@ -782,6 +871,44 @@ CBMHashTable *cbm_pkgmap_build_from_files(const cbm_file_info_t *files, int file
return map;
}
/* Variant of cbm_pkgmap_build_from_files that ALSO walks the repo
* filesystem to pick up manifests filtered out by the main discoverer
* (the canonical case: package.json, which is in IGNORED_JSON_FILES).
* Falls back to the files[]-only behaviour if repo_path is NULL. */
CBMHashTable *cbm_pkgmap_build_from_repo(const char *repo_path, const cbm_file_info_t *files,
int file_count, const char *project_name) {
cbm_pkg_entries_t entries;
cbm_pkg_entries_init(&entries);
/* Manifests already visible through discovery (Cargo.toml, go.mod,
* pyproject.toml, ...). package.json typically isn't, but we still
* harvest whatever the discovery filter exposed in case downstream
* filters change. */
int from_files = 0;
for (int i = 0; i < file_count; i++) {
const char *basename = path_basename(files[i].rel_path);
if (!is_pkgmap_manifest_basename(basename)) {
continue;
}
from_files++;
int source_len = 0;
char *source = pkgmap_read_file(files[i].path, &source_len);
if (!source) {
continue;
}
cbm_pkgmap_try_parse(basename, files[i].rel_path, source, source_len, &entries);
free(source);
}
int from_walk = cbm_pkgmap_scan_repo(repo_path, &entries);
cbm_log_info("pkgmap.scan", "manifests_from_files", pkgmap_itoa(from_files),
"manifests_from_walk", pkgmap_itoa(from_walk),
"entries", pkgmap_itoa(entries.count));
CBMHashTable *map = cbm_pkgmap_build(&entries, SKIP_ONE, project_name);
cbm_pkg_entries_free(&entries);
return map;
}
static void pkgmap_free_entry(const char *key, void *value, void *userdata) {
(void)userdata;
free((void *)key);
+280
View File
@@ -779,6 +779,281 @@ static void create_data_flows(cbm_gbuf_t *gb) {
}
}
/* ── SvelteKit filesystem-based route extractor ──────────────────────
*
* SvelteKit derives REST endpoints and SSR loaders from the filesystem
* layout under `src/routes/`. No `app.get(...)` style call exists for
* pass_calls.c to pick up, so we walk File nodes and synthesise Route
* nodes + HANDLES edges directly here.
*
* Recognised filenames (TypeScript and JavaScript variants):
*
* +server.{ts,js} → REST endpoints, exports GET/POST/...
* +page.server.{ts,js} → SSR page server module: load + actions
* +layout.server.{ts,js} → SSR layout server module: load
*
* Route path is derived from the directory chain under `routes/`:
*
* apps/x/src/routes/foo/+server.ts → /foo
* apps/x/src/routes/api/items/+server.ts → /api/items
* apps/x/src/routes/(grp)/foo/+server.ts → /foo (group stripped)
* apps/x/src/routes/[slug]/+server.ts → /:slug (param rewrite)
* apps/x/src/routes/+server.ts → / (root)
*/
enum {
SKR_PATH_BUF = 1024,
SKR_NAME_BUF = 64,
};
/* Detect the SvelteKit kind of a file path. Returns 1 for +server,
* 2 for +page.server, 3 for +layout.server; 0 if not a SvelteKit
* server-side route file. */
static int sveltekit_file_kind(const char *file_path) {
if (!file_path) {
return 0;
}
/* The basename must match one of the three patterns. We also require
* a "/routes/" segment somewhere in the path so we don't snag files
* in unrelated directories that happen to be named "+server.ts". */
if (!strstr(file_path, "/routes/")) {
return 0;
}
const char *slash = strrchr(file_path, '/');
const char *base = slash ? slash + 1 : file_path;
if (strcmp(base, "+server.ts") == 0 || strcmp(base, "+server.js") == 0) {
return 1;
}
if (strcmp(base, "+page.server.ts") == 0 || strcmp(base, "+page.server.js") == 0) {
return 2;
}
if (strcmp(base, "+layout.server.ts") == 0 || strcmp(base, "+layout.server.js") == 0) {
return 3;
}
return 0;
}
/* Compute the URL route path for a SvelteKit file path. Writes into
* `out` (caller-provided buffer) and returns the buffer or NULL on
* error. The leading "/" is always present; "/" is used for the root
* route. Group segments wrapped in parentheses are stripped. Dynamic
* params `[slug]` become `:slug`, and rest params `[...slug]` become
* `*slug` so the result is recognisable as a path pattern. */
static const char *sveltekit_route_path(const char *file_path, char *out, int outsz) {
if (!file_path || !out || outsz <= 1) {
return NULL;
}
const char *routes_seg = strstr(file_path, "/routes/");
if (!routes_seg) {
return NULL;
}
/* Walk segment-by-segment between "/routes/" and the trailing "+...". */
const char *p = routes_seg + strlen("/routes/");
const char *last_slash = strrchr(file_path, '/');
if (!last_slash || last_slash < p) {
/* File is directly under /routes/ — root route. */
out[0] = '/';
out[1] = '\0';
return out;
}
/* p points at first char after /routes/, last_slash points at the
* slash before the +...filename. Iterate segments between them. */
int pos = 0;
out[pos] = '\0';
while (p < last_slash) {
const char *seg_end = strchr(p, '/');
if (!seg_end || seg_end > last_slash) {
seg_end = last_slash;
}
size_t seg_len = (size_t)(seg_end - p);
if (seg_len > 0) {
/* Group `(name)` — skip entirely. */
if (p[0] == '(' && p[seg_len - 1] == ')') {
p = seg_end + 1;
continue;
}
/* Emit "/" then rewritten segment. */
if (pos + 1 < outsz - 1) {
out[pos++] = '/';
}
/* Dynamic param: `[slug]` → `:slug`, `[...slug]` → `*slug`. */
if (seg_len >= 2 && p[0] == '[' && p[seg_len - 1] == ']') {
const char *inner = p + 1;
size_t inner_len = seg_len - 2;
if (inner_len >= 3 && strncmp(inner, "...", 3) == 0) {
if (pos < outsz - 1) {
out[pos++] = '*';
}
inner += 3;
inner_len -= 3;
} else {
if (pos < outsz - 1) {
out[pos++] = ':';
}
}
/* Strip an optional `=matcher` suffix after the param name. */
size_t copy_len = inner_len;
for (size_t i = 0; i < inner_len; i++) {
if (inner[i] == '=') {
copy_len = i;
break;
}
}
if ((int)copy_len > outsz - 1 - pos) {
copy_len = (size_t)(outsz - 1 - pos);
}
memcpy(out + pos, inner, copy_len);
pos += (int)copy_len;
} else {
size_t copy_len = seg_len;
if ((int)copy_len > outsz - 1 - pos) {
copy_len = (size_t)(outsz - 1 - pos);
}
memcpy(out + pos, p, copy_len);
pos += (int)copy_len;
}
out[pos] = '\0';
}
p = seg_end + 1;
}
if (pos == 0) {
out[pos++] = '/';
out[pos] = '\0';
}
return out;
}
/* Returns the HTTP method string a function name maps to for a +server
* file, or NULL if the function isn't a SvelteKit REST handler. The set
* mirrors SvelteKit's documented HTTP verb exports. */
static const char *sveltekit_server_method(const char *name) {
if (!name) {
return NULL;
}
static const char *const verbs[] = {
"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD",
};
for (size_t i = 0; i < sizeof(verbs) / sizeof(verbs[0]); i++) {
if (strcmp(name, verbs[i]) == 0) {
return verbs[i];
}
}
/* `fallback` catches any verb not explicitly exported. */
if (strcmp(name, "fallback") == 0) {
return "ANY";
}
return NULL;
}
typedef struct {
cbm_gbuf_t *gb;
int routes_created;
int handles_created;
int files_seen;
} sveltekit_ctx_t;
/* Process one File node: if it matches a SvelteKit server-side file,
* synthesise a Route node and HANDLES edges from any handler functions
* (or actions Variable) defined in the file. */
static void sveltekit_file_visitor(const cbm_gbuf_node_t *node, void *userdata) {
sveltekit_ctx_t *ctx = (sveltekit_ctx_t *)userdata;
if (!node || !node->label || strcmp(node->label, "File") != 0) {
return;
}
int kind = sveltekit_file_kind(node->file_path);
if (kind == 0) {
return;
}
ctx->files_seen++;
char route_path[SKR_PATH_BUF];
if (!sveltekit_route_path(node->file_path, route_path, sizeof(route_path))) {
return;
}
/* Find every DEFINES edge from this file to a Function or Variable. */
const cbm_gbuf_edge_t **edges = NULL;
int edge_count = 0;
if (cbm_gbuf_find_edges_by_source_type(ctx->gb, node->id, "DEFINES", &edges, &edge_count) != 0
|| edge_count == 0) {
return;
}
for (int i = 0; i < edge_count; i++) {
const cbm_gbuf_node_t *child = cbm_gbuf_find_by_id(ctx->gb, edges[i]->target_id);
if (!child || !child->name || !child->label) {
continue;
}
bool is_fn = strcmp(child->label, "Function") == 0;
bool is_var = strcmp(child->label, "Variable") == 0;
if (!is_fn && !is_var) {
continue;
}
const char *method = NULL;
bool is_actions = false;
if (kind == 1) {
method = sveltekit_server_method(child->name);
} else if (kind == 2) {
if (is_fn && strcmp(child->name, "load") == 0) {
method = "LOAD";
} else if (strcmp(child->name, "actions") == 0) {
/* SvelteKit form actions: emit a single Route at this
* path with method=ACTIONS; finer-grained action names
* aren't exposed as top-level identifiers. */
method = "ACTIONS";
is_actions = is_var;
}
} else if (kind == 3) {
if (is_fn && strcmp(child->name, "load") == 0) {
method = "LOAD";
}
}
if (!method) {
continue;
}
char route_qn[CBM_ROUTE_QN_SIZE];
snprintf(route_qn, sizeof(route_qn), "__route__%s__%s", method, route_path);
char route_props[CBM_SZ_256];
snprintf(route_props, sizeof(route_props),
"{\"method\":\"%s\",\"framework\":\"sveltekit\"}", method);
int64_t route_id = cbm_gbuf_upsert_node(ctx->gb, "Route", route_path, route_qn, "", 0, 0,
route_props);
if (route_id == 0) {
continue;
}
ctx->routes_created++;
char hprops[CBM_SZ_256];
snprintf(hprops, sizeof(hprops),
"{\"handler\":\"%s\",\"framework\":\"sveltekit\"%s}",
child->qualified_name ? child->qualified_name : child->name,
is_actions ? ",\"via\":\"actions_object\"" : "");
cbm_gbuf_insert_edge(ctx->gb, child->id, route_id, "HANDLES", hprops);
ctx->handles_created++;
}
}
/* Public entry point: scan all File nodes for SvelteKit server modules
* and synthesise Route + HANDLES nodes from the filesystem convention. */
static void create_sveltekit_routes(cbm_gbuf_t *gb) {
if (!gb) {
return;
}
sveltekit_ctx_t ctx = {.gb = gb, .routes_created = 0, .handles_created = 0, .files_seen = 0};
cbm_gbuf_foreach_node(gb, sveltekit_file_visitor, &ctx);
if (ctx.files_seen > 0) {
char b1[CBM_SZ_16];
char b2[CBM_SZ_16];
char b3[CBM_SZ_16];
snprintf(b1, sizeof(b1), "%d", ctx.files_seen);
snprintf(b2, sizeof(b2), "%d", ctx.routes_created);
snprintf(b3, sizeof(b3), "%d", ctx.handles_created);
cbm_log_info("pass.sveltekit_routes", "files", b1, "routes", b2, "handles", b3);
}
}
void cbm_pipeline_create_route_nodes(cbm_gbuf_t *gb) {
if (!gb) {
return;
@@ -812,4 +1087,9 @@ void cbm_pipeline_create_route_nodes(cbm_gbuf_t *gb) {
* Scans Class nodes from .proto files, follows DEFINES_METHOD edges
* to find rpc methods, creates __grpc__ServiceName/MethodName Route nodes. */
create_grpc_routes(gb);
/* Phase 5: filesystem-based SvelteKit routes (+server / +page.server /
* +layout.server) — no call-site equivalent for pass_calls.c to pick
* up, so we walk File nodes directly here. */
create_sveltekit_routes(gb);
}
+17 -5
View File
@@ -497,9 +497,12 @@ static int run_sequential_pipeline(cbm_pipeline_t *p, cbm_pipeline_ctx_t *ctx,
struct timespec *t) {
cbm_log_info("pipeline.mode", "mode", "sequential", "files", itoa_buf(file_count));
/* Build package map from manifest files (sequential: read manifests directly) */
/* Build package map from manifest files (sequential: read manifests directly) */
cbm_pipeline_set_pkgmap(cbm_pkgmap_build_from_files(files, file_count, ctx->project_name));
/* Build package map from manifest files (sequential: read manifests directly).
* Use the repo-walking variant so manifests filtered out by the main
* discoverer (package.json, composer.json) still feed pkgmap and let
* workspace imports like `@my/pkg` resolve to their target Module. */
cbm_pipeline_set_pkgmap(
cbm_pkgmap_build_from_repo(ctx->repo_path, files, file_count, ctx->project_name));
CBMFileResult **seq_cache = (CBMFileResult **)calloc(file_count, sizeof(CBMFileResult *));
if (seq_cache) {
@@ -590,8 +593,17 @@ static int run_parallel_pipeline(cbm_pipeline_t *p, cbm_pipeline_ctx_t *ctx,
* extract. */
cbm_clock_gettime(CLOCK_MONOTONIC, t);
/* Cross-file LSP (type-aware call/usage resolution across files) — the
* most expensive phase — runs in every mode. */
const bool run_cross_lsp = true;
* most expensive phase. CBM_DISABLE_LSP_CROSS=1 opts out (it can SIGSEGV
* on large TS projects — see #340/#344); with cross-LSP off, all_defs
* stays NULL and the fused resolver simply no-ops cross-file resolution
* (per-file LSP already ran during extract). */
char cbm_lsp_cross_env[CBM_SZ_16];
const bool run_cross_lsp =
cbm_safe_getenv("CBM_DISABLE_LSP_CROSS", cbm_lsp_cross_env, sizeof(cbm_lsp_cross_env),
NULL) == NULL;
if (!run_cross_lsp) {
cbm_log_info("lsp_cross.skipped", "reason", "CBM_DISABLE_LSP_CROSS env set");
}
char **def_modules = NULL;
int def_count = 0;
CBMLSPDef *all_defs = NULL;
+3
View File
@@ -90,6 +90,9 @@ CBMHashTable *cbm_pkgmap_build(cbm_pkg_entries_t *worker_entries, int worker_cou
const char *project_name);
/* Build pkgmap by reading manifest files from the files array (sequential path). */
int cbm_pkgmap_scan_repo(const char *repo_path, cbm_pkg_entries_t *entries);
CBMHashTable *cbm_pkgmap_build_from_repo(const char *repo_path, const cbm_file_info_t *files,
int file_count, const char *project_name);
CBMHashTable *cbm_pkgmap_build_from_files(const cbm_file_info_t *files, int file_count,
const char *project_name);