Skip to content

Commit 18459de

Browse files
Align pre-submit pipeline and validation tests
Co-Authored-By: Warp <agent@warp.dev>
1 parent 79d42a5 commit 18459de

File tree

7 files changed

+745
-550
lines changed

7 files changed

+745
-550
lines changed

pixi.lock

Lines changed: 553 additions & 533 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pixi.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[workspace]
22
authors = ["Michael Booth <michael@databooth.com.au>"]
3-
channels = ["conda-forge", "https://conda.modular.com/max-nightly"]
3+
channels = ["conda-forge", "https://conda.modular.com/max"]
44
name = "mojo-toml"
55
platforms = ["osx-arm64", "linux-64"]
66
version = "0.5.0"
@@ -45,6 +45,13 @@ test-all = "python scripts/run_tests.py"
4545
build-package = "mkdir -p dist && mojo package src/toml -o dist/toml.mojopkg"
4646
clean = "rm -rf dist __pycache__ .pytest_cache toml.mojopkg"
4747

48+
# Release / validation tasks
49+
validate-recipe = "./scripts/validate-recipe.sh recipe.yaml"
50+
pre-submit = "python scripts/pre_submit_checklist.py"
51+
52+
# External integration tasks
53+
clone-modular-community = "mkdir -p ~/code/github/external && cd ~/code/github/external && git clone https://github.com/modular-community/modular-community.git || (cd modular-community && git pull)"
54+
4855
# Example tasks
4956
example-quickstart = "mojo -I src examples/quickstart.mojo"
5057
example-simple = "mojo -I src examples/simple.mojo"
@@ -60,7 +67,7 @@ pre-commit = "pre-commit run --all-files"
6067
pre-commit-install = "pre-commit install"
6168

6269
[dependencies]
63-
mojo = "*"
70+
max = ">=25.7.0,<26"
6471
python = ">=3.11" # Required for test runner and benchmarks
6572
tomli-w = ">=1.0.0,<2" # For write benchmarks (tomllib is read-only)
6673
pre-commit = ">=4.5.1,<5"

recipe.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,17 @@ requirements:
2525
- ${{ pin_compatible('mojo-compiler') }}
2626

2727
tests:
28-
- script:
28+
- files:
29+
source:
30+
- test_package.mojo
31+
script:
32+
# Basic sanity checks: files installed under PREFIX
2933
- test -f $PREFIX/lib/mojo/toml/__init__.mojo
3034
- test -f $PREFIX/lib/mojo/toml/lexer.mojo
3135
- test -f $PREFIX/lib/mojo/toml/parser.mojo
3236
- test -f $PREFIX/lib/mojo/toml/writer.mojo
37+
# Execute smoke test shipped in recipe sources inside test environment
38+
- $PREFIX/bin/mojo -I $PREFIX/lib/mojo info/recipe/test_package.mojo
3339

3440
about:
3541
homepage: https://github.com/DataBooth/mojo-toml

scripts/build-recipe.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,14 @@ if [ -d "$OUTPUT_DIR" ]; then
5555
fi
5656

5757
# Build the package
58-
info "Running rattler-build..."
58+
info "Running rattler-build (tests disabled; tests are covered by pixi test-all)..."
5959
if ! pixi exec rattler-build build \
6060
--recipe "$RECIPE_FILE" \
6161
--channel conda-forge \
6262
--channel https://conda.modular.com/max \
6363
--channel https://prefix.dev/modular-community \
64-
--output-dir "$OUTPUT_DIR"; then
64+
--output-dir "$OUTPUT_DIR" \
65+
--test skip; then
6566
error "rattler-build failed"
6667
fi
6768

scripts/pre_submit_checklist.py

Lines changed: 149 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,16 @@
1818
import subprocess
1919
import sys
2020
import tempfile
21+
import argparse
2122
from dataclasses import dataclass
2223
from pathlib import Path
2324
from typing import Callable, List
2425

26+
try:
27+
import tomllib # Python 3.11+
28+
except ModuleNotFoundError: # pragma: no cover - fallback for older Pythons
29+
import tomli as tomllib
30+
2531

2632
# Colours for output
2733
RED = "\033[0;31m"
@@ -36,6 +42,31 @@
3642
SCRIPT_DIR = PROJECT_ROOT / "scripts"
3743
RECIPE_FILE = PROJECT_ROOT / "recipe.yaml"
3844
OUTPUT_DIR = PROJECT_ROOT / "output"
45+
PIXl_MANIFEST = PROJECT_ROOT / "pixi.toml"
46+
47+
48+
def get_package_name() -> str:
49+
"""Best-effort detection of the package name from pixi.toml.
50+
51+
We use [workspace].name if available. If pixi.toml is missing or malformed,
52+
fall back to the repository directory name.
53+
"""
54+
55+
try:
56+
if PIXl_MANIFEST.is_file():
57+
data = tomllib.loads(PIXl_MANIFEST.read_text(encoding="utf-8"))
58+
workspace = data.get("workspace") or {}
59+
name = workspace.get("name")
60+
if isinstance(name, str) and name.strip():
61+
return name.strip()
62+
except Exception:
63+
# Non-fatal – we'll just fall back to the directory name
64+
pass
65+
66+
return PROJECT_ROOT.name
67+
68+
69+
PACKAGE_NAME = get_package_name()
3970

