svn commit: r1936349 - in subversion/trunk: . .github/workflows build/generator build/generator/templates subversion/bindings/swig/ruby/test

[email protected] Mon, 20 Jul 2026 08:31:24 -0000
Newsgroups gmane.comp.version-control.subversion.svn
Message-ID <178453628423.2581699.5759084101999374968@svn03-he-fi>
Author: jun66j5
Date: Mon Jul 20 08:31:24 2026
New Revision: 1936349

Log:
cmake: Fix build, test and install for Perl, Python and Ruby bindings.

* .github/workflows/cmake.yml
  (Install dependencies (Mac OS, homebrew)):
    Add swig to install.
  (Checkout py3c):
    Checkout py3c 1.4 from encukou/py3c for building swig-py on Linux and
    macOS.
  (Configure CMake):
    Enable building swig-pl, swig-py and swig-rb on Ubuntu and macOS, and
    configure py3c include directory.

* CMakeLists.txt
  (cmake_minimum_required):
    Bump up to `3.22` for `ENVIRONMENT_MODIFICATION` feature.
  (SVN_ENABLE_SWIG_PERL_MAN_PAGES):
    Add the option to install man pages for swig-pl.
  (SVN_SWIG_PERL_SITEARCH):
    Add the variable for path to install sitearch files of swig-pl module.
  (SVN_SWIG_PERL_MAN3DIR):
    Add the variable for path to install man pages of swig-pl module.
  (SVN_SWIG_PYTHON_SITELIB):
    Add the variable for path to install sitelib files of swig-py module.
  (SVN_SWIG_PYTHON_SITEARCH):
    Add the variable for path to install sitearch files of swig-py module.
  (SVN_SWIG_RUBY_SITELIB):
    Add the variable for path to install sitelib files of swig-rb module.
  (SVN_SWIG_RUBY_SITEARCH):
    Add the variable for path to install sitearch files of swig-rb module.
  (testing):
    Enable testing if `SVN_ENABLE_SWIG_PERL`, `SVN_ENABLE_SWIG_PYTHON` or
    `SVN_ENABLE_SWIG_RUBY` is enabled.
  (SVN_SWIG_PY_OPTS, SVN_SWIG_PY_OPTS, SVN_SWIG_RB_OPTS):
    Add the variables for the options of swig command to build swig-* bindings.
  (swig-pl, swig-py, swig-rb):
    Add install steps and test steps.
    Set `SVN_TEST_SWIG_RUBY=ctest` environment and add the build directory to
    `PATH` environment on running the tests of swig-rb.

* build/generator/gen_cmake.py
  (get_output_name):
    Fix the output name of the extension modules for swig-pl and swig-rb.

* build/generator/templates/targets.cmake.ezt
  (swig-pl, swig-py, swig-rb):
    Use swig-python, swig-perl and swig-ruby in the build directory for
    `OUTPUT_DIR`.
    Use `ARCHIVE_OUTPUT_NAME` to fix the conflict of *.lib and *.exp files for
    swig-py and swig-rb on Windows.
    Use *.so suffix for the extension modules of swig-rb on Windows.

* subversion/bindings/swig/ruby/test/run-test.rb
  ():
    Avoid unnecessary initialization when running the tests via ctest.

* subversion/bindings/swig/ruby/test/windows_util.rb
  (SvnTestUtil.setup_svnserve):
    Use svnserve.exe from PATH environment when running the tests via ctest.

Modified:
   subversion/trunk/.github/workflows/cmake.yml
   subversion/trunk/CMakeLists.txt
   subversion/trunk/build/generator/gen_cmake.py
   subversion/trunk/build/generator/templates/targets.cmake.ezt
   subversion/trunk/subversion/bindings/swig/ruby/test/run-test.rb
   subversion/trunk/subversion/bindings/swig/ruby/test/windows_util.rb

Modified: subversion/trunk/.github/workflows/cmake.yml
==============================================================================
--- subversion/trunk/.github/workflows/cmake.yml	Mon Jul 20 08:30:13 2026	(r1936348)
+++ subversion/trunk/.github/workflows/cmake.yml	Mon Jul 20 08:31:24 2026	(r1936349)
@@ -191,7 +191,7 @@ jobs:
         run: |
           $packages = @('apr', 'apr-util', 'expat', 'sqlite', 'apache-serf',
                         'gettext', 'ncurses')
