Skip to content
Closed
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
273 changes: 273 additions & 0 deletions .github/workflows/tmp-vulkan-dist-probe.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
# TEMPORARY PROBE -- do NOT merge. Answers two questions the Vulkan
# cross-platform design left unmeasured, against the compat.vulkan 1.4.357.3
# branch (mcpp-index #395):
#
# windows Does `mcpp pack` carry the deployed vulkan-1.dll, and does the
# packed program start from a clean directory on an image that has
# NO system loader?
# macos Does the statically linked loader search <exe dir>/vulkan/icd.d
# (CFBundle resources of an unbundled executable), and does MoltenVK
# enumerate a device on the runner at all?
name: tmp-vulkan-dist-probe

on:
pull_request:
paths: [".github/workflows/tmp-vulkan-dist-probe.yml"]

permissions:
contents: read

env:
MCPP_VERSION: "2026.9.11.2"
MCPP_INDEX_MIRROR: GLOBAL
PROBE_REF: feat/vulkan-windows-ships-loader

jobs:
windows-pack:
runs-on: windows-2022
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
with: { ref: "${{ env.PROBE_REF }}" }

- name: The image must have no system Vulkan loader
shell: pwsh
run: |
foreach ($p in @("$env:SystemRoot\System32\vulkan-1.dll", "$env:SystemRoot\SysWOW64\vulkan-1.dll")) {
if (Test-Path $p) { throw "found $p -- this image cannot prove anything" }
}
"no system vulkan-1.dll: confirmed"

- name: Download mcpp
shell: bash
run: |
a="mcpp-${MCPP_VERSION}-windows-x86_64.zip"
curl -L -fsS -o "$a" "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/$a"
powershell -NoProfile -Command "Expand-Archive -Force -Path '$a' -DestinationPath '.'"
root="$PWD/mcpp-${MCPP_VERSION}-windows-x86_64"
mkdir -p "$HOME/.mcpp/registry" && cp -a "$root/registry/." "$HOME/.mcpp/registry/"
echo "MCPP_HOME=$HOME/.mcpp" >> "$GITHUB_ENV"
echo "MCPP=$(cygpath -m "$root/bin/mcpp.exe")" >> "$GITHUB_ENV"
echo "MCPP_VENDORED_XLINGS=$(cygpath -m "$root/registry/bin/xlings.exe")" >> "$GITHUB_ENV"
echo "INDEX=$(cygpath -m "$PWD")" >> "$GITHUB_ENV"

- name: Write the probe program
shell: bash
run: |
P="$RUNNER_TEMP/vkprobe"; mkdir -p "$P/src"
echo "PROBE_DIR=$P" >> "$GITHUB_ENV"
cat > "$P/mcpp.toml" <<EOF
[package]
name = "vkprobe"
version = "0.1.0"

[indices]
compat = { path = "$INDEX" }