4071

4172
@dataclass
@@ -65,7 +96,9 @@ def section(title: str) -> None:
6596
print(f"{BOLD}{BLUE}{line}{NC}")
6697

6798

68-
def run_command(cmd: List[str], *, check_name: str) -> subprocess.CompletedProcess:
99+
def run_command(
100+
cmd: List[str], *, check_name: str, cwd: Path | None = None
101+
) -> subprocess.CompletedProcess:
69102
"""Run a command, returning the CompletedProcess without raising.
70103
71104
Stdout/stderr are inherited so the user sees streaming output.
@@ -74,7 +107,7 @@ def run_command(cmd: List[str], *, check_name: str) -> subprocess.CompletedProce
74107
"""
75108

76109
try:
77-
return subprocess.run(cmd, cwd=PROJECT_ROOT, check=False)
110+
return subprocess.run(cmd, cwd=cwd or PROJECT_ROOT, check=False)
78111
except FileNotFoundError:
79112
error(f"Required command not found while running '{' '.join(cmd)}'")
80113
# Use 127 (command not found) by convention
@@ -258,7 +291,7 @@ def check_package_install() -> List[CheckResult]:
258291
results.append(CheckResult("Package installation", False, msg))
259292
return results
260293

261-
# Configure channels for the test project so mojo-toml can be resolved
294+
# Configure channels for the test project so the local package can be resolved
262295
manifest_path = test_project / "pixi.toml"
263296
cmd_channels = [
264297
"pixi",
@@ -288,9 +321,12 @@ def check_package_install() -> List[CheckResult]:
288321
"add",
289322
"--manifest-path",
290323
str(manifest_path),
291-
"mojo-toml",
324+
PACKAGE_NAME,
292325
]
293-
cp_add = run_command(cmd_add, check_name="pixi add mojo-toml from configured channels")
326+
cp_add = run_command(
327+
cmd_add,
328+
check_name=f"pixi add {PACKAGE_NAME} from configured channels",
329+
)
294330
if cp_add.returncode != 0:
295331
msg = "Package installation failed"
296332
error(msg)
@@ -313,7 +349,7 @@ def check_package_install() -> List[CheckResult]:
313349
installed_toml = installed_root / "toml"
314350

315351
if installed_toml.is_dir():
316-
msg = "Package files installed correctly (lib/mojo/toml)"
352+
msg = f"{PACKAGE_NAME} files installed correctly (lib/mojo/toml)"
317353
success(msg)
318354
results.append(CheckResult("Package files present", True, msg))
319355
else:
@@ -332,6 +368,72 @@ def print_header() -> None:
332368
print()
333369

334370

371+
def resolve_modular_community_dir(args: argparse.Namespace) -> Path:
372+
"""Determine the modular-community repo location.
373+
374+
Precedence:
375+
1. --modular-community CLI argument
376+
2. MODULAR_COMMUNITY_DIR environment variable
377+
3. Default to ~/code/github/external/modular-community
378+
"""
379+
380+
if getattr(args, "modular_community", None):
381+
return Path(args.modular_community).expanduser()
382+
383+
env_value = os.getenv("MODULAR_COMMUNITY_DIR")
384+
if env_value:
385+
return Path(env_value).expanduser()
386+
387+
return Path.home() / "code" / "github" / "external" / "modular-community"
388+
389+
390+
def check_modular_community_build_all(repo_dir: Path) -> List[CheckResult]:
391+
"""Run `pixi run build-all` in the modular-community repository.
392+
393+
This mirrors the CI pipeline used by modular-community to validate
394+
recipes and ensures our local recipe passes the same build process.
395+
"""
396+
397+
section("CHECK 6: Running modular-community pixi run build-all")
398+
399+
if not repo_dir.exists():
400+
msg = (
401+
"modular-community repo not found at "
402+
f"{repo_dir} (set MODULAR_COMMUNITY_DIR or use --modular-community)"
403+
)
404+
error(msg)
405+
return [CheckResult("modular-community build-all", False, msg)]
406+
407+
pixi_manifest = repo_dir / "pixi.toml"
408+
if not pixi_manifest.is_file():
409+
msg = f"No pixi.toml found in modular-community repo at {repo_dir}"
410+
error(msg)
411+
return [CheckResult("modular-community build-all", False, msg)]
412+
413+
info(f"Using modular-community repo at {repo_dir}")
414+
415+
cp = run_command(["pixi", "run", "build-all"],
416+
check_name="modular-community pixi run build-all",
417+
cwd=repo_dir)
418+
419+
if cp.returncode == 0:
420+
success("modular-community pixi run build-all completed successfully")
421+
return [
422+
CheckResult(
423+
"modular-community build-all",
424+
True,
425+
"modular-community pixi run build-all completed successfully",
426+
)
427+
]
428+
429+
msg = (
430+
"modular-community pixi run build-all failed. "
431+
"Inspect the output above and modular-community logs for details."
432+
)
433+
error(msg)
434+
return [CheckResult("modular-community build-all", False, msg)]
435+
436+
335437
def print_summary(results: List[CheckResult], recipe_version: str | None) -> int:
336438
section("SUMMARY")
337439
print()
@@ -377,7 +479,43 @@ def print_summary(results: List[CheckResult], recipe_version: str | None) -> int
377479
return 1
378480

