diff --git a/.github/workflows/additional-examples.yml b/.github/workflows/additional-examples.yml index 44f2866..904a8dd 100644 --- a/.github/workflows/additional-examples.yml +++ b/.github/workflows/additional-examples.yml @@ -44,6 +44,9 @@ jobs: repository: antono2/glfw ref: v2.0.1 path: build/modules/antono2/glfw + - name: Verify the declared dependency graph + shell: bash + run: v run scripts/check_installed_deps.vsh --tag-suffix antono2.imgui=-standard - name: Install graphics dependencies run: sudo apt-get update && sudo apt-get install -y build-essential cmake libglfw3-dev libvulkan-dev libvulkan-volk-dev pkg-config mesa-vulkan-drivers vulkan-validationlayers xvfb - name: Build native library diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index f4acb1b..c3f4443 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -64,6 +64,9 @@ jobs: run: | make local=1 echo "$GITHUB_WORKSPACE/toolchain/v" >> "$GITHUB_PATH" + - name: Verify the declared dependency graph + shell: bash + run: v run source/demo/scripts/check_installed_deps.vsh - name: Install native dependencies run: sudo apt-get update && sudo apt-get install -y build-essential cmake libglfw3-dev libvulkan-dev libvulkan-volk-dev pkg-config mesa-vulkan-drivers vulkan-validationlayers xvfb openbox xdotool x11-utils x11-xserver-utils wmctrl gdb - name: Build native ImGui library @@ -135,6 +138,9 @@ jobs: shell: pwsh run: | "$env:GITHUB_WORKSPACE\toolchain\v" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Verify the declared dependency graph + shell: bash + run: v run source/demo/scripts/check_installed_deps.vsh - name: Install Vulkan SDK and SwiftShader test artifacts shell: bash run: v run source/modules/antono2/imgui/scripts/setup_vulkan_ci.vsh 1.4.309.0 v1.2 @@ -249,6 +255,9 @@ jobs: run: | make -C toolchain/v local=1 echo "$GITHUB_WORKSPACE/toolchain/v" >> "$GITHUB_PATH" + - name: Verify the declared dependency graph + shell: bash + run: v run source/demo/scripts/check_installed_deps.vsh - name: Check demo with V3 run: | for source in source/demo source/demo/examples/widget_gallery source/demo/examples/implot_dashboard; do diff --git a/.github/workflows/release-packages.yml b/.github/workflows/release-packages.yml index 31934ef..17d1845 100644 --- a/.github/workflows/release-packages.yml +++ b/.github/workflows/release-packages.yml @@ -44,6 +44,9 @@ jobs: repository: antono2/glfw ref: v2.0.1 path: build/modules/antono2/glfw + - name: Verify the declared dependency graph + shell: bash + run: v run scripts/check_installed_deps.vsh ${{ matrix.variant == 'standard' && '--tag-suffix antono2.imgui=-standard' || '' }} - name: Install Linux build and rendering tools if: runner.os == 'Linux' run: sudo apt-get update && sudo apt-get install -y build-essential cmake libglfw3-dev libvulkan-dev libvulkan-volk-dev libdbus-1-dev libatk1.0-dev libatk-bridge2.0-dev libatspi2.0-dev libglib2.0-dev at-spi2-core dbus-x11 pkg-config mesa-vulkan-drivers vulkan-validationlayers ripgrep gdb xvfb patchelf zip xdotool openbox x11-utils x11-xserver-utils wmctrl diff --git a/docs/releasing.md b/docs/releasing.md index cc9aaec..3f022a2 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -37,7 +37,7 @@ embeds license notices, aligns the APKs, signs them and verifies each signature. Keep the key and password private and backed up outside the repository. Never upload them as release assets. Use the same signing identity for updates. The private key uses the same password as the keystore. -Increment `android:versionCode` and update `android:versionName` for each release. +Increment `android:versionCode` and update `android:versionName` whenever publishing newly built Android APKs. Test the final universal APK on a device, including the installation/update path. Review [the testing checklist](testing.md) and package instructions. @@ -45,3 +45,22 @@ Generate `SHA256SUMS.txt` from the final ZIP and APK files. Tag the verified commits only after checks pass, then publish the assets and installation link. The upstream imgui release has a docking tag and a companion `-standard` source tag; the same example packages can be attached to both repositories' releases. + +## Desktop-only patch releases + +A desktop fix can be released without rebuilding Android. Run the full portable +workflow for the new source commit and promote the four checked desktop ZIPs. +For unchanged Android code, retain the four signed APKs from the previous +release byte-for-byte. Verify their SHA-256 hashes against that release before +uploading them, and include them in the new `SHA256SUMS.txt`. + +State explicitly in the release notes and `BUILD-PROVENANCE.txt` that the APKs +retain their previous source revision, Android version and signing identity. +Do not label retained APKs as builds of the desktop patch. New CI debug APKs +remain test artifacts. An Android change requires new version metadata, signing +and device validation as described above. + +Required desktop jobs run `scripts/check_installed_deps.vsh` before building. +It compares every direct and transitive `v.mod` pin with the installed Git tag +commit. The standard lane explicitly selects `antono2.imgui=-standard`; other +lanes must match the declared tags. Keep advisory master jobs separate. diff --git a/docs/testing.md b/docs/testing.md index 9e208fd..f0fa5b7 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -3,6 +3,10 @@ Build and rendering results are recorded in the PR checks. See [the 2026-10-03 interaction report](validation-2026-10-03.md) for completed checks, the viewport fix and the remaining device checks. +[The 2026-10-09 follow-up](validation-2026-10-09.md) records the Linux startup +fix, dependency checks and desktop patch release scope. Checked interaction +items below describe the recorded earlier sessions; they do not imply a new +manual session for each release. ## Automated checks diff --git a/docs/validation-2026-10-09.md b/docs/validation-2026-10-09.md new file mode 100644 index 0000000..0932a67 --- /dev/null +++ b/docs/validation-2026-10-09.md @@ -0,0 +1,42 @@ +# Validation follow-up — 2026-10-09 + +## Linux startup fix + +The raw GLFW/Vulkan example now maps its initially hidden window immediately +after creation and waits for visibility before renderer setup. In the affected Xvfb/Openbox/Lavapipe session, +the previous startup could wait inside `vkQueuePresentKHR` before the window +manager mapped it. The wait processes GLFW events and has a five-second limit. Mapping after +renderer setup still timed out once in the standard package keyboard check, +although that package had passed rendering and AT-SPI. Mapping is therefore +completed before Vulkan/ImGui setup adds further X11 requests and events. +The revised host passed twelve local startup/render/exit runs (six without a +window manager, six with Openbox) under Lavapipe with Vulkan validation. The +updated raw demo also passed F11/held-key/restore/Escape checks. Existing gallery +and dashboard binaries passed the remainder of the keyboard suite. + +[PR #24](https://github.com/antono2/v_imgui_examples/pull/24) passed all 17 checks: +Linux and Windows builds, the pinned V3 lane, standard and docking portable +packages, keyboard shortcuts, Linux AT-SPI, Windows UI Automation and Android +builds for three ABIs. The earlier interaction results remain in +[the October 3 report](validation-2026-10-03.md). + +## Dependency graph verification + +Required desktop jobs verify installed tag commits against `v.mod`, recursively +including each dependency's own pins. Standard ImGui requires an explicit +companion-tag selection. Current dependency masters remain advisory. + +Local fixtures passed with stable V 0.5.2 and pinned V3 +`c0449e860641`: recursive annotated tags and paths containing spaces, rejection +of a mismatched transitive checkout, conflicting pins, implicit companion tags, +unused companion selections and unpinned dependencies; explicit companion +selection passed. The complete release dependency graph also passed locally. + +## Release scope + +Version 1.0.1 is a desktop patch. Publish the four desktop ZIPs from the passing +portable workflow for this revision and record that run in release provenance. +Retain the signed Android 1.0.0 APKs unchanged, verify their original hashes and +record their earlier provenance. This patch does not claim another Android +device session or new TalkBack/Narrator speech validation. Automated provider +checks establish actions and state, not spoken announcement quality. diff --git a/examples/glfw_vulkan/main.c.v b/examples/glfw_vulkan/main.c.v index 42c843c..9582fdd 100644 --- a/examples/glfw_vulkan/main.c.v +++ b/examples/glfw_vulkan/main.c.v @@ -49,7 +49,7 @@ pub fn run(options Options, frame fn (mut App, voidptr), state voidptr) { // Create window with Vulkan context glfw.window_hint(glfw.client_api, glfw.no_api) - // Map the window after its renderer and UI are ready. + // Create hidden so startup events can be drained before mapping. glfw.window_hint(glfw.visible, 0) // Borderless fullscreen should remain available when switching applications. glfw.window_hint(glfw.auto_iconify, 0) @@ -59,6 +59,23 @@ pub fn run(options Options, frame fn (mut App, voidptr), state voidptr) { panic(err) } + // Map before renderer setup adds more X11 requests and events. GLFW 3.3 + // can otherwise return from its visibility wait with the window unmapped. + // Drain startup events before asking the window manager to map it. + glfw.poll_events() + glfw.show_window(window) + // Showing a window can return before its window manager maps it. Mesa's + // X11 FIFO presentation can then block before another event poll occurs. + // Observe visibility before submitting the first frame, without sleeping + // indefinitely if the display/window manager cannot show this window. + visibility_deadline := glfw.get_time() + 5.0 + for glfw.get_window_attrib(window, glfw.visible) == 0 { + if glfw.get_time() >= visibility_deadline { + panic('Window did not become visible within 5 seconds') + } + glfw.wait_events_timeout(0.01) + } + if !glfw.vulkan_supported() { panic('GLFW: Vulkan Not Supported') } @@ -124,21 +141,6 @@ pub fn run(options Options, frame fn (mut App, voidptr), state voidptr) { impl_vulkan.vkinit(&init_info) options.initialize(state) - // Drain startup events before GLFW's X11 visibility wait. Queued unrelated - // events can prevent that wait from reaching its timeout during creation. - glfw.poll_events() - glfw.show_window(window) - // Showing a window can return before its window manager maps it. Mesa's - // X11 FIFO presentation can then block before another event poll occurs. - // Observe visibility before submitting the first frame, without sleeping - // indefinitely if the display/window manager cannot show this window. - visibility_deadline := glfw.get_time() + 5.0 - for glfw.get_window_attrib(window, glfw.visible) == 0 { - if glfw.get_time() >= visibility_deadline { - panic('Window did not become visible within 5 seconds') - } - glfw.wait_events_timeout(0.01) - } // No custom fonts are loaded, so Dear ImGui uses its default font. diff --git a/scripts/check_installed_deps.vsh b/scripts/check_installed_deps.vsh new file mode 100644 index 0000000..1ef3e07 --- /dev/null +++ b/scripts/check_installed_deps.vsh @@ -0,0 +1,69 @@ +#!/usr/bin/env -S v run + +// Verify direct and transitive release pins against the installed Git checkouts. +// Companion tags require an explicit --tag-suffix module=suffix argument. +import os +import v.vmod + +fn check_module(root string, modules string, suffixes map[string]string, mut checked map[string]string) ! { + manifest := vmod.from_file(os.join_path(root, 'v.mod'))! + for dependency in manifest.dependencies { + name, declared_tag := dependency.rsplit_once('@') or { + return error('Dependency is not pinned in ${root}: ${dependency}') + } + tag := declared_tag + suffixes[name] + if previous := checked[name] { + if previous != tag { + return error('Conflicting dependency pins for ${name}: ${previous} and ${tag}') + } + continue + } + module_dir := os.join_path(modules, ...name.split('.')) + actual := os.execute('git -C ${os.quoted_path(module_dir)} rev-parse HEAD') + expected := os.execute('git -C ${os.quoted_path(module_dir)} rev-parse ${os.quoted_path('refs/tags/' + tag + '^{commit}')}') + if actual.exit_code != 0 || expected.exit_code != 0 { + return error('Cannot verify installed ${name}@${tag}: ${actual.output}${expected.output}') + } + if actual.output.trim_space() != expected.output.trim_space() { + return error('Installed ${name} does not match ${tag}: ${actual.output.trim_space()}') + } + checked[name] = tag + println('Verified ${name}@${tag}') + check_module(module_dir, modules, suffixes, mut checked)! + } +} + +fn check_dependencies() ! { + mut suffixes := map[string]string{} + mut index := 1 + for index < os.args.len { + if os.args[index] != '--tag-suffix' || index + 1 >= os.args.len { + return error('Usage: v run scripts/check_installed_deps.vsh [--tag-suffix module=suffix]') + } + name, suffix := os.args[index + 1].split_once('=') or { + return error('--tag-suffix requires module=suffix') + } + if name == '' || suffix == '' || name in suffixes { + return error('Invalid or repeated tag suffix: ${os.args[index + 1]}') + } + suffixes[name] = suffix + index += 2 + } + root := os.dir(os.dir(os.real_path(@FILE))) + configured := os.getenv('VMODULES') + modules := if configured != '' { configured } else { os.join_path(os.home_dir(), '.vmodules') } + mut checked := map[string]string{} + check_module(root, modules, suffixes, mut checked)! + for name, _ in suffixes { + if name !in checked { + return error('Tag suffix did not match a dependency: ${name}') + } + } +} + +fn main() { + check_dependencies() or { + eprintln(err) + exit(1) + } +} diff --git a/v.mod b/v.mod index 68e89b9..813c61c 100644 --- a/v.mod +++ b/v.mod @@ -2,7 +2,7 @@ Module { name: 'v_imgui_examples' description: 'Shows how to use V Dear ImGui' author: 'Anton Oreskin' - version: '1.0.0' + version: '1.0.1' license: 'MIT' dependencies: ['antono2.imgui@v0.3.1'] }