Skip to content

Commit ef778ac

Browse files
committed
fix: two files shipped in 0.3.0 that cannot compile from a tarball
`ytsaurus-skiff/tests/wire.rs` and `ytsaurus-job/tests/skiff_reader_tests.rs` `include_str!` reference vectors from `tests/skiff-go-interop/`, which is outside their crates. Those macros resolve at compile time, so both files compile in this repository and cannot compile from the published `.crate` — the fixtures are not in the tarball and no consumer can put them there. Verified against the actual artifacts downloaded from crates.io: of the nine published, `ytsaurus-skiff` and `ytsaurus-job` are the two whose `cargo test --no-run` fails. Consumers are unaffected: cargo does not build a dependency's tests. It bites whoever unpacks the crate and runs its suite, and vendoring or packaging workflows that do the same. Four files of this kind were found and excluded before 0.3.0 went out. These two were missed, and the reason is worth recording: the search was a line-based grep for include_str!("../../../tests/… which does not match hex_fixture(include_str!( "../../../tests/…" )) — the same macro with a newline in it. The audit that caught the other four did not catch these either. So `scripts/check-package-includes.sh` replaces the grep. It asks cargo which files actually ship, parses each with a multi-line-aware regex, resolves every include target and fails if one escapes its crate root. It runs in CI, and it was checked to fail: re-including `tests/wire.rs` makes it exit 1 naming the file and the three fixtures it reaches. 0.3.0 is published and cannot be replaced. This lands the fix so it is correct from here, and so nothing else ships with it.
1 parent 55e299a commit ef778ac

4 files changed

Lines changed: 104 additions & 7 deletions

File tree

.github/workflows/ci.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ jobs:
8686
cargo xtask generate-protos
8787
git diff --exit-code -- crates/ytsaurus-proto/src/generated
8888
89+
# `include_str!`/`include_bytes!` resolve at compile time, so a published
90+
# file reaching outside its own crate builds here and cannot build from a
91+
# tarball. `cargo package` does not catch it — it verifies by building the
92+
# library, not the tests — and 0.3.0 shipped two such files because the
93+
# check for them was a line-based grep and the macros had newlines in
94+
# them. This parses instead.
95+
- name: No published file reaches outside its own crate
96+
run: ./scripts/check-package-includes.sh
97+
8998
- name: cargo fmt --check
9099
run: cargo fmt --all -- --check
91100

crates/ytsaurus-job/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ categories = ["encoding"]
2424
# time, so shipping that file gives anyone who unpacks the crate and runs
2525
# `cargo test` a build error and no way to fix it. The other e2e tests build the
2626
# example workers, which *are* published, and are left in.
27-
exclude = ["examples/selfrun.rs", "tests/cat_e2e.rs"]
27+
exclude = ["examples/selfrun.rs", "tests/cat_e2e.rs", "tests/skiff_reader_tests.rs"]
2828

2929
[dependencies]
3030
ytsaurus-yson.workspace = true

crates/ytsaurus-skiff/Cargo.toml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ readme = "README.md"
1919
keywords = ["ytsaurus", "skiff", "serialization", "mapreduce"]
2020
categories = ["encoding"]
2121

22-
# `tests/cpp_interop.rs` `include_str!`s the C++ reference vectors from
23-
# `tests/skiff-cpp-interop/`, which is outside this package and so is not in the
24-
# tarball. `include_str!` resolves at compile time, so shipping the file would
25-
# hand anyone who unpacks the crate a build error they cannot fix. The vectors
26-
# and the test both live in the repository, where they run.
27-
exclude = ["tests/cpp_interop.rs"]
22+
# `tests/cpp_interop.rs` and `tests/wire.rs` `include_str!` reference vectors
23+
# from `tests/skiff-cpp-interop/` and `tests/skiff-go-interop/`, which are
24+
# outside this package and so are not in the tarball. `include_str!` resolves at
25+
# compile time, so shipping either hands anyone who unpacks the crate a build
26+
# error they cannot fix. The vectors and the tests both live in the repository,
27+
# where they run. `scripts/check-package-includes.sh` is what keeps this list
28+
# honest.
29+
exclude = ["tests/cpp_interop.rs", "tests/wire.rs"]
2830

