Skip to content

Commit 9c284b1

Browse files
committed
Just: introduce scaffolding for common verbs, and apply to rust
1 parent 9dd3b33 commit 9c284b1

File tree

12 files changed

+275
-16
lines changed

12 files changed

+275
-16
lines changed

justfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import 'misc/just/lib.just'
2+
import 'misc/just/forward.just'

misc/bazel/justfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import '../just/lib.just'
2+
3+
[no-cd, positional-arguments, no-exit-message]
4+
hello +ARGS:
5+
@echo "hello from bzl" "$@"

misc/just/codeql-test-run.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import * as child_process from "child_process";
2+
import * as path from "path";
3+
4+
function codeqlTestRun(argv: string[]): number {
5+
const [language, args, ...plus] = argv;
6+
let codeql =
7+
process.env["SEMMLE_CODE"] ?
8+
path.join(process.env["SEMMLE_CODE"], "target", "intree", `codeql-${language}`, "codeql")
9+
:
10+
"codeql"
11+
;
12+
process.env["CODEQL_CONFIG_FILE"] ||= "." // disable the default implicit config file, but keep an explicit one
13+
let plus_options = plus.map(option => option.trim().split("\n").filter(option => option !== ""));
14+
let testing_level = 0;
15+
let parsed_args = args.split(" ").filter(arg => {
16+
if (arg === "") return false;
17+
if (/^\++$/.test(arg)) {
18+
testing_level = Math.max(testing_level, arg.length);
19+
return false;
20+
}
21+
return true;
22+
});
23+
if (parsed_args.every(arg => arg.startsWith("-"))) {
24+
parsed_args.push(".");
25+
}
26+
let invocation = [codeql, "test", "run", "-j0", ...parsed_args];
27+
for (let i = 0; i < Math.min(plus_options.length, testing_level); i++) {
28+
invocation.push(...plus_options[i]);
29+
}
30+
console.log(`${process.env["CMD_BEGIN"] || ""}${invocation.join(" ")}${process.env["CMD_END"] || ""}`);
31+
try {
32+
child_process.execFileSync(invocation[0], invocation.slice(1), { stdio: "inherit" });
33+
} catch (error) {
34+
return 1;
35+
}
36+
return 0;
37+
}
38+
39+
process.exit(codeqlTestRun(process.argv.slice(2)));

misc/just/forward-command.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import * as child_process from "child_process";
2+
import * as path from "path";
3+
import * as fs from "fs";
4+
5+
function commonDir(paths: string[]): string {
6+
if (paths.length === 0) return "";
7+
const splitPaths = paths.map(p => p.split(path.sep));
8+
let i;
9+
for (i = 0; i < splitPaths[0].length; i++) {
10+
if (!splitPaths.every(parts => parts[i] === splitPaths[0][i])) {
11+
break;
12+
}
13+
}
14+
const commonParts = splitPaths[0].slice(0, i);
15+
let ret = commonParts.join(path.sep);
16+
if (!fs.existsSync(ret)) {
17+
throw new Error(`Common directory does not exist: ${ret}`);
18+
}
19+
if (!fs.lstatSync(ret).isDirectory()) {
20+
ret = path.dirname(ret);
21+
}
22+
return ret;
23+
}
24+
25+
function forwardCommand(args: string[]): number {
26+
// Avoid infinite recursion
27+
if (args.length == 0) {
28+
console.error("No command provided");
29+
return 1;
30+
}
31+
const cmd = args[0];
32+
const envVariable = `__JUST_FORWARD_${cmd}`;
33+
if (process.env[envVariable]) {
34+
console.error(`No common ${cmd} handler found`);
35+
return 1;
36+
}
37+
process.env[envVariable] = "true";
38+
const cmdArgs = args.slice(1);
39+
const is_flag = /^(-.*|\++)$/; // + is used for testing level in some langauge tests
40+
const flags = cmdArgs.filter(arg => is_flag.test(arg));
41+
const positionalArgs = cmdArgs.filter(arg => !is_flag.test(arg));
42+
43+
if (positionalArgs.length === 0) {
44+
console.error("No positional arguments provided");
45+
return 1;
46+
}
47+
48+
const commonPath = commonDir(positionalArgs);
49+
let relativeArgs = positionalArgs.map(arg => path.relative(commonPath, arg) || ".");
50+
if (relativeArgs.length === 1 && relativeArgs[0] === ".") {
51+
relativeArgs = [];
52+
}
53+
54+
const invocation = [process.env["JUST_EXECUTABLE"] || "just", cmd, ...flags, ...relativeArgs];
55+
console.log(`-> ${commonPath}: just ${invocation.slice(1).join(" ")}`);
56+
try {
57+
child_process.execFileSync(invocation[0], invocation.slice(1), { stdio: "inherit", cwd: commonPath });
58+
} catch (error) {
59+
return 1;
60+
}
61+
return 0;
62+
}
63+
64+
process.exit(forwardCommand(process.argv.slice(2)));