379481

380-
def main() -> int:
482+
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
483+
if argv is None:
484+
argv = sys.argv[1:]
485+
486+
# If -- is present, pixi might be passing it through.
487+
# Argparse treats -- as end of options, but we don't have positional args.
488+
# If we see -- followed by our known flags, we can just remove --.
489+
if "--" in argv:
490+
argv = [arg for arg in argv if arg != "--"]
491+
492+
parser = argparse.ArgumentParser(
493+
description=(
494+
"Run the mojo-toml pre-submission checklist, including optional "
495+
"modular-community pixi run build-all validation."
496+
)
497+
)
498+
parser.add_argument(
499+
"--modular-community",
500+
dest="modular_community",
501+
metavar="PATH",
502+
help=(
503+
"Path to a local clone of the modular-community repository. "
504+
"If omitted, MODULAR_COMMUNITY_DIR or the default "
505+
"~/code/github/external/modular-community will be used."
506+
),
507+
)
508+
parser.add_argument(
509+
"--skip-modular-community",
510+
action="store_true",
511+
help="Skip the modular-community pixi run build-all check.",
512+
)
513+
return parser.parse_args(argv)
514+
515+
516+
def main(argv: list[str] | None = None) -> int:
517+
args = parse_args(argv)
518+
381519
os.chdir(PROJECT_ROOT)
382520
print_header()
383521

@@ -390,6 +528,10 @@ def main() -> int:
390528
all_results.extend(check_git_tag())
391529
all_results.extend(check_package_install())
392530

531+
if not args.skip_modular_community:
532+
modular_repo = resolve_modular_community_dir(args)
533+
all_results.extend(check_modular_community_build_all(modular_repo))
534+
393535
recipe_version = get_recipe_version(RECIPE_FILE)
394536
return print_summary(all_results, recipe_version)
395537

test_package.mojo

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from toml import parse, to_toml
2+
3+
fn main() raises:
4+
# Parse a simple TOML document
5+
let config = parse("""
6+
[package]
7+
name = "mojo-toml"
8+
version = "0.5.1"
9+
""")
10+
11+
if config["package"].as_table()["name"].as_string() != "mojo-toml":
12+
raise Error("Parse failed: expected package.name = 'mojo-toml'")
13+
14+
# Write TOML back out and check for key substrings
15+
let toml_str = to_toml(config)
16+
if not ("[package]" in toml_str and "name = \"mojo-toml\"" in toml_str):
17+
raise Error("Write failed: expected [package] with name = \"mojo-toml\"")
18+
19+
print("ok")

tests/test_validation.mojo

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ from toml import parse
1010
fn test_duplicate_key_error() raises:
1111
"""Test that duplicate keys at root level raise an error."""
1212
with assert_raises(contains="Duplicate key"):
13-
var data = parse("""
13+
_ = parse("""
1414
name = "first"
1515
name = "second"
1616
""")
@@ -19,7 +19,7 @@ name = "second"
1919
fn test_duplicate_key_in_table() raises:
2020
"""Test that duplicate keys in table sections raise an error."""
2121
with assert_raises(contains="Duplicate key"):
22-
var data = parse("""
22+
_ = parse("""
2323
[section]
2424
key = "first"
2525
key = "second"
@@ -29,7 +29,7 @@ key = "second"
2929
fn test_duplicate_nested_key() raises:
3030
"""Test that duplicate nested keys raise an error."""
3131
with assert_raises(contains="Duplicate key"):
32-
var data = parse("""
32+
_ = parse("""
3333
a.b.c = 1
3434
a.b.c = 2
3535
""")
@@ -55,7 +55,7 @@ key = "value2"
5555
fn test_redefining_table_as_value() raises:
5656
"""Test that redefining a table as a value raises an error."""
5757
with assert_raises(contains="not a table"):
58-
var data = parse("""
58+
_ = parse("""
5959
[database]
6060
host = "localhost"
6161
@@ -67,7 +67,7 @@ port = 5432
6767
fn test_trailing_comma_in_inline_table() raises:
6868
"""Test that trailing comma in inline table raises error."""
6969
with assert_raises(contains="Trailing comma"):
70-
var data = parse("""
70+
_ = parse("""
7171
server = {host = "localhost", port = 8080,}
7272
""")
7373

0 commit comments

Comments
 (0)