Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion bin/axiomcode
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,30 @@
# or is compiled locally when souffle is present).
# ─────────────────────────────────────────────────────────────────────────────
set -eu
ROOT="$(d="$(cd "$(dirname "$0")" && pwd)"; while [ "$d" != / ] && { [ ! -f "$d/package.json" ] || [ ! -d "$d/graph" ]; }; do d="$(dirname "$d")"; done; echo "$d")"
# RESOLVE $0 THROUGH SYMLINKS BEFORE THE WALK. npm installs a `bin` entry as a link in
# node_modules/.bin, so under `npx axiomcode` or a global install $0 is that link and
# dirname "$0" is .bin/, a directory this package does not live under. The walk below then
# climbs the USER'S project, finds a package.json with no graph/ beside it, runs out of
# parents and returns "/" -- and the run fails with `parser not built at
# //parser/dist/index.js`, a path with a doubled slash that names nothing, plus advice to run
# `npm install && npm run build`, which on an installed copy is neither possible nor correct.
# readlink -f where it exists (GNU, macOS 12.3+), else walk the links by hand: the same
# fallback souffle-include.sh uses, and for the same reason, no interpreter and no coreutils.
self="$0"
if [ -L "$self" ]; then
r="$(readlink -f "$self" 2>/dev/null)" || r=""
if [ -z "$r" ]; then
r="$self"; while [ -L "$r" ]; do
t="$(readlink "$r")"
case "$t" in /*) r="$t";; *) r="$(dirname "$r")/$t";; esac
done
fi
self="$r"
fi
ROOT="$(d="$(cd "$(dirname "$self")" && pwd)"; while [ "$d" != / ] && { [ ! -f "$d/package.json" ] || [ ! -d "$d/graph" ]; }; do d="$(dirname "$d")"; done; echo "$d")"
# No marker found means every path built from ROOT is nonsense; say so here rather than fail
# later on a path the user cannot interpret.
[ -d "$ROOT/graph" ] || { echo "axiomcode: cannot locate the package root from $0 (resolved to $self)" >&2; exit 2; }
PARSER="${AXIOM_PARSER:-$ROOT/parser/dist/index.js}"
usage(){ sed -n '3,30p' "$0" | sed 's/^# \{0,1\}//'; }
die(){ echo "axiomcode: $*" >&2; exit 2; }
Expand Down
8 changes: 8 additions & 0 deletions graph/test/java/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ if ! bash "$ROOT/graph/test/tools/engine-id-locale-test.sh"; then
echo "aborting: the program text depends on the shell locale"
exit 1
fi
# ── The published package must be installable ──────────────────────────────
# Reads the pack manifest, so it costs a second. It guards defects that a source checkout
# cannot show: here the parser is built, the CLI is run by path and the dependencies are
# present, none of which is true of the tarball a user installs.
if ! bash "$ROOT/graph/test/tools/package-contents-test.sh"; then
echo "aborting: the published package would not be installable"
exit 1
fi
# ── The bundle stage must build the language-neutral output ─────────────────
# Every solve below ends by joining the raw relations to the IR and writing graph.sqlite
# (graph/bundle/SCHEMA.md); graph/*.csv is the same core tables and is written only under
Expand Down
66 changes: 66 additions & 0 deletions graph/test/tools/package-contents-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────────────
# What `npm pack` would actually put in the tarball, and whether an install of it could run.
#
# Every defect this guards against was invisible from a source checkout, which is why none of
# them was caught before: here the parser is built, the CLI is invoked by path, and the
# dependencies are present because a workspace install put them there. The published tarball
# has none of those advantages, and it failed on first use, for every language, on a machine
# where the advice it printed could not be followed.
#
# It reads the pack MANIFEST rather than installing anything, so it costs a second and needs
# no network. --ignore-scripts because `prepare` would otherwise run a full build and print
# over the JSON. The full acceptance test remains an install on a machine with no toolchain.
# ─────────────────────────────────────────────────────────────────────────────
set -uo pipefail
ROOT="$(d="$(cd "$(dirname "$0")" && pwd)"; while [ "$d" != / ] && { [ ! -f "$d/package.json" ] || [ ! -d "$d/graph" ]; }; do d="$(dirname "$d")"; done; echo "$d")"
cd "$ROOT"
command -v node >/dev/null 2>&1 || { echo "package-contents: SKIP (no node)"; exit 0; }

node - "$ROOT" <<'JS'
const {execFileSync}=require("child_process"), root=process.argv[2], fs=require("fs");
let fail=0, checks=0;
const ok =(m)=>{checks++; if(process.env.PACKAGE_CONTENTS_VERBOSE) console.log(" ok "+m);};
const bad=(m)=>{checks++; fail=1; console.log(" FAIL "+m);};

// ---- the manifest fields an installable package needs ----
const j=JSON.parse(fs.readFileSync(root+"/package.json","utf8"));
// Without bin there is no command after install; the CLI exists only as a path in node_modules.
(j.bin&&j.bin.axiomcode) ? ok("bin.axiomcode is declared") : bad("package.json has no bin.axiomcode entry");
// Without files npm falls back to the ignore rules and honours a WORKSPACE .gitignore, which
// is how parser/dist stopped shipping while parser/src/test-data did.
(Array.isArray(j.files)&&j.files.length) ? ok("files is declared") : bad("package.json has no files array");
// The parser ships inside this tarball but is a workspace and is never published, so nothing
// installs what it requires unless this package declares it.
const deps=j.dependencies||{}, pdeps=JSON.parse(fs.readFileSync(root+"/parser/package.json","utf8")).dependencies||{};
Object.keys(deps).length ? ok("runtime dependencies are declared") : bad("package.json declares no runtime dependencies");
for(const k of Object.keys(pdeps)) deps[k] ? ok("dependency hoisted: "+k) : bad("runtime dependency not hoisted from the parser workspace: "+k);

// ---- what the tarball would contain ----
let files;
try{
files=JSON.parse(execFileSync("npm",["pack","--dry-run","--json","--ignore-scripts"],
{cwd:root, encoding:"utf8", stdio:["ignore","pipe","ignore"], maxBuffer:64*1024*1024}))[0].files.map(f=>f.path);
}catch(e){ console.log(" FAIL npm pack --dry-run failed: "+e.message.split("\n")[0]); process.exit(1); }
const has=(p)=>files.includes(p);
const count=(re)=>files.filter(f=>re.test(f)).length;

has("bin/axiomcode") ? ok("ships bin/axiomcode") : bad("does NOT ship bin/axiomcode");
// The corpora are the bulk of the repository and run nothing for a consumer.
count(/^graph\/test\//)===0 ? ok("does not ship graph/test") : bad("ships "+count(/^graph\/test\//)+" files under graph/test");
count(/^parser\/src\/test-data\//)===0 ? ok("does not ship parser test-data") : bad("ships "+count(/^parser\/src\/test-data\//)+" files under parser/src/test-data");
// The rules ARE the engine; the CLI without them installs a tool that cannot solve.
for(const lang of ["java","typescript","python","javascript"]){
const n=count(new RegExp("^graph/"+lang+"/.*\\.dl$"));
n>0 ? ok("ships "+n+" "+lang+" rule files") : bad("ships no "+lang+" .dl rules");
}
// Build outputs can only be asserted when they exist. A source checkout that has not been
// built is not a failing package, so this SKIPS rather than failing -- but it must never skip
// silently, because that is exactly how the missing parser went unnoticed.
for(const [dir,probe] of [["dist/", "dist/reason.js"],["parser/dist/","parser/dist/index.js"]]){
if(!fs.existsSync(root+"/"+probe)){ console.log(" skip "+dir+" not built in this checkout (run npm run build to assert it ships)"); continue; }
count(new RegExp("^"+dir.replace("/","\\/")))>0 ? ok("ships "+dir) : bad("does NOT ship "+dir+" (it is built but excluded from the pack)");
}
console.log("package-contents: "+checks+" checks, "+(fail?"FAIL":"PASS"));
process.exit(fail);
JS
23 changes: 23 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,28 @@
"@axiomcode/engine-linux-x64": "0.1.0",
"@axiomcode/engine-linux-arm64": "0.1.0",
"@axiomcode/engine-win32-x64": "0.1.0"
},
"bin": {
"axiomcode": "bin/axiomcode"
},
"files": [
"bin/",
"dist/",
"graph/",
"!graph/test/",
"parser/dist/",
"parser/package.json",
"README.md",
"LICENSE.md"
],
"dependencies": {
"sax": "^1.4.4",
"tree-sitter": "^0.21.1",
"tree-sitter-c-sharp": "0.23.1",
"tree-sitter-groovy": "^0.1.2",
"tree-sitter-java": "^0.21.0",
"tree-sitter-python": "^0.21.0",
"typescript": "^6.0.0",
"yaml": "^2.8.2"
}
}