[dependencies.compat]
vulkan = "1.4.357.3"
EOF
cat > "$P/src/main.cpp" <<'EOF'
#include <vulkan/vulkan.h>
#include <cstdio>
#include <cstring>
#include <vector>
#if defined(_WIN32)
extern "C" __declspec(dllimport) void* GetModuleHandleA(const char*);
extern "C" __declspec(dllimport) unsigned long GetModuleFileNameA(void*, char*, unsigned long);
#endif
int main() {
uint32_t v = 0;
if (vkEnumerateInstanceVersion(&v) != VK_SUCCESS) { std::puts("PROBE: vkEnumerateInstanceVersion failed"); return 1; }
std::printf("PROBE: loader api %u.%u.%u\n", VK_VERSION_MAJOR(v), VK_VERSION_MINOR(v), VK_VERSION_PATCH(v));
#if defined(_WIN32)
char dll[4096] = {0}, exe[4096] = {0};
GetModuleFileNameA(GetModuleHandleA("vulkan-1.dll"), dll, sizeof dll);
GetModuleFileNameA(nullptr, exe, sizeof exe);
std::printf("PROBE: vulkan-1.dll = %s\nPROBE: exe = %s\n", dll, exe);
#endif
uint32_t n = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &n, nullptr);
std::vector<VkExtensionProperties> ex(n);
vkEnumerateInstanceExtensionProperties(nullptr, &n, ex.data());
bool port = false;
for (auto& x : ex) if (!std::strcmp(x.extensionName, "VK_KHR_portability_enumeration")) port = true;
const char* names[] = { "VK_KHR_portability_enumeration" };
VkApplicationInfo app{}; app.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app.apiVersion = VK_API_VERSION_1_1;
VkInstanceCreateInfo ci{}; ci.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; ci.pApplicationInfo = &app;
if (port) { ci.enabledExtensionCount = 1; ci.ppEnabledExtensionNames = names; ci.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; }
VkInstance inst = VK_NULL_HANDLE;
VkResult r = vkCreateInstance(&ci, nullptr, &inst);
std::printf("PROBE: portability_enumeration=%d vkCreateInstance=%d\n", port ? 1 : 0, (int)r);
if (r != VK_SUCCESS) { std::puts("PROBE: devices=0 (no instance)"); return 0; }
uint32_t dc = 0; vkEnumeratePhysicalDevices(inst, &dc, nullptr);
std::vector<VkPhysicalDevice> ds(dc); vkEnumeratePhysicalDevices(inst, &dc, ds.data());
std::printf("PROBE: devices=%u\n", dc);
for (auto d : ds) { VkPhysicalDeviceProperties pr; vkGetPhysicalDeviceProperties(d, &pr); std::printf("PROBE: device %s\n", pr.deviceName); }
vkDestroyInstance(inst, nullptr);
return 0;
}
EOF

- name: Build and run in place
shell: bash
run: |
cd "$PROBE_DIR"
"$MCPP" build 2>&1 | tail -15
exe=$(find target -type f -name vkprobe.exe | head -1); echo "built: $exe"
ls -la "$(dirname "$exe")"
"$exe"

- name: Pack, extract into a clean directory, run there
shell: bash
run: |
cd "$PROBE_DIR"
"$MCPP" pack 2>&1 | tail -15
zip=$(find target/dist -maxdepth 1 -name '*.zip' | head -1); echo "archive: $zip"
[ -n "$zip" ] || { echo "PROBE-FAIL: no zip produced"; ls -la target/dist 2>/dev/null; exit 1; }
unzip -l "$zip"
unzip -l "$zip" | grep -qi 'vulkan-1.dll' && echo "PROBE: archive carries vulkan-1.dll" || { echo "PROBE-FAIL: archive has no vulkan-1.dll"; exit 1; }
clean="$RUNNER_TEMP/clean-run"; rm -rf "$clean"; mkdir -p "$clean"
powershell -NoProfile -Command "Expand-Archive -Force -Path '$(cygpath -w "$zip")' -DestinationPath '$(cygpath -w "$clean")'"
find "$clean" -type f | head -20
exe=$(find "$clean" -type f -name vkprobe.exe | head -1)
# A PATH with nothing of ours on it, so only the archive can supply the loader.
env -i SYSTEMROOT="$SYSTEMROOT" PATH="/c/Windows/System32:/c/Windows" "$exe"

macos-bundle:
runs-on: macos-15
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
with: { ref: "${{ env.PROBE_REF }}" }

- name: Where CoreFoundation puts an unbundled executable's resources
shell: bash
run: |
d="$RUNNER_TEMP/cfprobe/bin"; mkdir -p "$d"
cat > "$RUNNER_TEMP/cfprobe/b.c" <<'EOF'
#include <CoreFoundation/CoreFoundation.h>
#include <stdio.h>
static void show(const char *label, CFURLRef u) {
char p[4096];
if (u && CFURLGetFileSystemRepresentation(u, true, (UInt8 *)p, sizeof p)) printf("PROBE: %s = %s\n", label, p);
else printf("PROBE: %s = (none)\n", label);
}
int main(void) {
CFBundleRef b = CFBundleGetMainBundle();
printf("PROBE: main bundle %s\n", b ? "present" : "absent");
if (!b) return 1;
show("bundle url", CFBundleCopyBundleURL(b));
show("resources dir", CFBundleCopyResourcesDirectoryURL(b));
show("executable", CFBundleCopyExecutableURL(b));
return 0;
}
EOF
clang "$RUNNER_TEMP/cfprobe/b.c" -framework CoreFoundation -o "$d/cfprobe"
"$d/cfprobe"

