1818import subprocess
1919import sys
2020import tempfile
21+ import argparse
2122from dataclasses import dataclass
2223from pathlib import Path
2324from 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
2733RED = "\033 [0;31m"
3642SCRIPT_DIR = PROJECT_ROOT / "scripts"
3743RECIPE_FILE = PROJECT_ROOT / "recipe.yaml"
3844OUTPUT_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+
335437def 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
0 commit comments