-          brew install ninja apache2 $packages
+          brew install ninja apache2 swig $packages
           $PKG_CONFIG_PATH = `
             $packages | `
             % { "$(& brew --prefix $_)/lib/pkgconfig" } | `
@@ -215,6 +215,14 @@ jobs:
 
       - uses: actions/checkout@v6
 
+      - name: Checkout py3c (Linux and macOS)
+        if:  runner.os == 'Linux' || runner.os == 'macOS'
+        uses: actions/checkout@v6
+        with:
+          repository: encukou/py3c
+          ref: v1.4
+          path: py3c
+
       - name: gen-make
         run: python ./gen-make.py -t cmake
 
@@ -227,6 +235,10 @@ jobs:
           -DSVN_ENABLE_NLS=ON
           -DSVN_ENABLE_APACHE_MODULES=${{ runner.os != 'Windows' && 'ON' || 'OFF' }}
           -DSVN_ENABLE_TUI=ON
+          -DSVN_ENABLE_SWIG_PERL=${{ runner.os != 'Windows' && 'ON' || 'OFF' }}
+          -DSVN_ENABLE_SWIG_PERL_MAN_PAGES=${{ runner.os != 'Windows' && 'ON' || 'OFF' }}
+          -DSVN_ENABLE_SWIG_PYTHON=${{ runner.os != 'Windows' && 'ON' || 'OFF' }}
+          -DSVN_ENABLE_SWIG_RUBY=${{ runner.os != 'Windows' && 'ON' || 'OFF' }}
           -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/installdir
           -DVCPKG_TARGET_TRIPLET=${{ matrix.vcpkg_triplet }}
           -DSVN_TEST_CONFIGURE_FOR_PARALLEL=ON
@@ -235,6 +247,7 @@ jobs:
           -DPython3_FIND_REGISTRY=NEVER
           -DPython3_FIND_STRATEGY=LOCATION
           -DPython3_FIND_UNVERSIONED_NAMES=FIRST
+          -DPY3C_INCLUDE_DIR=${{ github.workspace }}/py3c/include
           ${{ matrix.extra_config_opts }}
 
       - name: Build CMake

Modified: subversion/trunk/CMakeLists.txt
==============================================================================
--- subversion/trunk/CMakeLists.txt	Mon Jul 20 08:30:13 2026	(r1936348)
+++ subversion/trunk/CMakeLists.txt	Mon Jul 20 08:31:24 2026	(r1936349)
@@ -21,7 +21,7 @@
 
 # CMAKE_MINIMUM_REQUIRED should be the first directive in the file:
 # https://cmake.org/cmake/help/latest/command/cmake_minimum_required.html
-cmake_minimum_required(VERSION 3.14)
+cmake_minimum_required(VERSION 3.22)
 
 # CMP0092: MSVC warning flags are not in CMAKE_<LANG>_FLAGS by default.
 if(POLICY CMP0092)
@@ -79,9 +79,22 @@ set(SVN_TEST_COMMAND_ARGUMENTS "" CACHE
 option(SVN_TEST_CHECK_XML_SCHEMA "Enable extended XML schema validation" OFF)
 option(SVN_ENABLE_APACHE_MODULES "Build modules for Apache HTTPD" OFF)
 
-option(SVN_ENABLE_SWIG_PERL "Enable Subversion SWIG bindings for Perl" OFF)
-option(SVN_ENABLE_SWIG_PYTHON "Enable Subversion SWIG bindings into Python" OFF)
-option(SVN_ENABLE_SWIG_RUBY "Enable Subversion SWIG bindings into Ruby" OFF)
+option(SVN_ENABLE_SWIG_PERL "Enable Subversion Perl bindings Perl" OFF)
+option(SVN_ENABLE_SWIG_PERL_MAN_PAGES "Enable man pages for Subversion Perl bindings" OFF)
+option(SVN_ENABLE_SWIG_PYTHON "Enable Subversion Python bindings" OFF)
+option(SVN_ENABLE_SWIG_RUBY "Enable Subversion Ruby bindings" OFF)
+set(SVN_SWIG_PERL_SITEARCH "${CMAKE_INSTALL_PREFIX}/lib/svn-perl"
+  CACHE PATH "Path to install Subversion Perl bindings for sitearch")
+set(SVN_SWIG_PERL_MAN3DIR "${CMAKE_INSTALL_PREFIX}/share/man/man3"
+  CACHE PATH "Path to install Subversion Perl bindings for man3dir")
+set(SVN_SWIG_PYTHON_SITELIB "${CMAKE_INSTALL_PREFIX}/lib/svn-python"
+  CACHE PATH "Path to install Subversion Python bindings for sitelib")
+set(SVN_SWIG_PYTHON_SITEARCH "${CMAKE_INSTALL_PREFIX}/lib/svn-python"
+  CACHE PATH "Path to install Subversion Python bindings for sitearch")
+set(SVN_SWIG_RUBY_SITELIB "${CMAKE_INSTALL_PREFIX}/lib/svn-ruby"
+  CACHE PATH "Path to install Subversion Ruby bindings for sitelib")
+set(SVN_SWIG_RUBY_SITEARCH "${CMAKE_INSTALL_PREFIX}/lib/svn-ruby"
+  CACHE PATH "Path to install Subversion Ruby bindings for sitearch")
 
 # Enable modules and features
 option(SVN_ENABLE_RA_LOCAL "Enable Subversion Local Repository Access Library" ON)
@@ -210,7 +223,8 @@ add_private_config_definition(
   "SVN_SOVERSION" "${SVN_SOVERSION}"
 )
 
-if (SVN_ENABLE_TESTS)
+if (SVN_ENABLE_TESTS OR SVN_ENABLE_SWIG_PERL OR SVN_ENABLE_SWIG_PYTHON OR
+    SVN_ENABLE_SWIG_RUBY)
   enable_testing()
 endif()
 
@@ -246,6 +260,36 @@ if (SVN_ENABLE_SWIG_PERL OR SVN_ENABLE_S
     ${CMAKE_CURRENT_BINARY_DIR}/subversion/bindings/swig/proxy
     ${CMAKE_CURRENT_BINARY_DIR}
   )
+
+  if(SVN_ENABLE_SWIG_PYTHON)
+    if(SWIG_VERSION VERSION_LESS "3.0.10")
+      message(SEND_ERROR
+        "Subversion Python bindings require SWIG 3.0.10 or newer")
+    elseif(SWIG_VERSION VERSION_LESS "4.0.0")
+      set(SVN_SWIG_PY_OPTS "-py3;-nofastunpack;-modern")
+    elseif(SWIG_VERSION VERSION_LESS "4.1.0")
+      set(SVN_SWIG_PY_OPTS "-py3;-nofastunpack")
+    else()
+      set(SVN_SWIG_PY_OPTS "-nofastunpack")
+    endif()
+  endif()
+
+  if(SVN_ENABLE_SWIG_PERL)
+    set(SVN_SWIG_PL_OPTS "-nopm;-noproxy")
+  endif()
+
+  if(SVN_ENABLE_SWIG_RUBY)
+    if(SWIG_VERSION VERSION_EQUAL "4.2.0")
+      message(WARNING
+        "Ruby bindings cannot be built with swig 4.2.0, see "
+        "https://github.com/swig/swig/issues/2751")
+    elseif(SWIG_VERSION VERSION_EQUAL "3.0.8")
+      message(WARNING
+        "Ruby bindings are known not to support swig 3.0.8, see https://"
+        "subversion.apache.org/docs/release-notes/1.11#ruby-swig-issue-602")
+    endif()
+    set(SVN_SWIG_RB_OPTS)
+  endif()
 endif()
 
 if (MSVC)
@@ -828,13 +872,65 @@ if(SVN_ENABLE_SWIG_PYTHON)
     ${SWIG_INCLUDE_DIRECTORIES}
     ${CMAKE_CURRENT_SOURCE_DIR}/subversion/bindings/swig/python/libsvn_swig_py
   )
-
   swig_target_external_runtime(external-python python)
+
+  add_custom_command(TARGET libsvn_swig_py POST_BUILD
+    COMMAND ${CMAKE_COMMAND} -E copy_directory
+      "${CMAKE_CURRENT_SOURCE_DIR}/subversion/bindings/swig/python/svn"
+      "${CMAKE_CURRENT_BINARY_DIR}/swig-python/svn"
+    COMMAND ${CMAKE_COMMAND} -E make_directory
+      "${CMAKE_CURRENT_BINARY_DIR}/swig-python/libsvn"
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "${CMAKE_CURRENT_SOURCE_DIR}/subversion/bindings/swig/python/__init__.py"
+      "${CMAKE_CURRENT_BINARY_DIR}/swig-python/libsvn/__init__.py"
+  )
+  install(
+    DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/subversion/bindings/swig/python/svn/"
+    DESTINATION "${SVN_SWIG_PYTHON_SITELIB}/svn"
+    FILES_MATCHING
+      PATTERN "__pycache__" EXCLUDE
+      PATTERN "*.py"
+  )
+  install(
+    DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/swig-python/libsvn/"
+    DESTINATION "${SVN_SWIG_PYTHON_SITEARCH}/libsvn"
+    FILES_MATCHING
+      PATTERN "__pycache__" EXCLUDE
+      PATTERN "*.py"
+      PATTERN "*.so"
+      PATTERN "*.pyd"
+  )
+  install(CODE [==[
+    execute_process(
+      COMMAND
+        "${Python3_EXECUTABLE}" -m compileall -q
+        "${SVN_SWIG_PYTHON_SITELIB}/svn"
+        "${SVN_SWIG_PYTHON_SITEARCH}/libsvn"
+    )
+  ]==])
+
+  add_test(
+    NAME swig-python
+    COMMAND
+      "${Python3_EXECUTABLE}"
+      ${CMAKE_CURRENT_SOURCE_DIR}/subversion/bindings/swig/python/tests/run_all.py
+      --verbose
+    WORKING_DIRECTORY
+      "${CMAKE_CURRENT_BINARY_DIR}/swig-python"
+  )
+  if(WIN32 OR CYGWIN)
+    set_property(TEST swig-python APPEND PROPERTY ENVIRONMENT_MODIFICATION
+      "PATH=path_list_prepend:$<SHELL_PATH:${CMAKE_CURRENT_BINARY_DIR}>"
+    )
+  endif()
 endif()
 
 if(SVN_ENABLE_SWIG_PERL)
   find_package(Perl REQUIRED)
   find_package(PerlLibs REQUIRED)
+  if(SVN_ENABLE_SWIG_PERL_MAN_PAGES)
+    find_program(POD2MAN_EXECUTABLE NAMES pod2man REQUIRED)
+  endif()
 
   separate_arguments(PERL_EXTRA_C_FLAGS)
 
@@ -849,8 +945,65 @@ if(SVN_ENABLE_SWIG_PERL)
     ${PERL_EXTRA_C_FLAGS}
   )
   target_link_libraries(external-perl INTERFACE ${PERL_LIBRARY})
-
   swig_target_external_runtime(external-perl perl)
+
+  block()
+    set(pm_files)
+    file(GLOB pm_files
+      "${CMAKE_CURRENT_SOURCE_DIR}/subversion/bindings/swig/perl/native/*.pm")
+    add_custom_command(TARGET libsvn_swig_perl POST_BUILD
+      COMMAND ${CMAKE_COMMAND} -E make_directory
+        "${CMAKE_CURRENT_BINARY_DIR}/swig-perl/lib/SVN"
+        "${CMAKE_CURRENT_BINARY_DIR}/swig-perl/man"
+      COMMAND ${CMAKE_COMMAND} -E copy
+        ${pm_files}
+        "${CMAKE_CURRENT_BINARY_DIR}/swig-perl/lib/SVN"
+    )
+    if(SVN_ENABLE_SWIG_PERL_MAN_PAGES)
+      foreach(pm_file ${pm_files})
+        get_filename_component(name ${pm_file} NAME_WLE)
+        set(man_file "${CMAKE_CURRENT_BINARY_DIR}/swig-perl/man/SVN::${name}.3pm")
+        add_custom_command(TARGET libsvn_swig_perl POST_BUILD
+          COMMAND ${POD2MAN_EXECUTABLE} --section=3pm ${pm_file} "${man_file}"
+        )
+      endforeach()
+    endif()
+  endblock()
+
+  install(
+    DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/swig-perl/lib/SVN/"
+    DESTINATION "${SVN_SWIG_PERL_SITEARCH}/SVN"
+    FILES_MATCHING PATTERN "*.pm"
+  )
+  install(
+    DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/swig-perl/lib/auto/SVN/"
+    DESTINATION "${SVN_SWIG_PERL_SITEARCH}/auto/SVN"
+    FILES_MATCHING PATTERN "*.so"
+  )
+  if(SVN_ENABLE_SWIG_PERL_MAN_PAGES)
+    install(
+      DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/swig-perl/man/"
+      DESTINATION "${SVN_SWIG_PERL_MAN3DIR}"
+      FILES_MATCHING PATTERN "*.3pm"
+    )
+  endif()
+
+  add_test(
+    NAME swig-perl
+    COMMAND
+      ${PERL_EXECUTABLE}
+      "-I${CMAKE_CURRENT_BINARY_DIR}/swig-perl/lib"
+      -MTest::Harness
+      -e
+      "$Test::Harness::verbose = 1; runtests(glob('t/*.t'))"
+    WORKING_DIRECTORY
+      ${CMAKE_CURRENT_SOURCE_DIR}/subversion/bindings/swig/perl/native
+  )
+  if(WIN32 OR CYGWIN)
+    set_property(TEST swig-perl APPEND PROPERTY ENVIRONMENT_MODIFICATION
+      "PATH=path_list_prepend:$<SHELL_PATH:${CMAKE_CURRENT_BINARY_DIR}>"
+    )
+  endif()
 endif()
 
 if(SVN_ENABLE_SWIG_RUBY)
@@ -870,8 +1023,38 @@ if(SVN_ENABLE_SWIG_RUBY)
     $<$<BOOL:${WIN32}>:SVN_SWIG_RUBY__CUSTOM_RUBY_CONFIG>
   )
   target_link_libraries(external-ruby INTERFACE ${Ruby_LIBRARY})
-
   swig_target_external_runtime(external-ruby ruby)
+
+  install(
+    DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/subversion/bindings/swig/ruby/svn/"
+    DESTINATION "${SVN_SWIG_RUBY_SITELIB}/svn"
+    FILES_MATCHING PATTERN "*.rb"
+  )
+  install(
+    DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/swig-ruby/svn/ext/"
+    DESTINATION "${SVN_SWIG_RUBY_SITEARCH}/svn/ext"
+    FILES_MATCHING PATTERN "*.so"
+  )
+
+  add_test(
+    NAME swig-ruby
+    COMMAND
+      "${Ruby_EXECUTABLE}"
+      "-I${CMAKE_CURRENT_BINARY_DIR}/swig-ruby"
+      "-I${CMAKE_CURRENT_SOURCE_DIR}/subversion/bindings/swig/ruby"
+      test/run-test.rb
+      --collector=dir
+      --verbose=v
+    WORKING_DIRECTORY
+      ${CMAKE_CURRENT_SOURCE_DIR}/subversion/bindings/swig/ruby
+  )
+  set_property(TEST swig-ruby APPEND PROPERTY ENVIRONMENT
+    "SVN_TEST_SWIG_RUBY=ctest"
+  )
+  # svnserve in the build directory is launched by the tests
+  set_property(TEST swig-ruby APPEND PROPERTY ENVIRONMENT_MODIFICATION
+    "PATH=path_list_prepend:$<SHELL_PATH:${CMAKE_CURRENT_BINARY_DIR}>"
+  )
 endif()
 
 ### Checks
@@ -1109,20 +1292,6 @@ if(SVN_USE_WIN32_CRASHHANDLER)
   )
 endif()
 
-if(SVN_ENABLE_SWIG_PYTHON)
-  add_custom_command(TARGET libsvn_swig_py POST_BUILD
-    COMMAND ${CMAKE_COMMAND} -E copy_directory
-      "${CMAKE_CURRENT_SOURCE_DIR}/subversion/bindings/swig/python/svn"
-      "$<TARGET_FILE_DIR:libsvn_swig_py>/PythonPackages/svn"
-  )
-
-  add_custom_command(TARGET libsvn_swig_py POST_BUILD
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "${CMAKE_CURRENT_SOURCE_DIR}/subversion/bindings/swig/python/__init__.py"
-      "$<TARGET_FILE_DIR:libsvn_swig_py>/PythonPackages/libsvn/__init__.py"
-  )
-endif()
-
 string(TOLOWER "${CMAKE_HOST_SYSTEM_PROCESSOR}-${CMAKE_HOST_SYSTEM_NAME}" SVN_BUILD_HOST)
 string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_SYSTEM_NAME}" SVN_BUILD_TARGET)
 

Modified: subversion/trunk/build/generator/gen_cmake.py
==============================================================================
--- subversion/trunk/build/generator/gen_cmake.py	Mon Jul 20 08:30:13 2026	(r1936348)
+++ subversion/trunk/build/generator/gen_cmake.py	Mon Jul 20 08:31:24 2026	(r1936349)
@@ -59,10 +59,11 @@ def get_output_name(target):
     return target.name[3:] + "-1"
   elif isinstance(target, gen_base.TargetSWIG):
     module_name = target.name[len(target.lang + "_"):]
-    if target.lang == "python":
+    if target.lang in ("python", "ruby"):
       return module_name
-    else:
-      return target.name
+    if target.lang == "perl":
+      return "_" + module_name.capitalize()
+    return target.name
   else:
     return target.name
 

Modified: subversion/trunk/build/generator/templates/targets.cmake.ezt
==============================================================================
--- subversion/trunk/build/generator/templates/targets.cmake.ezt	Mon Jul 20 08:30:13 2026	(r1936348)
+++ subversion/trunk/build/generator/templates/targets.cmake.ezt	Mon Jul 20 08:31:24 2026	(r1936349)
@@ -21,23 +21,39 @@
 #
 [for targets]
 # [if-any targets.description][targets.description][else][targets.name][end]
-if ([targets.enable_condition])[is targets.type "swig"]
-  # TODO: Use generator expressions instead of CMAKE_CURRENT_BINARY_DIR?
+if ([targets.enable_condition])[is targets.type "swig"][is targets.swig_lang "perl"]
+  # XXX UseSWIG module automatically adds the `-shadow` option for Perl.
+  #     Although the option is unnecessary, there is no settings to disable it.
+  #     Instead, we do effectively disable it by adding the `-noproxy` option
+  #     after the `-shadow` option.
+  set(SWIG_MODULE_[targets.name]_EXTRA_FLAGS "-shadow" ${SVN_SWIG_PL_OPTS})[end]
   swig_add_library([targets.name]
     LANGUAGE [targets.swig_lang]
     SOURCES[for targets.sources] [targets.sources][end][is targets.swig_lang "python"]
-    OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/PythonPackages/libsvn"[end][is targets.swig_lang "perl"]
-    OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/PerlLibs/TODO"[end][is targets.swig_lang "ruby"]
-    OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/site_ruby/TODO"[end]
+    OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/swig-python/libsvn"[end][is targets.swig_lang "perl"]
+    OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/swig-perl/lib/SVN"[end][is targets.swig_lang "ruby"]
+    OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/swig-ruby/svn/ext"[end]
   )
   set_target_properties([targets.name] PROPERTIES
     SWIG_INCLUDE_DIRECTORIES "${SWIG_INCLUDE_DIRECTORIES}"
     INCLUDE_DIRECTORIES "${SWIG_INCLUDE_DIRECTORIES}"
     OUTPUT_NAME [targets.output_name]
+    ARCHIVE_OUTPUT_NAME [targets.name]
   )[is targets.swig_lang "python"]
   set_target_properties([targets.name] PROPERTIES
-    SWIG_COMPILE_OPTIONS "-nofastunpack"
-  )[end]
+    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/swig-python/libsvn"
+    SWIG_COMPILE_OPTIONS "${SVN_SWIG_PY_OPTS}"
+  )[end][is targets.swig_lang "perl"]
+  set_target_properties([targets.name] PROPERTIES
+    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/swig-perl/lib/auto/SVN/[targets.output_name]"
+  )[end][is targets.swig_lang "ruby"]
+  set_target_properties([targets.name] PROPERTIES
+    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/swig-ruby/svn/ext"
+    SWIG_COMPILE_OPTIONS "${SVN_SWIG_RB_OPTS}"
+  )
+  if (WIN32)
+    set_target_properties([targets.name] PROPERTIES SUFFIX ".so")
+  endif()[end]
   target_link_libraries([targets.name] PRIVATE[for targets.libs]
     [targets.libs][end]
     external-[targets.swig_lang]

Modified: subversion/trunk/subversion/bindings/swig/ruby/test/run-test.rb
==============================================================================
--- subversion/trunk/subversion/bindings/swig/ruby/test/run-test.rb	Mon Jul 20 08:30:13 2026	(r1936348)
+++ subversion/trunk/subversion/bindings/swig/ruby/test/run-test.rb	Mon Jul 20 08:31:24 2026	(r1936349)
@@ -30,14 +30,18 @@ top_dir = File.expand_path(File.join(bas
 ext_dir = File.join(base_dir, ".ext")
 ext_svn_dir = File.join(ext_dir, "svn")
 ext_svn_ext_dir = File.join(ext_svn_dir, "ext")
-FileUtils.mkdir_p(ext_svn_dir)
-at_exit {FileUtils.rm_rf(ext_dir)}
+if ENV['SVN_TEST_SWIG_RUBY'] != 'ctest'
+  FileUtils.mkdir_p(ext_svn_dir)
+  at_exit {FileUtils.rm_rf(ext_dir)}
+end
 
 $LOAD_PATH.unshift(test_dir)
 require 'util'
 require 'test-unit-ext'
 
-SvnTestUtil.setup_test_environment(top_dir, base_dir, ext_svn_ext_dir)
+if ENV['SVN_TEST_SWIG_RUBY'] != 'ctest'
+  SvnTestUtil.setup_test_environment(top_dir, base_dir, ext_svn_ext_dir)
+end
 
 $LOAD_PATH.unshift(ext_dir)
 $LOAD_PATH.unshift(base_src_dir)

Modified: subversion/trunk/subversion/bindings/swig/ruby/test/windows_util.rb
==============================================================================
--- subversion/trunk/subversion/bindings/swig/ruby/test/windows_util.rb	Mon Jul 20 08:30:13 2026	(r1936348)
+++ subversion/trunk/subversion/bindings/swig/ruby/test/windows_util.rb	Mon Jul 20 08:31:24 2026	(r1936349)
@@ -38,11 +38,14 @@ module SvnTestUtil
         @svnserve_port = @svnserve_ports.last
         @repos_svnserve_uri = "svn://#{@svnserve_host}:#{@svnserve_port}"
 
-        top_directory = File.join(File.dirname(__FILE__), "..", "..", "..", "..", "..")
-        build_type = ENV["BUILD_TYPE"] || "Release"
-        svnserve_path = File.join(top_directory, build_type, 'subversion', 'svnserve', 'svnserve.exe')
+        if ENV['SVN_TEST_SWIG_RUBY'] == 'ctest'
+          svnserve_path = "svnserve.exe"
+        else
+          top_directory = File.join(File.dirname(__FILE__), "..", "..", "..", "..", "..")
+          build_type = ENV["BUILD_TYPE"] || "Release"
+          svnserve_path = File.join(top_directory, build_type, 'subversion', 'svnserve', 'svnserve.exe')
+        end
         svnserve_path = Svnserve.escape_value(svnserve_path)
-
         root = @full_repos_path.tr(File::SEPARATOR, File::ALT_SEPARATOR)
         FileUtils.mkdir_p(root)