misc/just/forward.just

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import "lib.just"
2+
3+
# copy&paste necessary for each command until proper forwarding of multiple args is implemented
4+
# see https://github.com/casey/just/issues/1988
5+
6+
_forward := tsx + source_dir() + "/forward-command.ts"
7+
8+
9+
[no-cd, positional-arguments, no-exit-message]
10+
@test *ARGS=".":
11+
{{ _forward }} test "$@"
12+
13+
14+
[no-cd, positional-arguments, no-exit-message]
15+
@build *ARGS=".":
16+
{{ _forward }} build "$@"
17+
18+
[no-cd, positional-arguments, no-exit-message]
19+
@generate *ARGS=".":
20+
{{ _forward }} generate "$@"
21+
22+
[no-cd, positional-arguments, no-exit-message]
23+
@lint *ARGS=".":
24+
{{ _forward }} lint "$@"
25+
26+
[no-cd, positional-arguments, no-exit-message]
27+
@format *ARGS=".":
28+
{{ _forward }} format "$@"

misc/just/lib.just

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
set fallback
2+
set allow-duplicate-recipes
3+
set allow-duplicate-variables
4+
set unstable
5+
6+
export PATH_SEP := if os() == "windows" { ";" } else { ":" }
7+
export JUST_EXECUTABLE := just_executable()
8+
9+
error := style("error") + "error" + NORMAL + ": "
10+
cmd_sep := "\n#--------------------------------------------------------\n"
11+
export CMD_BEGIN := style("command") + cmd_sep
12+
export CMD_END := cmd_sep + NORMAL
13+
14+
tsx := "npx tsx@4.19.0 "
15+
16+
import? '../../../semmle-code.just' # internal repo just file, if present
17+
import 'semmle-code-stub.just'
18+
19+
20+
[no-exit-message]
21+
@_require_semmle_code:
22+
{{ if SEMMLE_CODE == "" { '''
23+
echo "''' + error + ''' running this recipe requires doing so from an internal repository checkout" >&2
24+
exit 1
25+
''' } else { "" } }}
26+
27+
_build LANGUAGE: _require_semmle_code (_maybe_build LANGUAGE)
28+
29+
[no-exit-message]
30+
_maybe_build LANGUAGE:
31+
{{ cmd_sep }}{{ if SEMMLE_CODE == "" { '# using codeql from PATH, if any' } else { 'cd $SEMMLE_CODE; ./build target/intree/codeql-' + LANGUAGE } }}{{ cmd_sep }}
32+
33+
[no-cd, positional-arguments, no-exit-message]
34+
@_codeql_test LANGUAGE +ARGS: (_maybe_build LANGUAGE)
35+
{{ tsx + source_dir() }}/codeql-test-run.ts "$@"
36+
37+
[no-cd, positional-arguments, no-exit-message]
38+
@_codeql_test_run_only LANGUAGE +ARGS:
39+
{{ tsx + source_dir() }}/codeql-test-run.ts "$@"
40+
41+
42+
[no-cd, no-exit-message]
43+
_ql_format +ARGS: (_maybe_build "nolang")
44+
{{ cmd_sep }}{{ if SEMMLE_CODE != "" { '$SEMMLE_CODE/target/intree/codeql-nolang/' } else { '' } }}codeql query format --in-place $(find {{ ARGS }} -type f -name '*.ql' -or -name '*.qll'){{ cmd_sep }}
45+
46+
47+
[no-cd, no-exit-message]
48+
_bazel COMMAND *ARGS:
49+
{{ cmd_sep }}{{ if SEMMLE_CODE != "" { '$SEMMLE_CODE/tools/' } else { '' } }}bazel {{ COMMAND }} {{ ARGS }}{{ cmd_sep }}
50+
51+
[no-cd, no-exit-message]
52+
_integration_test *ARGS: _require_semmle_code (_run "$SEMMLE_CODE/tools/pytest" ARGS)
53+
54+
[no-cd]
55+
_run +ARGS:
56+
{{ cmd_sep }}{{ ARGS }}{{ cmd_sep }}
57+
58+
[no-cd, positional-arguments, no-exit-message]
59+
_just +ARGS:
60+
{{ JUST_EXECUTABLE }} "$@"
61+
62+
[no-cd]
63+
_black *ARGS=".": (_run "uv" "run" "black" ARGS)