- name: Download mcpp
shell: bash
run: |
a="mcpp-${MCPP_VERSION}-macosx-arm64.tar.gz"
curl -L -fsS -o "$a" "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/$a"
tar -xzf "$a"
root="$PWD/mcpp-${MCPP_VERSION}-macosx-arm64"
mkdir -p "$HOME/.mcpp/registry" && cp -a "$root/registry/." "$HOME/.mcpp/registry/"
echo "MCPP_HOME=$HOME/.mcpp" >> "$GITHUB_ENV"
echo "MCPP=$root/bin/mcpp" >> "$GITHUB_ENV"
echo "MCPP_VENDORED_XLINGS=$root/registry/bin/xlings" >> "$GITHUB_ENV"
echo "INDEX=$PWD" >> "$GITHUB_ENV"

- name: Build the probe (statically linked loader)
shell: bash
run: |
P="$RUNNER_TEMP/vkprobe"; mkdir -p "$P/src"
echo "PROBE_DIR=$P" >> "$GITHUB_ENV"
printf '[package]\nname = "vkprobe"\nversion = "0.1.0"\n\n[indices]\ncompat = { path = "%s" }\n\n[dependencies.compat]\nvulkan = "1.4.357.3"\n' "$INDEX" > "$P/mcpp.toml"
cat > "$P/src/main.cpp" <<'EOF'
#include <vulkan/vulkan.h>
#include <cstdio>
#include <cstring>
#include <vector>
int main() {
uint32_t v = 0;
if (vkEnumerateInstanceVersion(&v) != VK_SUCCESS) { std::puts("PROBE: vkEnumerateInstanceVersion failed"); return 1; }
std::printf("PROBE: loader api %u.%u.%u\n", VK_VERSION_MAJOR(v), VK_VERSION_MINOR(v), VK_VERSION_PATCH(v));
uint32_t n = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &n, nullptr);
std::vector<VkExtensionProperties> ex(n);
vkEnumerateInstanceExtensionProperties(nullptr, &n, ex.data());
bool port = false;
for (auto& x : ex) if (!std::strcmp(x.extensionName, "VK_KHR_portability_enumeration")) port = true;
const char* names[] = { "VK_KHR_portability_enumeration" };
VkApplicationInfo app{}; app.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app.apiVersion = VK_API_VERSION_1_1;
VkInstanceCreateInfo ci{}; ci.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; ci.pApplicationInfo = &app;
if (port) { ci.enabledExtensionCount = 1; ci.ppEnabledExtensionNames = names; ci.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; }
VkInstance inst = VK_NULL_HANDLE;
VkResult r = vkCreateInstance(&ci, nullptr, &inst);
std::printf("PROBE: portability_enumeration=%d vkCreateInstance=%d\n", port ? 1 : 0, (int)r);
if (r != VK_SUCCESS) { std::puts("PROBE: devices=0 (no instance)"); return 0; }
uint32_t dc = 0; vkEnumeratePhysicalDevices(inst, &dc, nullptr);
std::vector<VkPhysicalDevice> ds(dc); vkEnumeratePhysicalDevices(inst, &dc, ds.data());
std::printf("PROBE: devices=%u\n", dc);
for (auto d : ds) { VkPhysicalDeviceProperties pr; vkGetPhysicalDeviceProperties(d, &pr); std::printf("PROBE: device %s\n", pr.deviceName); }
vkDestroyInstance(inst, nullptr);
return 0;
}
EOF
cd "$P" && "$MCPP" build 2>&1 | tail -15
exe=$(find target -type f -name vkprobe -perm -u+x | head -1); echo "EXE=$PWD/$exe" >> "$GITHUB_ENV"; echo "built: $exe"

- name: "A: no MoltenVK anywhere"
shell: bash
run: |
set +e
VK_LOADER_DEBUG=error,warn,driver "$EXE" > "$RUNNER_TEMP/a.out" 2> "$RUNNER_TEMP/a.dbg"; echo "EXIT=$?"
cat "$RUNNER_TEMP/a.out"
grep -E "Found ICD|drivers named|Could not|Failed|failed|ERROR" "$RUNNER_TEMP/a.dbg" | sort | uniq -c | head -20

