feat(version-scanner): support target list inputs and refine python version checks#17475
feat(version-scanner): support target list inputs and refine python version checks#17475chalmerlowe wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request enhances the version scanner script to support scanning for multiple dependency and version targets simultaneously. It introduces a new --targets CLI argument that accepts a file path or inline YAML/JSON string, adds a new dependency_requirement rule, and updates reporting to include file names, dependencies, and versions. The accompanying unit tests have been updated and expanded to cover these new features. The review feedback suggests several robustness improvements: explicitly handling non-existent file paths to avoid misleading YAML/JSON parsing errors, filtering out None values when dependencies are defined without versions, and tightening CLI argument validation to prevent invalid combinations of --targets and individual target options.
| if os.path.exists(targets_input): | ||
| try: | ||
| with open(targets_input, 'r', encoding='utf-8') as f: | ||
| content = f.read() | ||
| except PermissionError: | ||
| print(f"Error: Permission denied reading targets file: {targets_input}", file=sys.stderr) | ||
| sys.exit(1) | ||
| except Exception as e: | ||
| print(f"Error reading targets file {targets_input}: {e}", file=sys.stderr) | ||
| sys.exit(1) |
There was a problem hiding this comment.
If the targets_input is a file path that does not exist, os.path.exists returns False, causing the scanner to treat the filename as an inline YAML/JSON string. This leads to a misleading error message: Error: Targets input must resolve to a JSON object or YAML mapping. We should check if the input looks like a file path and explicitly report if it is not found.
| if os.path.exists(targets_input): | |
| try: | |
| with open(targets_input, 'r', encoding='utf-8') as f: | |
| content = f.read() | |
| except PermissionError: | |
| print(f"Error: Permission denied reading targets file: {targets_input}", file=sys.stderr) | |
| sys.exit(1) | |
| except Exception as e: | |
| print(f"Error reading targets file {targets_input}: {e}", file=sys.stderr) | |
| sys.exit(1) | |
| looks_like_file = not ("{" in targets_input or "\n" in targets_input) and ( | |
| os.path.splitext(targets_input)[1] in [".yaml", ".yml", ".json"] or not ":" in targets_input | |
| ) | |
| if looks_like_file and not os.path.exists(targets_input): | |
| print(f"Error: Targets file not found: {targets_input}", file=sys.stderr) | |
| sys.exit(1) | |
| if os.path.exists(targets_input): | |
| try: | |
| with open(targets_input, 'r', encoding='utf-8') as f: | |
| content = f.read() | |
| except PermissionError: | |
| print(f"Error: Permission denied reading targets file: {targets_input}", file=sys.stderr) | |
| sys.exit(1) | |
| except Exception as e: | |
| print(f"Error reading targets file {targets_input}: {e}", file=sys.stderr) | |
| sys.exit(1) |
| targets = [] | ||
| for dep, versions in raw_targets.items(): | ||
| if isinstance(versions, list): | ||
| for v in versions: | ||
| targets.append((str(dep), str(v))) | ||
| else: | ||
| targets.append((str(dep), str(versions))) |
There was a problem hiding this comment.
If a dependency in the targets YAML/JSON is defined without a version (e.g., python: with no value, which parses as None), the scanner will append ("python", "None") and scan for the literal string "None". We should handle None values to avoid unexpected scanning behavior.
| targets = [] | |
| for dep, versions in raw_targets.items(): | |
| if isinstance(versions, list): | |
| for v in versions: | |
| targets.append((str(dep), str(v))) | |
| else: | |
| targets.append((str(dep), str(versions))) | |
| targets = [] | |
| for dep, versions in raw_targets.items(): | |
| if versions is None: | |
| print(f"Warning: No versions specified for dependency '{dep}'. Skipping.", file=sys.stderr) | |
| continue | |
| if isinstance(versions, list): | |
| for v in versions: | |
| if v is not None: | |
| targets.append((str(dep), str(v))) | |
| else: | |
| targets.append((str(dep), str(versions))) |
| has_single_target = bool(args.dependency and args.version) | ||
| has_targets_list = bool(args.targets) | ||
|
|
||
| if not (has_single_target or has_targets_list): | ||
| parser.error("Must specify either (-d/--dependency AND -v/--version) OR (--targets)") | ||
| if has_single_target and has_targets_list: | ||
| parser.error("Cannot specify both single target (-d/-v) and targets list (--targets)") |
There was a problem hiding this comment.
The current validation logic allows invalid combinations such as specifying --targets along with only -d (without -v), which silently ignores -d. We should explicitly disallow any individual target options (-d or -v) when --targets is used, and require both -d and -v when --targets is not used.
| has_single_target = bool(args.dependency and args.version) | |
| has_targets_list = bool(args.targets) | |
| if not (has_single_target or has_targets_list): | |
| parser.error("Must specify either (-d/--dependency AND -v/--version) OR (--targets)") | |
| if has_single_target and has_targets_list: | |
| parser.error("Cannot specify both single target (-d/-v) and targets list (--targets)") | |
| if args.targets: | |
| if args.dependency or args.version: | |
| parser.error("Cannot specify both targets list (--targets) and individual target options (-d/-v)") | |
| else: | |
| if not (args.dependency and args.version): | |
| parser.error("Must specify both -d/--dependency and -v/--version when not using --targets") |
This pull request enables scanning for multiple dependency and version targets (e.g.
python [3.7, 3.8, 3.9]) in a single scan, easing monorepo compatibility testing.Additional Key changes:
--targetsoption to parse YAML/JSON files or inline strings.regex_config.yamlto cover subscript minor checks (sys.version_info[1]) and optional patch numbers.