Coverage for src / rhiza_hooks / check_makefile_targets.py: 100%

40 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-08 08:53 +0000

1#!/usr/bin/env python3 

2"""Check that Makefile contains expected targets for rhiza projects.""" 

3 

4from __future__ import annotations 

5 

6import argparse 

7import re 

8import sys 

9from pathlib import Path 

10 

11# Common targets expected in rhiza-based projects 

12RECOMMENDED_TARGETS = { 

13 "install", 

14 "test", 

15 "fmt", 

16 "help", 

17} 

18 

19# Pattern to match Makefile target definitions 

20TARGET_PATTERN = re.compile(r"^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:", re.MULTILINE) 

21 

22 

23def extract_targets(content: str) -> set[str]: 

24 """Extract target names from Makefile content. 

25 

26 Args: 

27 content: Contents of a Makefile 

28 

29 Returns: 

30 Set of target names found 

31 """ 

32 matches = TARGET_PATTERN.findall(content) 

33 return set(matches) 

34 

35 

36def check_makefile(filepath: Path) -> list[str]: 

37 """Check a Makefile for recommended targets. 

38 

39 Args: 

40 filepath: Path to the Makefile 

41 

42 Returns: 

43 List of warning messages (empty if all recommended targets exist) 

44 """ 

45 warnings: list[str] = [] 

46 

47 try: 

48 content = filepath.read_text() 

49 except FileNotFoundError: 

50 return [f"File not found: {filepath}"] 

51 

52 targets = extract_targets(content) 

53 

54 # Only check the main Makefile for recommended targets 

55 if filepath.name == "Makefile": 

56 missing = RECOMMENDED_TARGETS - targets 

57 if missing: 

58 warnings.append(f"Missing recommended targets: {', '.join(sorted(missing))}") 

59 

60 return warnings 

61 

62 

63def main(argv: list[str] | None = None) -> int: 

64 """Main entry point for the hook.""" 

65 parser = argparse.ArgumentParser(description="Check Makefile for recommended targets") 

66 parser.add_argument( 

67 "filenames", 

68 nargs="*", 

69 help="Filenames to check", 

70 ) 

71 parser.add_argument( 

72 "--strict", 

73 action="store_true", 

74 help="Exit with error if recommended targets are missing", 

75 ) 

76 args = parser.parse_args(argv) 

77 

78 retval = 0 

79 for filename in args.filenames: 

80 filepath = Path(filename) 

81 warnings = check_makefile(filepath) 

82 if warnings: 

83 print(f"{filename}:") 

84 for warning in warnings: 

85 print(f" - {warning}") 

86 if args.strict: 

87 retval = 1 

88 

89 return retval 

90 

91 

92if __name__ == "__main__": 

93 sys.exit(main())