2931
[dependencies]
3032
ytsaurus-yson.workspace = true

scripts/check-package-includes.sh

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/env bash
2+
#
3+
# No published file may `include_str!`/`include_bytes!` something outside its own
4+
# crate.
5+
#
6+
# Those macros resolve at **compile time**, so a file that reaches into
7+
# `tests/skiff-go-interop/` or `tests/cluster-e2e/fixtures/` compiles here and
8+
# cannot compile from a `.crate` tarball — the fixture is not in it, and no
9+
# consumer can put it there. `cargo package` does not catch this: it verifies by
10+
# building the library, not the tests.
11+
#
12+
# `ytsaurus-skiff` 0.3.0 and `ytsaurus-job` 0.3.0 shipped with exactly that
13+
# defect. It was looked for beforehand, with a line-based grep, which matched
14+
#
15+
# include_str!("../../../tests/…")
16+
#
17+
# and missed
18+
#
19+
# hex_fixture(include_str!(
20+
# "../../../tests/…"
21+
# ))
22+
#
23+
# — the same macro with a newline in it. Hence a real parse below rather than a
24+
# pattern, and hence this running in CI rather than being remembered.
25+
#
26+
# The fix for a violation is an `exclude` entry in that crate's Cargo.toml, the
27+
# way `ytsaurus-skiff` and `ytsaurus-job` already carry one.
28+
29+
set -euo pipefail
30+
31+
cd "$(dirname "$0")/.."
32+
33+
python3 - <<'PY'
34+
import re, subprocess, sys, json
35+
from pathlib import Path
36+
37+
# Multi-line by construction: `re.S` plus `\s*` across the paren and the string.
38+
PAT = re.compile(r'include_(?:str|bytes)!\s*\(\s*"([^"]+)"', re.S)
39+
40+
meta = json.loads(subprocess.run(
41+
["cargo", "metadata", "--no-deps", "--format-version", "1"],
42+
capture_output=True, text=True, check=True).stdout)
43+
44+
bad = []
45+
for pkg in meta["packages"]:
46+
if pkg.get("publish") == []: # publish = false
47+
continue
48+
root = Path(pkg["manifest_path"]).parent
49+
50+
# Ask cargo what actually ships, so an `exclude`d file is not reported.
51+
listed = subprocess.run(
52+
["cargo", "package", "--list", "--allow-dirty", "-p", pkg["name"]],
53+
capture_output=True, text=True)
54+
if listed.returncode != 0:
55+
print(f"warning: could not list package {pkg['name']}", file=sys.stderr)
56+
continue
57+
shipped = {line.strip() for line in listed.stdout.splitlines() if line.strip()}
58+
59+
for rel in sorted(shipped):
60+
if not rel.endswith(".rs"):
61+
continue
62+
f = root / rel
63+
if not f.is_file():
64+
continue
65+
try:
66+
src = f.read_text()
67+
except (OSError, UnicodeDecodeError):
68+
continue
69+
for m in PAT.finditer(src):
70+
target = (f.parent / m.group(1)).resolve()
71+
try:
72+
target.relative_to(root.resolve())
73+
except ValueError:
74+
bad.append((pkg["name"], rel, m.group(1)))
75+
76+
if bad:
77+
print("ERROR: published files include data from outside their own crate.")
78+
print("These compile here and cannot compile from a crates.io tarball.\n")
79+
for name, rel, target in bad:
80+
print(f" {name}: {rel}")
81+
print(f" -> {target}")
82+
print("\nAdd the file to that crate's `exclude` in Cargo.toml.")
83+
sys.exit(1)
84+
85+
print("OK: no published file reaches outside its own crate")
86+
PY

0 commit comments

Comments
 (0)