misc/just/semmle-code-stub.just

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export SEMMLE_CODE := ""

rust/justfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import '../misc/just/lib.just'
2+
3+
install: (_bazel "run" "@codeql//rust:install")
4+
5+
build: generate (_build "rust")
6+
7+
generate: (_bazel "run" "@codeql//rust/codegen")
8+
9+
lint: (_run "python3" "lint.py")
10+
11+
format: (_run "python3" "lint.py" "--format-only")

rust/lint.py

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44
import pathlib
55
import shutil
66
import sys
7+
import argparse
8+
9+
10+
def options():
11+
parser = argparse.ArgumentParser(description="lint rust language pack code")
12+
parser.add_argument("--format-only", action="store_true", help="Only apply formatting")
13+
return parser.parse_args()
14+
715

816

917
def tool(name):
@@ -12,27 +20,35 @@ def tool(name):
1220
return ret
1321

1422

15-
this_dir = pathlib.Path(__file__).resolve().parent
23+
def main():
24+
args = options()
25+
this_dir = pathlib.Path(__file__).resolve().parent
26+
27+
28+
cargo = tool("cargo")
29+
bazel = tool("bazel")
30+
31+
runs = []
1632

17-
cargo = tool("cargo")
18-
bazel = tool("bazel")
1933

20-
runs = []
34+
def run(tool, args, *, cwd=this_dir):
35+
print("+", tool, args)
36+
runs.append(subprocess.run([tool] + args.split(), cwd=cwd))
2137

2238

23-
def run(tool, args, *, cwd=this_dir):
24-
print("+", tool, args)
25-
runs.append(subprocess.run([tool] + args.split(), cwd=cwd))
39+
# make sure bazel-provided sources are put in tree for `cargo` to work with them
40+
run(bazel, "run ast-generator:inject-sources")
41+
run(cargo, "fmt --all --quiet")
2642

43+
if not args.format_only:
44+
for manifest in this_dir.rglob("Cargo.toml"):
45+
if not manifest.is_relative_to(this_dir / "ql") and not manifest.is_relative_to(this_dir / "integration-tests"):
46+
run(cargo,
47+
"clippy --fix --allow-dirty --allow-staged --quiet -- -D warnings",
48+
cwd=manifest.parent)
2749

28-
# make sure bazel-provided sources are put in tree for `cargo` to work with them
29-
run(bazel, "run ast-generator:inject-sources")
30-
run(cargo, "fmt --all --quiet")
50+
return max(r.returncode for r in runs)
3151

32-
for manifest in this_dir.rglob("Cargo.toml"):
33-
if not manifest.is_relative_to(this_dir / "ql") and not manifest.is_relative_to(this_dir / "integration-tests"):
34-
run(cargo,
35-
"clippy --fix --allow-dirty --allow-staged --quiet -- -D warnings",
36-
cwd=manifest.parent)
3752

38-
sys.exit(max(r.returncode for r in runs))
53+
if __name__ == "__main__":
54+
sys.exit(main())

rust/ql/integration-tests/justfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import "../../../misc/just/lib.just"
2+
3+
4+
[no-cd]
5+
test *ARGS=".": (_just "generate") (_integration_test ARGS)
6+
7+
# TODO in separate PR
8+
# [no-cd]
9+
# format *ARGS=".": (_ql_format ARGS) (_black ARGS)

0 commit comments

Comments
 (0)