- name: "B: manifest beside the executable, RELATIVE library_path"
shell: bash
run: |
set +e
curl -L -fsS -o mvk.tgz https://github.com/xlings-res/moltenvk/releases/download/1.4.2/moltenvk-1.4.2-macosx-universal.tar.gz
tar -xzf mvk.tgz
bindir=$(dirname "$EXE")
mkdir -p "$bindir/vulkan/icd.d"
cp moltenvk-1.4.2/lib/libMoltenVK.dylib "$bindir/"
sed 's#"library_path"[^,]*#"library_path" : "../../libMoltenVK.dylib"#' moltenvk-1.4.2/share/vulkan/icd.d/MoltenVK_icd.json > "$bindir/vulkan/icd.d/MoltenVK_icd.json"
VK_LOADER_DEBUG=error,warn,driver "$EXE" > "$RUNNER_TEMP/b.out" 2> "$RUNNER_TEMP/b.dbg"; echo "EXIT=$?"
cat "$RUNNER_TEMP/b.out"
grep -E "Found ICD|drivers named|Could not|Failed|failed|ERROR|dlopen|Loading" "$RUNNER_TEMP/b.dbg" | sort | uniq -c | head -20

- name: "B2: manifest beside the executable, ABSOLUTE library_path"
shell: bash
run: |
set +e
bindir=$(dirname "$EXE")
sed "s#\"library_path\"[^,]*#\"library_path\" : \"$bindir/libMoltenVK.dylib\"#" moltenvk-1.4.2/share/vulkan/icd.d/MoltenVK_icd.json > "$bindir/vulkan/icd.d/MoltenVK_icd.json"
cat "$bindir/vulkan/icd.d/MoltenVK_icd.json"
VK_LOADER_DEBUG=error,warn,driver "$EXE" > "$RUNNER_TEMP/b2.out" 2> "$RUNNER_TEMP/b2.dbg"; echo "EXIT=$?"
cat "$RUNNER_TEMP/b2.out"
grep -E "Found ICD|drivers named|Could not|Failed|failed|ERROR" "$RUNNER_TEMP/b2.dbg" | sort | uniq -c | head -20

- name: "B3: whole bin/ copied elsewhere (does the relative layout travel?)"
shell: bash
run: |
set +e
bindir=$(dirname "$EXE")
sed 's#"library_path"[^,]*#"library_path" : "../../libMoltenVK.dylib"#' moltenvk-1.4.2/share/vulkan/icd.d/MoltenVK_icd.json > "$bindir/vulkan/icd.d/MoltenVK_icd.json"
moved="$RUNNER_TEMP/moved-app"; rm -rf "$moved"; cp -R "$bindir" "$moved"
VK_LOADER_DEBUG=error,warn,driver "$moved/vkprobe" > "$RUNNER_TEMP/b3.out" 2> "$RUNNER_TEMP/b3.dbg"; echo "EXIT=$?"
cat "$RUNNER_TEMP/b3.out"
grep -E "Found ICD|drivers named|Could not|Failed|failed|ERROR" "$RUNNER_TEMP/b3.dbg" | sort | uniq -c | head -20

- name: "C: MoltenVK through VK_DRIVER_FILES (xim:moltenvk's documented route)"
shell: bash
run: |
set +e
abs="$PWD/moltenvk-1.4.2/lib/libMoltenVK.dylib"
sed "s#\"library_path\"[^,]*#\"library_path\" : \"$abs\"#" moltenvk-1.4.2/share/vulkan/icd.d/MoltenVK_icd.json > "$RUNNER_TEMP/mvk_icd.json"
VK_LOADER_DEBUG=error,warn,driver VK_DRIVER_FILES="$RUNNER_TEMP/mvk_icd.json" "$EXE" > "$RUNNER_TEMP/c.out" 2> "$RUNNER_TEMP/c.dbg"; echo "EXIT=$?"
cat "$RUNNER_TEMP/c.out"
Loading