diff --git a/.github/workflows/demo-release.yml b/.github/workflows/demo-release.yml index 8a7295e..05cb478 100644 --- a/.github/workflows/demo-release.yml +++ b/.github/workflows/demo-release.yml @@ -57,6 +57,7 @@ jobs: working-directory: source/modules/antono2/imgui run: | v run setup.vsh --check + v run generate.vsh --help ./scripts/check_upstream_variant.sh - name: Select package variant id: variant diff --git a/QUICKSTART.md b/QUICKSTART.md index 3ea20b3..0ec42f4 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -87,7 +87,7 @@ v run build_vimgui.vsh --linkage shared --glfw bundled --glfw-version 3.4 ## Regenerating bindings -Binding regeneration is maintainer-oriented. Run `./generate_v.sh` only when +Binding regeneration is maintainer-oriented. Run `v run generate.vsh` only when updating the generated ImGui or ImPlot API. It translates the generated API committed by the pinned upstream submodules; LuaJIT is required only with the advanced `--regenerate-c` option. Use `./scripts/update_upstream.sh docking` on diff --git a/README.md b/README.md index f9e9fde..4fed949 100644 --- a/README.md +++ b/README.md @@ -132,15 +132,14 @@ developers; they do not need to multiply the end-user demo downloads. ```bash # Go to the installed antono2/imgui module -./generate_v.sh +v run generate.vsh ``` -`generate_v.sh` regenerates both V bindings from the generated C API committed +`generate.vsh` regenerates both V bindings from the generated C API committed by the pinned cimgui/cimplot revisions, then builds `libvimgui`. It therefore does not require LuaJIT for a normal upstream refresh. Pass `--regenerate-c` only when intentionally rerunning the upstream Lua generators; that advanced -mode requires LuaJIT. `v generate.vsh` is retained as a compatibility entry -point and delegates to the same canonical script. +mode requires LuaJIT. Maintainers can update either line reproducibly with: @@ -151,9 +150,7 @@ Maintainers can update either line reproducibly with: ``` To only rebuild the native library after a system upgrade or on an older Linux -distribution, run `v run build_vimgui.vsh`. The Bash `build_vimgui.sh` helper -remains available as a bootstrap fallback on Unix-like machines where V is not -yet in `PATH`. +distribution, run `v run build_vimgui.vsh`. ## Thanks Thank you [@ryoskzypu](https://github.com/ryoskzypu) - from #regex on [libera.chat](https://libera.chat/) - for loving perl and helping people out. diff --git a/build_vimgui.sh b/build_vimgui.sh deleted file mode 100755 index 7f13194..0000000 --- a/build_vimgui.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash -# Build the native Dear ImGui/ImPlot library for this machine without -# regenerating the V bindings. This avoids copying a libvimgui built against a -# newer glibc onto an older Linux installation. -set -euo pipefail - -linkage=${VIMGUI_LINKAGE:-shared} -glfw_provider=${VIMGUI_GLFW_PROVIDER:-system} -glfw_version=${VIMGUI_GLFW_VERSION:-3.3} - -while (($#)); do - case "$1" in - --linkage) linkage=$2; shift 2 ;; - --glfw) glfw_provider=$2; shift 2 ;; - --glfw-version) glfw_version=$2; shift 2 ;; - -h|--help) - echo 'Usage: build_vimgui.sh [--linkage shared|static] [--glfw system|bundled] [--glfw-version VERSION]' - exit 0 ;; - *) echo "Unknown option: $1" >&2; exit 2 ;; - esac -done - -if [[ $linkage != shared && $linkage != static ]]; then - echo 'linkage must be shared or static' >&2 - exit 2 -fi -if [[ $glfw_provider != system && $glfw_provider != bundled ]]; then - echo 'glfw provider must be system or bundled' >&2 - exit 2 -fi - -SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd) - -if [[ ! -f "$SCRIPT_DIR/CMakeLists.txt" || ! -f "$SCRIPT_DIR/cimgui/cimgui.cpp" || ! -f "$SCRIPT_DIR/cimplot/cimplot.cpp" ]]; then - echo 'Missing cimgui or cimplot sources. Run generate_v.sh first (or initialise the submodules).' - exit 1 -fi - -static_build=OFF -library_suffix=so -if [[ $linkage == static ]]; then - static_build=ON - library_suffix=a -fi -build_dir="$SCRIPT_DIR/build/${linkage}-${glfw_provider}-${glfw_version}" - -cmake -S "$SCRIPT_DIR" -B "$build_dir" \ - -DVIMGUI_OUTPUT_DIR="$SCRIPT_DIR/lib" \ - -DSTATIC_BUILD="$static_build" \ - -DVIMGUI_GLFW_PROVIDER="$glfw_provider" \ - -DVIMGUI_GLFW_VERSION="$glfw_version" \ - -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" \ - -DCIMGUI_NO_EXPORT=ON -cmake --build "$build_dir" --parallel - -echo "Built $SCRIPT_DIR/lib/libvimgui.$library_suffix (GLFW: $glfw_provider${glfw_provider:+, version $glfw_version})" diff --git a/build_vimgui.vsh b/build_vimgui.vsh index e4ef063..952ce3c 100755 --- a/build_vimgui.vsh +++ b/build_vimgui.vsh @@ -29,20 +29,27 @@ fn run(parts []string) { } mut linkage := os.getenv('VIMGUI_LINKAGE') + if linkage == '' { linkage = 'shared' } + mut glfw_provider := os.getenv('VIMGUI_GLFW_PROVIDER') + if glfw_provider == '' { glfw_provider = 'system' } + mut glfw_version := os.getenv('VIMGUI_GLFW_VERSION') + if glfw_version == '' { glfw_version = '3.3' } args := os.args[1..] + mut index := 0 + for index < args.len { match args[index] { '--linkage' { @@ -73,6 +80,7 @@ if linkage !in ['shared', 'static'] { eprintln('linkage must be shared or static') exit(2) } + if glfw_provider !in ['system', 'bundled'] { eprintln('glfw provider must be system or bundled') exit(2) @@ -81,29 +89,35 @@ if glfw_provider !in ['system', 'bundled'] { if !os.is_file(os.join_path(repo_dir, 'CMakeLists.txt')) || !os.is_file(os.join_path(repo_dir, 'cimgui', 'cimgui.cpp')) || !os.is_file(os.join_path(repo_dir, 'cimplot', 'cimplot.cpp')) { - eprintln('Missing cimgui or cimplot sources. Run generate_v.sh first (or initialise the submodules).') + eprintln('Missing cimgui or cimplot sources. Run `v run generate.vsh` first (or initialise the submodules).') exit(1) } static_build := if linkage == 'static' { 'ON' } else { 'OFF' } + mut no_export := 'ON' + $if windows { // A Windows DLL needs cimgui/cimplot API exports and an import library. no_export = 'OFF' } + build_type := if os.getenv('CMAKE_BUILD_TYPE') == '' { 'Release' } else { os.getenv('CMAKE_BUILD_TYPE') } + safe_glfw_version := glfw_version.replace('..', '_').replace('/', '_').replace('\\', '_') + build_dir := os.join_path(repo_dir, 'build', '${linkage}-${glfw_provider}-${safe_glfw_version}') + output_dir := os.join_path(repo_dir, 'lib') + os.mkdir_all(build_dir) or { eprintln('Could not create build directory ${build_dir}: ${err}') exit(1) } - run([ 'cmake', '-S', @@ -118,5 +132,4 @@ run([ '-DCIMGUI_NO_EXPORT=${no_export}', ]) run(['cmake', '--build', build_dir, '--config', build_type, '--parallel']) - println('Built vimgui in ${output_dir} (linkage: ${linkage}, GLFW: ${glfw_provider}, version: ${glfw_version})') diff --git a/generate.vsh b/generate.vsh index a4262ce..792940e 100755 --- a/generate.vsh +++ b/generate.vsh @@ -1,16 +1,116 @@ -#!/usr/bin/env -S v +#!/usr/bin/env -S v run + +// Canonical ImGui/ImPlot binding generator. -// Compatibility entry point for the canonical Bash generator. Keeping the -// generation logic in one implementation prevents the two paths from -// producing subtly different bindings. import os +const repo_dir = @DIR +const c2v_flags = '-DSTATIC_BUILD=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCIMGUI_DEFINE_ENUMS_AND_STRUCTS=ON -DIMGUI_STATIC=OFF -DCIMGUI_NO_EXPORT=ON -DCIMGUI_USE_GLFW=ON' + +fn usage() { + println('Usage: v run generate.vsh [--regenerate-c]') + println('By default, translate the generated C API committed by cimgui/cimplot.') +} + +fn run_at(command string, directory string) ! { + println('\n> ${command}') + result := os.execute('cd ${os.quoted_path(directory)} && ${command}') + if result.output.trim_space() != '' { + println(result.output.trim_right('\r\n')) + } + if result.exit_code != 0 { + return error('command failed with exit code ${result.exit_code}') + } +} + +fn remove_file(path string) ! { + if os.is_file(path) { + os.rm(path)! + } +} + +fn copy_matching(pattern string, destination string) ! { + files := os.glob(pattern) or { return error('could not expand ${pattern}: ${err}') } + if files.len == 0 { + return error('no files matched ${pattern}') + } + for source in files { + os.cp(source, os.join_path(destination, os.file_name(source)))! + } +} + +fn add_translation_fix(path string) ! { + program := r's/(struct\s[\w\d]+\s\{[^\}]+(?:union\s+\{[^\}]+\};[^\}]+)?\};\s)(typedef\s(?!struct|enum)[^\n]+)/$1\n\nstruct ____TRANSLATIONFIX____;\n$2/g' + run_at('perl -p -i -g -e ${os.quoted_path(program)} ${os.quoted_path(path)}', repo_dir)! +} + fn main() { - mut parts := [os.quoted_path(os.join_path(@DIR, 'generate_v.sh'))] + mut regenerate_c := false for arg in os.args[1..] { - parts << os.quoted_path(arg) + match arg { + '--regenerate-c' { + regenerate_c = true + } + '-h', '--help' { + usage() + return + } + else { + eprintln('Unknown option: ${arg}') + usage() + exit(2) + } + } + } + + println('Generating bindings in ${repo_dir}') + remove_file(os.join_path(repo_dir, 'cimgui', 'CMakeCache.txt')) or { panic(err) } + remove_file(os.join_path(repo_dir, 'cimplot', 'CMakeCache.txt')) or { panic(err) } + remove_file(os.join_path(repo_dir, 'CMakeCache.txt')) or { panic(err) } + + if regenerate_c { + run_at('luajit ./generator.lua gcc internal glfw', os.join_path(repo_dir, 'cimgui', 'generator')) or { panic(err) } + } else { + println('Using the generated cimgui API committed by the pinned revision.') + } + include_dir := os.join_path(repo_dir, 'include') + copy_matching(os.join_path(repo_dir, 'cimgui', '*.h'), include_dir) or { panic(err) } + copy_matching(os.join_path(repo_dir, 'cimgui', '*.cpp'), include_dir) or { panic(err) } + add_translation_fix(os.join_path(include_dir, 'cimgui.h')) or { panic(err) } + + if regenerate_c { + run_at('luajit ./generator.lua gcc internal glfw', os.join_path(repo_dir, 'cimplot', 'generator')) or { panic(err) } + } else { + println('Using the generated cimplot API committed by the pinned revision.') + } + copy_matching(os.join_path(repo_dir, 'cimplot', '*.h'), include_dir) or { panic(err) } + copy_matching(os.join_path(repo_dir, 'cimplot', '*.cpp'), include_dir) or { panic(err) } + add_translation_fix(os.join_path(include_dir, 'cimplot.h')) or { panic(err) } + + imgui_include := os.join_path(include_dir, 'imgui') + os.path_separator + implot_include := os.join_path(include_dir, 'implot') + os.path_separator + os.mkdir_all(imgui_include)! + os.mkdir_all(implot_include)! + run_at('git checkout-index -a -f --prefix=${os.quoted_path(imgui_include)}', os.join_path(repo_dir, 'cimgui', 'imgui')) or { panic(err) } + run_at('git checkout-index -a -f --prefix=${os.quoted_path(implot_include)}', os.join_path(repo_dir, 'cimplot', 'implot')) or { panic(err) } + + os.write_file(os.join_path(include_dir, 'c2v.toml'), '[project]\nadditional_flags = "${c2v_flags}"\n')! + run_at('v translate cimgui.h', include_dir) or { panic(err) } + run_at('v translate cimplot.h', include_dir) or { panic(err) } + remove_file(os.join_path(include_dir, 'cimgui.json')) or { panic(err) } + remove_file(os.join_path(include_dir, 'cimplot.json')) or { panic(err) } + + imgui_binding := os.join_path(repo_dir, 'imgui.v') + implot_binding := os.join_path(repo_dir, 'implot', 'implot.v') + os.mv(os.join_path(include_dir, 'cimgui.v'), imgui_binding)! + os.mv(os.join_path(include_dir, 'cimplot.v'), implot_binding)! + cleanup := os.join_path(repo_dir, 'cleanup_imgui_implot.perl') + run_at('perl ${os.quoted_path(cleanup)} ${os.quoted_path(imgui_binding)} ${os.quoted_path(imgui_binding)} imgui', repo_dir) or { panic(err) } + run_at('perl ${os.quoted_path(cleanup)} ${os.quoted_path(implot_binding)} ${os.quoted_path(implot_binding)} implot', repo_dir) or { panic(err) } + run_at('v fmt -w ${os.quoted_path(imgui_binding)}', repo_dir) or { panic(err) } + // Do not format ImPlot: its C field/type pair `Marker Marker` is currently + // collapsed by vfmt into invalid V. + run_at('v run ${os.quoted_path(os.join_path(repo_dir, 'build_vimgui.vsh'))}', repo_dir) or { + panic(err) } - result := os.execute(parts.join(' ')) - print(result.output) - exit(result.exit_code) } diff --git a/generate_v.sh b/generate_v.sh deleted file mode 100755 index 389bd01..0000000 --- a/generate_v.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -regenerate_c=0 -while (($#)); do - case "$1" in - --regenerate-c) regenerate_c=1; shift ;; - -h|--help) - printf '%s\n' 'Usage: generate_v.sh [--regenerate-c]' - printf '%s\n' 'By default, translate the generated C API committed by cimgui/cimplot.' - exit 0 ;; - *) printf 'Unknown option: %s\n' "$1" >&2; exit 2 ;; - esac -done - -# Make sure the current working dir = this script dir -SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd) -pushd "$SCRIPT_DIR" >/dev/null -printf " --- Changed working dir to\n$SCRIPT_DIR\n\n" - -TARGETS_CIMGUI="internal" #"comments constructors internal noimstrv" -TARGETS_CIMPLOT="internal" -#DFLAGS="-DCMAKE_BUILD_TYPE=RelWithDebInfo -DCIMGUI_DEFINE_ENUMS_AND_STRUCTS=ON -DIMGUI_STATIC=ON -DCIMGUI_NO_EXPORT=ON -DCIMGUI_USE_GLFW=ON" -DFLAGS="-DSTATIC_BUILD=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCIMGUI_DEFINE_ENUMS_AND_STRUCTS=ON -DIMGUI_STATIC=OFF -DCIMGUI_NO_EXPORT=ON -DCIMGUI_USE_GLFW=ON" -CFLAGS="glfw" #opengl3 opengl2 sdl2 sdl3" - -printf " --- Generate cimgui\n\n" -rm -f cimgui/CMakeCache.txt cimplot/CMakeCache.txt CMakeCache.txt -if ((regenerate_c)); then - pushd cimgui/generator - # ./generator.lua "" - luajit ./generator.lua gcc $TARGETS_CIMGUI $CFLAGS &> /dev/null - popd -else - printf " Using the generated API committed by the pinned cimgui revision\n" -fi - -printf " --- Copy cimgui to include\n\n" -cp cimgui/*.h include/ -cp cimgui/*.cpp include/ - -printf " --- Add ____TRANSLATIONFIX____ to include/cimgui.h\n\n" -# -p=print each line -i=edit in place -g=whole file at once -e=execute -# Each struct, where typedef comes right after, but not struct or enum -# Note: Struct may contain another scope inside for the union definition, which has { } -perl -p -i -g -e 's/(struct\s[\w\d]+\s\{[^\}]+(?:union\s+\{[^\}]+\};[^\}]+)?\};\s)(typedef\s(?!struct|enum)[^\n]+)/$1\n\nstruct ____TRANSLATIONFIX____;\n$2/g' include/cimgui.h - -printf " --- Generate cimplot\n\n" -if ((regenerate_c)); then - pushd cimplot/generator - luajit ./generator.lua gcc $TARGETS_CIMPLOT $CFLAGS &> /dev/null - popd -else - printf " Using the generated API committed by the pinned cimplot revision\n" -fi - -printf " --- Copy cimplot to include\n\n" -cp cimplot/*.cpp include/ -cp cimplot/*.h include/ - -printf " --- Add ____TRANSLATIONFIX____ to include/cimplot.h\n\n" -perl -p -i -g -e 's/(struct\s[\w\d]+\s\{[^\}]+(?:union\s+\{[^\}]+\};[^\}]+)?\};\s)(typedef\s(?!struct|enum)[^\n]+)/$1\n\nstruct ____TRANSLATIONFIX____;\n$2/g' include/cimplot.h - -#printf "Remove Asserts" -#perl -p -i -g -e 's/(IM_ASSERT\(ImGuiImplVulkanFuncs_vkCmdBeginRenderingKHR != nullptr\);)/\/\/$1/g' cimgui/imgui/backends/imgui_impl_vulkan.cpp -#perl -p -i -g -e 's/(IM_ASSERT\(ImGuiImplVulkanFuncs_vkCmdEndRenderingKHR != nullptr\);)/\/\/$1/g' cimgui/imgui/backends/imgui_impl_vulkan.cpp -#perl -p -i -g -e 's/(IM_ASSERT\(info->ImageCount >= info->MinImageCount\);)/\/\/$1/g' cimgui/imgui/backends/imgui_impl_vulkan.cpp - -printf " --- Copy imgui to ./include/imgui\nNote, ./cimgui/imgui submodule is copied, instead of ./imgui\n\n" -pushd cimgui/imgui - git checkout-index -a -f --prefix=$SCRIPT_DIR/include/imgui/ -popd - -printf " --- Copy implot to ./include/implot\n\n" -pushd cimplot/implot - git checkout-index -a -f --prefix=$SCRIPT_DIR/include/implot/ -popd - -pushd include - printf " --- Translate to V\n\n" - printf "[project]\nadditional_flags = \"$DFLAGS\"\n" > c2v.toml - v translate cimgui.h #&> /dev/null - v translate cimplot.h #&> /dev/null - # v translate leaves machine-specific Clang AST metadata behind. It is not - # needed by the bindings and contains absolute builder and system paths. - rm -f cimgui.json cimplot.json -popd - -printf " --- Move implot&gui.v\n\n" -mv -f include/cimplot.v implot/implot.v -mv -f include/cimgui.v imgui.v - -printf " --- Clean generated imgui & implot bindings\n\n" -./cleanup_imgui_implot.perl imgui.v imgui.v imgui -./cleanup_imgui_implot.perl implot/implot.v implot/implot.v implot -v fmt -w imgui.v -# Do not run v fmt on the generated ImPlot binding. ImPlotSpec has a C field -# named `Marker` whose V type is also `Marker`; the formatter currently -# collapses `Marker Marker` to `Marker`, producing invalid V source. - -printf " --- Build vimgui\n\n" -v run build_vimgui.vsh - -popd >/dev/null diff --git a/scripts/update_upstream.sh b/scripts/update_upstream.sh index 0e1735c..14706d1 100755 --- a/scripts/update_upstream.sh +++ b/scripts/update_upstream.sh @@ -54,7 +54,7 @@ git -C cimplot submodule update --init --recursive printf '%s\n' "$variant" > UPSTREAM_VARIANT ./scripts/configure_variant.sh "$variant" -./generate_v.sh "${regenerate_arg[@]}" +v run generate.vsh "${regenerate_arg[@]}" printf 'Updated %s bindings: cimgui=%s cimplot=%s\n' \ "$variant" \