Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 43 additions & 11 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
14 changes: 14 additions & 0 deletions compatibility.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
23 changes: 23 additions & 0 deletions docs/compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 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.

Choose `--profile v3` for the pinned V3 compiler or `--profile release` for
the released compiler when the project defines multiple profiles.
65 changes: 65 additions & 0 deletions scripts/compatibility.py
Original file line number Diff line number Diff line change
@@ -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()
97 changes: 97 additions & 0 deletions scripts/doctor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/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, 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'),
'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'])
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.')
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, 'profile': profile, '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('--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, args.profile)
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()
39 changes: 39 additions & 0 deletions scripts/test_compatibility.py
Original file line number Diff line number Diff line change
@@ -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()
Loading