GitNexus/gitnexus/scripts/build-tree-sitter-grammars.cjs
mengkaka 4154b63131
feat(indexing): add Objective-C semantic indexing support (#3179)
* docs: add Objective-C fork provider notes

* feat(objective-c): add deterministic provider and grammar

* feat(objective-c): finalize provider MVP

* fix(objective-c): harden provider integration

* fix(objective-c): normalize bare macro markers

* docs(objective-c): integrate provider documentation

* fix(objective-c): harden resolution and header classification

* fix(objective-c): complete provider follow-ups

* fix: address Objective-C review follow-ups

* chore: format Objective-C grammar sources

* fix(objective-c): harden review follow-ups

* Address PR review feedback (#3179)

Keep Objective-C chunking and macro recovery aligned with the grammar, and stop Community MEMBER_OF edges from leaking into symbol context.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address follow-up review on ObjC chunking and language fallback.

Keep preprocessor directive text from changing file-scope brace depth, group real ivar nodes, skip header modifiers, and restore Rakefile/Gemfile detection through getLanguageFromFilename.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Parse Objective-C headers with the objc grammar in embeddings.

ensureAndParse and structural extraction now use the same content classifier as ingest, including method snippets from .h files, so Protocol/Category/Class chunks are not re-parsed as C++.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#3179)

Keep file-scope macro elision off C line splices and @interface/@protocol/@implementation bodies, and attach ivar attributes to the following instance variable when chunking.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(bench): rebaseline Objective-C CSV emit

* feat(objective-c): add workspace resolution and linear emit benches

Plain .h files are classified as C++, so the ObjC pass could not
resolve #import of those headers. Load a C/C#-style workspace once
per pass, and keep protocol-candidate USES linear.

Refs #3179

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#3179)

- Compare LadybugDB labels() as a scalar when excluding Community MEMBER_OF edges.
- Walk superclass members, skip file-static C sibling defs, and ignore comments in ObjC header/macro scans.

Note: pre-existing failure in objective-c-provider integration (worker-pool ready timeout) not addressed by this PR.
Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#3179)

Emit Objective-C declaration captures so compilation-unit siblings can share
header/implementation bindings, and keep class vs protocol visibility groups
distinct.

Note: pre-existing failure in worker-pool startup (GITNEXUS_WORKER_READY_TIMEOUT_MS) not addressed by this PR.
Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#3179)

Emit every comma-separated property/ivar declarator, and count @interface
after a multiline block comment closes so in-declaration macros stay intact.

Note: pre-existing failure in worker-pool startup (GITNEXUS_WORKER_READY_TIMEOUT_MS) not addressed by this PR.
Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: ximengkai <ximengkai@soyoung.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 09:40:21 +00:00

128 lines
5.1 KiB
JavaScript

#!/usr/bin/env node
/**
* Activate the vendored tree-sitter native bindings IN PLACE under `vendor/`.
* One registry-driven script replaces the former per-grammar
* build-tree-sitter-<name>.cjs files (they were ~95% identical).
*
* The grammars (tree-sitter-c/objc/dart/proto/swift/kotlin/zig) are loaded from
* `vendor/<name>/` by absolute path at runtime (see
* src/core/tree-sitter/vendored-grammars.ts) and are NEVER copied into
* node_modules — an undeclared package under node_modules is "extraneous" to
* every subsequent npm/npx reify, which prunes/relocates it (Windows
* `EPERM: …, symlink` + a silent grammar deletion on the 2nd run; #2111/#1728).
*
* For each grammar the resolution order is identical:
* 1. If the vendored source is absent (no binding.gyp) or the binding is
* already built, do nothing.
* 2. Prefer a committed prebuild for this platform-arch (toolchain-free) via
* node-gyp-build — `vendor/<name>/prebuilds/` ships all six tuples, so on a
* supported platform this returns immediately and writes nothing.
* 3. Otherwise source-build from the vendored grammar source (binding.gyp +
* src/) into `vendor/<name>/build/` (gitignored) so parsing still works on
* a toolchain host that lacks a matching prebuild.
*
* HARD INVARIANT: this runs in `gitnexus`'s postinstall, so it MUST NEVER throw
* or exit non-zero — a failure for any single grammar must not break the install.
*
* Opt-out: GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1 (strict '1') skips the OPTIONAL
* grammars only. tree-sitter-c and tree-sitter-objc are REQUIRED (C backstops upstream's 4/6 ARM
* prebuild gap, #2116) and are always built.
*
* Usage:
* node build-tree-sitter-grammars.cjs # all grammars (postinstall)
* node build-tree-sitter-grammars.cjs swift c # only the named grammars
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// Registry. `display`/`ext` drive the human-readable warnings; `required`
// grammars ignore the opt-out gate. Insertion order == build order (c first).
const GRAMMARS = {
c: { required: true, display: 'C', ext: '.c' },
objc: { required: true, display: 'Objective-C', ext: '.m/.mm/.h' },
dart: { required: false, display: 'Dart', ext: '.dart' },
proto: { required: false, display: 'Proto', ext: '.proto' },
swift: { required: false, display: 'Swift', ext: '.swift' },
kotlin: { required: false, display: 'Kotlin', ext: '.kt/.kts' },
zig: { required: false, display: 'Zig', ext: '.zig' },
};
const skipOptional = process.env.GITNEXUS_SKIP_OPTIONAL_GRAMMARS === '1';
function buildGrammar(short) {
const cfg = GRAMMARS[short];
const tag = `[tree-sitter-${short}]`;
if (!cfg.required && skipOptional) {
console.warn(
`${tag} Skipping build (GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1). ${cfg.display} parsing will be unavailable until reinstalled without the env var.`,
);
return;
}
const dir = path.join(__dirname, '..', 'vendor', `tree-sitter-${short}`);
const bindingGyp = path.join(dir, 'binding.gyp');
const bindingNode = path.join(dir, 'build', 'Release', `tree_sitter_${short}_binding.node`);
try {
// Not materialized (no source), or already built — nothing to do.
if (!fs.existsSync(bindingGyp) || fs.existsSync(bindingNode)) {
return;
}
// Prefer a committed prebuild for this platform-arch (no toolchain needed).
try {
require('node-gyp-build').path(dir);
return;
} catch {
// No matching prebuild — fall through to the source build below.
}
// The hoisted build deps must be resolvable to source-build.
try {
require.resolve('node-addon-api');
require.resolve('node-gyp-build');
} catch (resolveErr) {
console.warn(
`${tag} Skipping build: hoisted build deps not resolvable (${resolveErr.message}).`,
);
console.warn(
`${tag} ${cfg.display} parsing will be unavailable until a prebuild or toolchain is present.`,
);
return;
}
console.log(`${tag} No prebuild for this platform — building native binding from source...`);
execSync('npx node-gyp rebuild', { cwd: dir, stdio: 'pipe', timeout: 180000 });
console.log(`${tag} Native binding built successfully`);
} catch (err) {
console.warn(`${tag} Could not build native binding:`, err.message);
console.warn(
`${tag} ${cfg.display} (${cfg.ext}) parsing will be unavailable. Non-${cfg.display} functionality is unaffected.`,
);
}
}
function main() {
const args = process.argv.slice(2).filter(Boolean);
const targets = args.length > 0 ? args : Object.keys(GRAMMARS);
for (const short of targets) {
if (!GRAMMARS[short]) {
console.warn(`[tree-sitter] Unknown grammar '${short}' — skipping.`);
continue;
}
// Defensive: never let an unexpected throw escape and fail the install.
try {
buildGrammar(short);
} catch (err) {
console.warn(`[tree-sitter-${short}] Unexpected build error (ignored): ${err.message}`);
}
}
// Hard guarantee: postinstall must never exit non-zero.
process.exit(0);
}
if (require.main === module) main();
module.exports = { GRAMMARS, buildGrammar };