From 3ba53d5306d0efce94078d44434d03819dc7185b Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 11 Oct 2026 11:04:02 +0200 Subject: [PATCH 1/2] Centralize tested profiles and add read-only diagnostics --- .github/workflows/test.yml | 54 ++++++++++++++++---- README.md | 6 +++ compatibility.json | 14 ++++++ docs/compatibility.md | 20 ++++++++ scripts/compatibility.py | 65 ++++++++++++++++++++++++ scripts/doctor.py | 94 +++++++++++++++++++++++++++++++++++ scripts/test_compatibility.py | 39 +++++++++++++++ scripts/test_doctor.py | 41 +++++++++++++++ 8 files changed, 322 insertions(+), 11 deletions(-) create mode 100644 compatibility.json create mode 100644 docs/compatibility.md create mode 100644 scripts/compatibility.py create mode 100644 scripts/doctor.py create mode 100644 scripts/test_compatibility.py create mode 100644 scripts/test_doctor.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9c6eaee..c2bfc55 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,19 +26,27 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: path: source/modules/antono2/vkmemalloc + - name: Validate and resolve tested compatibility + id: compat + shell: bash + working-directory: ${{ github.workspace }} + run: | + python source/modules/antono2/vkmemalloc/scripts/test_compatibility.py + python source/modules/antono2/vkmemalloc/scripts/test_doctor.py + python source/modules/antono2/vkmemalloc/scripts/compatibility.py --github-output "$GITHUB_OUTPUT" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: antono2/vulkan - ref: v3.2.2 + ref: ${{ steps.compat.outputs.module_vulkan }} path: source/modules/antono2/vulkan - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: antono2/memory - ref: v1.4.0 + ref: ${{ steps.compat.outputs.module_memory }} path: source/modules/antono2/memory - uses: prantlf/setup-v-action@92f20bea50d2c9931a28c96bb4997a3c7357baf0 # v4 with: - version: 0.5.2 + version: ${{ steps.compat.outputs.v_release }} - name: Verify the declared dependency graph shell: bash run: v run source/modules/antono2/vkmemalloc/scripts/check_installed_deps.vsh @@ -83,25 +91,33 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: path: source/modules/antono2/vkmemalloc + - name: Validate and resolve tested compatibility + id: compat + shell: bash + working-directory: ${{ github.workspace }} + run: | + python source/modules/antono2/vkmemalloc/scripts/test_compatibility.py + python source/modules/antono2/vkmemalloc/scripts/test_doctor.py + python source/modules/antono2/vkmemalloc/scripts/compatibility.py --github-output "$GITHUB_OUTPUT" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: antono2/vulkan - ref: v3.2.2 + ref: ${{ steps.compat.outputs.module_vulkan }} path: source/modules/antono2/vulkan - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: antono2/memory - ref: v1.4.0 + ref: ${{ steps.compat.outputs.module_memory }} path: source/modules/antono2/memory - uses: prantlf/setup-v-action@92f20bea50d2c9931a28c96bb4997a3c7357baf0 # v4 with: - version: 0.5.2 + version: ${{ steps.compat.outputs.v_release }} - name: Verify the declared dependency graph shell: bash run: v run source/modules/antono2/vkmemalloc/scripts/check_installed_deps.vsh - name: Install Vulkan SDK and SwiftShader test artifacts shell: bash - run: bash source/modules/antono2/vulkan/.github/scripts/setup-vulkan-ci.sh 1.4.309.0 v1.2 + run: bash source/modules/antono2/vulkan/.github/scripts/setup-vulkan-ci.sh ${{ steps.compat.outputs.ci_sdk }} ${{ steps.compat.outputs.ci_runtime }} - name: Install binding-matched Vulkan headers and Volk shell: bash working-directory: source/modules/antono2/vulkan @@ -148,6 +164,14 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: path: source/modules/antono2/vkmemalloc + - name: Validate and resolve tested compatibility + id: compat + shell: bash + working-directory: ${{ github.workspace }} + run: | + python source/modules/antono2/vkmemalloc/scripts/test_compatibility.py + python source/modules/antono2/vkmemalloc/scripts/test_doctor.py + python source/modules/antono2/vkmemalloc/scripts/compatibility.py --github-output "$GITHUB_OUTPUT" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: antono2/vulkan @@ -156,7 +180,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: antono2/memory - ref: v1.4.0 + ref: ${{ steps.compat.outputs.module_memory }} path: source/modules/antono2/memory - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: @@ -195,19 +219,27 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: path: source/modules/antono2/vkmemalloc + - name: Validate and resolve tested compatibility + id: compat + shell: bash + working-directory: ${{ github.workspace }} + run: | + python source/modules/antono2/vkmemalloc/scripts/test_compatibility.py + python source/modules/antono2/vkmemalloc/scripts/test_doctor.py + python source/modules/antono2/vkmemalloc/scripts/compatibility.py --github-output "$GITHUB_OUTPUT" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: antono2/vulkan - ref: v3.2.2 + ref: ${{ steps.compat.outputs.module_vulkan }} path: source/modules/antono2/vulkan - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: antono2/memory - ref: v1.4.0 + ref: ${{ steps.compat.outputs.module_memory }} path: source/modules/antono2/memory - uses: prantlf/setup-v-action@92f20bea50d2c9931a28c96bb4997a3c7357baf0 # v4 with: - version: 0.5.2 + version: ${{ steps.compat.outputs.v_release }} - name: Verify the declared dependency graph shell: bash run: v run source/modules/antono2/vkmemalloc/scripts/check_installed_deps.vsh diff --git a/README.md b/README.md index a4ee41b..2d74c5e 100644 --- a/README.md +++ b/README.md @@ -451,3 +451,9 @@ v run examples/image_suballocation CI executes all examples against Mesa's CPU Vulkan implementation with the Khronos validation layer enabled, so it does not depend on a hardware GPU and binding-rule regressions remain visible. + + +## Compatibility and diagnostics + +See [tested profiles and read-only diagnostics](docs/compatibility.md) for +compiler/dependency versions, setup recovery actions and upgrade checks. diff --git a/compatibility.json b/compatibility.json new file mode 100644 index 0000000..fa726e4 --- /dev/null +++ b/compatibility.json @@ -0,0 +1,14 @@ +{ + "schema_version": 1, + "v_release": "0.5.2", + "ci_sdk": "1.4.309.0", + "ci_runtime": "v1.2", + "modules": { + "vulkan": "v3.2.2", + "memory": "v1.4.0" + }, + "build_tools": [ + "git", + "cmake" + ] +} diff --git a/docs/compatibility.md b/docs/compatibility.md new file mode 100644 index 0000000..926d3f7 --- /dev/null +++ b/docs/compatibility.md @@ -0,0 +1,20 @@ +# Tested compatibility and diagnostics + +`compatibility.json` records the existing tested compiler, bootstrap, native and +module versions. CI resolves these values before dependency checkout and rejects +disagreement with `v.mod`. Advisory upstream-master lanes remain advisory. +Registry/version files owned by binding generators remain authoritative for +generated API contents; upstream update workflows deliberately select candidates +separately from the tested compiler profiles. + +Upgrade the manifest and affected VPM declarations together. Run +`python scripts/test_compatibility.py` and the full project CI. Consumer application +compilation/runtime checks are also required before publishing a dependency upgrade. +The resolver's `--modules PATH` option verifies installed Git tags without fetching. + +Run `python scripts/doctor.py --v PATH_TO_V --modules PATH_TO_MODULES` for read-only +compiler/tool/dependency diagnostics. Add `--json` for a support report and +`--runtime` for Vulkan device enumeration when Vulkan tools are available. Missing +or mismatched prerequisites include a recovery action; diagnostics never repair +caches or change the environment. Enumeration alone does not prove hardware video +decoding or OpenCL interoperability. Python 3.9 or newer is required for these tools. diff --git a/scripts/compatibility.py b/scripts/compatibility.py new file mode 100644 index 0000000..4e33944 --- /dev/null +++ b/scripts/compatibility.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +"""Resolve tested compiler/native/module profiles and validate VPM declarations.""" +import argparse +import json +from pathlib import Path +import re +import subprocess + + +def resolve(root, profile='stable'): + pins = json.loads((root / 'compatibility.json').read_text()) + if pins.get('schema_version') != 1: + raise ValueError('Unsupported compatibility schema_version') + if 'v_release' in pins and not re.fullmatch(r'[0-9]+\.[0-9]+\.[0-9]+', pins['v_release']): + raise ValueError('v_release must be a released compiler version') + upstream = root / 'third_party/upstream.json' + if upstream.is_file() and pins.get('glfw') != json.loads(upstream.read_text())['version']: + raise ValueError('Native GLFW version and compatibility.json disagree') + for key, value in pins.items(): + if key in ['v', 'vc', 'tcc', 'volk', 'generator'] or key.endswith(('_v', '_vc', '_tcc')): + if not re.fullmatch('[0-9a-f]{40}', value): + raise ValueError(f'{key} must be an immutable Git commit') + declared = dict(re.findall(r"'antono2\.([a-z0-9_]+)@([^']+)'", (root / 'v.mod').read_text())) + for name, tag in declared.items(): + if pins.get('modules', {}).get(name) != tag: + raise ValueError(f'v.mod and compatibility.json disagree for {name}') + outputs = {key: value for key, value in pins.items() if isinstance(value, str)} + for name, tag in pins.get('modules', {}).items(): + if not re.fullmatch('[a-z0-9_]+', name) or not re.fullmatch(r'v[0-9]+\.[0-9]+\.[0-9]+(?:-standard)?', tag): + raise ValueError(f'Invalid module pin: {name}={tag}') + outputs['module_' + name] = tag + outputs['selected_v'] = 'master' if profile == 'master' else (pins['v3_v'] if profile == 'v3' else pins.get('v', pins.get('v_release', ''))) + for key, value in outputs.items(): + if not re.fullmatch('[a-z0-9_]+', key) or '\n' in value or '\r' in value: + raise ValueError(f'Unsafe compatibility output: {key}') + return outputs + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--root', type=Path, default=Path(__file__).resolve().parents[1]) + parser.add_argument('--profile', choices=['stable', 'v3', 'master'], default='stable') + parser.add_argument('--github-output', type=Path) + parser.add_argument('--get') + parser.add_argument('--modules', type=Path) + args = parser.parse_args() + outputs = resolve(args.root, args.profile) + if args.modules: + for key, tag in outputs.items(): + if not key.startswith('module_'): + continue + checkout = args.modules / 'antono2' / key[len('module_'):] + def revision(ref): + return subprocess.check_output(['git', '-C', str(checkout), 'rev-parse', '--verify', ref], text=True).strip() + if revision('HEAD') != revision(f'refs/tags/{tag}^{{commit}}'): + raise ValueError(f'Installed module does not match {tag}: {checkout}') + if args.github_output: + with args.github_output.open('a', encoding='utf-8') as output: + for key, value in outputs.items(): + output.write(f'{key}={value}\n') + print(outputs[args.get] if args.get else json.dumps(outputs, indent=2)) + + +if __name__ == '__main__': + main() diff --git a/scripts/doctor.py b/scripts/doctor.py new file mode 100644 index 0000000..55ac920 --- /dev/null +++ b/scripts/doctor.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +"""Read-only build/runtime diagnostics; use --json for a support report.""" +import argparse +import json +import os +from pathlib import Path +import re +import shutil +import subprocess + + +def probe(command, timeout=15): + try: + result = subprocess.run(command, capture_output=True, text=True, errors='replace', timeout=timeout) + return result.returncode == 0, (result.stdout + result.stderr).strip() + except (OSError, subprocess.TimeoutExpired) as error: + return False, str(error) + + +def diagnose(root, compiler=None, modules=None, sdk=None, runtime=False): + checks = [] + def check(name, passed, detail, action, optional=False): + checks.append({'name': name, 'status': 'pass' if passed else ('warn' if optional else 'fail'), + 'detail': detail, 'action': '' if passed else action}) + try: + pins = json.loads((root / 'compatibility.json').read_text()) + if pins.get('schema_version') != 1: + raise ValueError('Unsupported schema_version') + except (OSError, ValueError) as error: + check('Compatibility manifest', False, str(error), 'Restore compatibility.json from this project revision.') + return {'checks': checks} + declared = dict(re.findall(r"'antono2\.([a-z0-9_]+)@([^']+)'", (root / 'v.mod').read_text())) + disagreement = [name for name, tag in declared.items() if pins.get('modules', {}).get(name) != tag] + check('Declared dependencies', not disagreement, ', '.join(disagreement) or 'Manifest agrees with v.mod', + 'Update compatibility.json and v.mod together; use the tested dependency versions.') + for tool in pins.get('build_tools', ['git', 'cmake']): + location = shutil.which(tool) + check(tool, bool(location), location or 'Not found on PATH', f'Install {tool}, reopen your shell and rerun diagnostics.') + compiler = str(compiler) if compiler else (os.environ.get('VEXE') or shutil.which('v')) + if compiler: + passed, detail = probe([compiler, '-version']) + expected = pins.get('v', '')[:7] or pins.get('v_release', '') + matches = passed and (not expected or expected in detail) + check('V compiler', matches, detail, 'Select the tested V compiler from compatibility.json or rerun the setup helper.') + else: + check('V compiler', False, 'No V compiler found', 'Run the project setup helper, or pass --v with the tested compiler path.') + modules = modules or os.environ.get('VMODULES') + if modules: + for name, tag in pins.get('modules', {}).items(): + checkout = Path(modules) / 'antono2' / name + head_ok, head = probe(['git', '-C', str(checkout), 'rev-parse', '--verify', 'HEAD']) + tag_ok, wanted = probe(['git', '-C', str(checkout), 'rev-parse', '--verify', f'refs/tags/{tag}^{{commit}}']) + check('Module ' + name, head_ok and tag_ok and head == wanted, f'{checkout}: expected {tag}', + 'Preserve local changes and use a fresh module/cache directory with the declared releases.') + else: + check('Installed modules', False, 'VMODULES is not set', 'Pass --modules with the module directory used by setup.', optional=True) + sdk = sdk or os.environ.get('VULKAN_SDK') + needs_vulkan = 'ci_sdk' in pins or 'vulkan' in pins.get('modules', {}) + if needs_vulkan and os.name == 'nt': + available = bool(sdk) and (Path(sdk) / 'Lib/vulkan-1.lib').is_file() + check('Vulkan SDK', available, str(sdk or 'Not configured'), 'Install the Vulkan SDK or pass --sdk with its directory.') + if runtime and needs_vulkan: + executable = shutil.which('vulkaninfo') + if not executable and sdk: + candidate = Path(sdk) / ('Bin/vulkaninfo.exe' if os.name == 'nt' else 'bin/vulkaninfo') + if candidate.is_file(): + executable = str(candidate) + passed, detail = probe([executable, '--summary'], timeout=30) if executable else (False, 'vulkaninfo is unavailable') + check('Vulkan devices', passed, detail, 'Install Vulkan runtime tools and a compatible graphics driver; rerun --runtime.', optional=True) + return {'project': root.name, 'platform': os.name, 'compatibility': pins, 'checks': checks, + 'hardware_note': 'Device enumeration does not validate hardware video decoding or OpenCL interoperability.'} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--v', type=Path, help='Tested V compiler executable') + parser.add_argument('--modules', type=Path) + parser.add_argument('--sdk', type=Path) + parser.add_argument('--runtime', action='store_true', help='Also enumerate Vulkan devices, when tools are available') + parser.add_argument('--json', action='store_true', help='Emit JSON without modifying the environment or caches') + args = parser.parse_args() + report = diagnose(Path(__file__).resolve().parents[1], args.v, args.modules, args.sdk, args.runtime) + if args.json: + print(json.dumps(report, indent=2)) + else: + for item in report['checks']: + print(f"{item['status'].upper()}: {item['name']}: {item['detail']}") + if item['action']: + print(' Next: ' + item['action']) + raise SystemExit(1 if any(item['status'] == 'fail' for item in report['checks']) else 0) + + +if __name__ == '__main__': + main() diff --git a/scripts/test_compatibility.py b/scripts/test_compatibility.py new file mode 100644 index 0000000..2829ea6 --- /dev/null +++ b/scripts/test_compatibility.py @@ -0,0 +1,39 @@ +"""Upgrades must keep declarations and immutable compiler pins in agreement.""" +import json +from pathlib import Path +import shutil +import tempfile +import unittest +from compatibility import resolve + + +class CompatibilityChecks(unittest.TestCase): + def test_project_profile(self): + root = Path(__file__).resolve().parents[1] + pins = json.loads((root / 'compatibility.json').read_text()) + values = resolve(root) + self.assertEqual(values['selected_v'], pins.get('v', pins.get('v_release', ''))) + if 'v3_v' in pins: + self.assertEqual(resolve(root, 'v3')['selected_v'], pins['v3_v']) + self.assertEqual(resolve(root, 'master')['selected_v'], 'master') + + def test_mutable_compiler_and_disagreement_rejected(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + source = Path(__file__).resolve().parents[1] + shutil.copyfile(source / 'v.mod', root / 'v.mod') + pins = json.loads((source / 'compatibility.json').read_text()) + pins['v'] = 'master' + (root / 'compatibility.json').write_text(json.dumps(pins)) + with self.assertRaisesRegex(ValueError, 'immutable'): + resolve(root) + del pins['v'] + (root / 'v.mod').write_text("Module { dependencies: ['antono2.memory@v99.0.0'] }") + pins['modules']['memory'] = 'v1.4.0' + (root / 'compatibility.json').write_text(json.dumps(pins)) + with self.assertRaisesRegex(ValueError, 'disagree'): + resolve(root) + + +if __name__ == '__main__': + unittest.main() diff --git a/scripts/test_doctor.py b/scripts/test_doctor.py new file mode 100644 index 0000000..612bfa7 --- /dev/null +++ b/scripts/test_doctor.py @@ -0,0 +1,41 @@ +"""Diagnostics must identify mismatches without repairing or changing caches.""" +import json +from pathlib import Path +import tempfile +import unittest +from unittest.mock import patch +from doctor import diagnose + + +class DoctorChecks(unittest.TestCase): + def fixture(self, root): + (root / 'compatibility.json').write_text(json.dumps({'schema_version': 1, + 'v': 'a' * 40, 'modules': {'memory': 'v1.4.0'}, 'build_tools': ['git']})) + (root / 'v.mod').write_text("Module { dependencies: ['antono2.memory@v1.4.0'] }") + + def test_missing_tools_are_actionable_and_read_only(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + self.fixture(root) + before = {p.name: p.read_bytes() for p in root.iterdir()} + with patch('doctor.shutil.which', return_value=None), patch.dict('os.environ', {}, clear=True): + report = diagnose(root) + failed = [item for item in report['checks'] if item['status'] == 'fail'] + self.assertEqual({item['name'] for item in failed}, {'git', 'V compiler'}) + self.assertTrue(all(item['action'] for item in failed)) + self.assertEqual(before, {p.name: p.read_bytes() for p in root.iterdir()}) + + def test_wrong_compiler_and_dependency_are_reported(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + self.fixture(root) + (root / 'v.mod').write_text("Module { dependencies: ['antono2.memory@v99.0.0'] }") + with patch('doctor.shutil.which', return_value='git'), patch('doctor.probe', return_value=(True, 'V wrong-revision')): + report = diagnose(root, compiler='fixture-v') + failed = {item['name'] for item in report['checks'] if item['status'] == 'fail'} + self.assertIn('V compiler', failed) + self.assertIn('Declared dependencies', failed) + + +if __name__ == '__main__': + unittest.main() From 2d9fa484fa49583ff5b4310f031aba611212ba79 Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 11 Oct 2026 11:13:10 +0200 Subject: [PATCH 2/2] Diagnose each supported compiler profile against its tested pin --- docs/compatibility.md | 3 +++ scripts/doctor.py | 13 ++++++++----- scripts/test_doctor.py | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/docs/compatibility.md b/docs/compatibility.md index 926d3f7..eaab86a 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -18,3 +18,6 @@ compiler/tool/dependency diagnostics. Add `--json` for a support report and or mismatched prerequisites include a recovery action; diagnostics never repair caches or change the environment. Enumeration alone does not prove hardware video decoding or OpenCL interoperability. Python 3.9 or newer is required for these tools. + +Choose `--profile v3` for the pinned V3 compiler or `--profile release` for +the released compiler when the project defines multiple profiles. diff --git a/scripts/doctor.py b/scripts/doctor.py index 55ac920..cd4c666 100644 --- a/scripts/doctor.py +++ b/scripts/doctor.py @@ -17,7 +17,7 @@ def probe(command, timeout=15): return False, str(error) -def diagnose(root, compiler=None, modules=None, sdk=None, runtime=False): +def diagnose(root, compiler=None, modules=None, sdk=None, runtime=False, profile='stable'): checks = [] def check(name, passed, detail, action, optional=False): checks.append({'name': name, 'status': 'pass' if passed else ('warn' if optional else 'fail'), @@ -39,8 +39,10 @@ def check(name, passed, detail, action, optional=False): compiler = str(compiler) if compiler else (os.environ.get('VEXE') or shutil.which('v')) if compiler: passed, detail = probe([compiler, '-version']) - expected = pins.get('v', '')[:7] or pins.get('v_release', '') - matches = passed and (not expected or expected in detail) + selected = pins.get('v_release', '') if profile == 'release' else ( + pins.get('v3_v', pins.get('v', '')) if profile == 'v3' else pins.get('v', '')) + expected = selected[:7] if selected else (pins.get('v_release', '') if profile == 'stable' else '') + matches = passed and bool(expected) and expected in detail check('V compiler', matches, detail, 'Select the tested V compiler from compatibility.json or rerun the setup helper.') else: check('V compiler', False, 'No V compiler found', 'Run the project setup helper, or pass --v with the tested compiler path.') @@ -67,7 +69,7 @@ def check(name, passed, detail, action, optional=False): executable = str(candidate) passed, detail = probe([executable, '--summary'], timeout=30) if executable else (False, 'vulkaninfo is unavailable') check('Vulkan devices', passed, detail, 'Install Vulkan runtime tools and a compatible graphics driver; rerun --runtime.', optional=True) - return {'project': root.name, 'platform': os.name, 'compatibility': pins, 'checks': checks, + return {'project': root.name, 'platform': os.name, 'profile': profile, 'compatibility': pins, 'checks': checks, 'hardware_note': 'Device enumeration does not validate hardware video decoding or OpenCL interoperability.'} @@ -77,9 +79,10 @@ def main(): parser.add_argument('--modules', type=Path) parser.add_argument('--sdk', type=Path) parser.add_argument('--runtime', action='store_true', help='Also enumerate Vulkan devices, when tools are available') + parser.add_argument('--profile', choices=['stable', 'v3', 'release'], default='stable', help='Compiler profile to diagnose') parser.add_argument('--json', action='store_true', help='Emit JSON without modifying the environment or caches') args = parser.parse_args() - report = diagnose(Path(__file__).resolve().parents[1], args.v, args.modules, args.sdk, args.runtime) + report = diagnose(Path(__file__).resolve().parents[1], args.v, args.modules, args.sdk, args.runtime, args.profile) if args.json: print(json.dumps(report, indent=2)) else: diff --git a/scripts/test_doctor.py b/scripts/test_doctor.py index 612bfa7..ba63017 100644 --- a/scripts/test_doctor.py +++ b/scripts/test_doctor.py @@ -36,6 +36,21 @@ def test_wrong_compiler_and_dependency_are_reported(self): self.assertIn('V compiler', failed) self.assertIn('Declared dependencies', failed) + def test_v3_compiler_is_checked_against_its_own_profile(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + self.fixture(root) + pins = json.loads((root / 'compatibility.json').read_text()) + pins['v3_v'] = 'b' * 40 + (root / 'compatibility.json').write_text(json.dumps(pins)) + with patch('doctor.shutil.which', return_value='git'), patch('doctor.probe', return_value=(True, 'V bbbbbbb')): + release = diagnose(root, compiler='fixture-v') + v3 = diagnose(root, compiler='fixture-v', profile='v3') + unsupported = diagnose(root, compiler='fixture-v', profile='release') + self.assertEqual(next(item for item in release['checks'] if item['name'] == 'V compiler')['status'], 'fail') + self.assertEqual(next(item for item in v3['checks'] if item['name'] == 'V compiler')['status'], 'pass') + self.assertEqual(next(item for item in unsupported['checks'] if item['name'] == 'V compiler')['status'], 'fail') + if __name__ == '__main__': unittest.main()