On Sat, Jun 8, 2024 at 4:10 PM Timofey Zhakov <[email protected]> wrote:
>
> Hi all!
>
> When I was starting using and developing Subversion, I had a big
> challenge to build it for the first time, especially because
> Subversion itself and its dependencies have different build systems,
> so this is a very complicated process.
>
> The build process can be improved by adding a generator for the CMake
> build system. This can be easily implemented due to the extensibility
> of the Subversion's gen-make.
>
> Some advantages of using CMake in Subversion:
> - CMake is used by some Subversion dependencies, so there is nothing
> else to learn.
> - Great Visual Studio integration for build and tests. In addition,
> there is a VSCode extension for CMake.
> - CMake provides a very useful and simple system for finding dependencies.
> - Better for build scripts and reproducibility of the build.
>
> The commands that would be needed to build Subversion, if you have all
> dependencies installed:
>
> ```
> $ .\gen-make.py -t cmake
> $ cmake -B out -DCMAKE_INSTALL_PREFIX=/path/to/deps
> $ cmake --build out
> ```
>
> Additionally, there is a great tool, vcpkg, which can be used to build
> dependencies. It is integrated with CMake. The following command can
> be used to install dependencies:
>
> ```
> git clone https://github.com/microsoft/vcpkg
> cd vcpkg
> .\bootstrap-vcpkg.bat -disableMetrics
> .\vcpkg.exe install apr apr-util expat zlib
> ```
>
> I am attaching a draft patch with the implementation of the CMake generator.
>
> What do you think?
Hello,
I'm updating the patch with improved dependency searching, related to
APR and APR Util, and some minor improvements.
Additionally, I realized that I didn't implement the SQL header
generation. I didn't notice it earlier because I had the headers
after using the vcnet project files. I'll do it soon.
--
Timofei Zhakov
Index: .
===================================================================
--- . (revision 1918202)
+++ . (working copy)
Property changes on: .
___________________________________________________________________
Modified: svn:ignore
## -65,3 +65,5 ##
.swig_pl_checked
.swig_py_checked
.swig_rb_checked
+out
+CMakeLists.txt
Index: build/generator/gen_cmake.py
===================================================================
--- build/generator/gen_cmake.py (nonexistent)
+++ build/generator/gen_cmake.py (working copy)
@@ -0,0 +1,199 @@
+import os
+import stat
+import sys
+from build.generator.gen_make import UnknownDependency, _normstr
+import ezt
+import gen_base
+
+class _eztdata(object):
+ def __init__(self, **kw):
+ vars(self).update(kw)
+
+class cmake_target():
+ def __init__(self, name: str, type: str, deps: str, libs: str,
+ sources, msvc_libs, msvc_export, msvc_static):
+ self.name = name
+ self.type = type
+ self.deps = deps
+ self.libs = libs
+ self.sources = sources
+ self.msvc_libs = msvc_libs
+ self.msvc_export = msvc_export
+ self.msvc_static = ezt.boolean(msvc_static)
+
+ self.has_msvc_libs = ezt.boolean(len(msvc_libs) > 0)
+
+def get_target_type(target: gen_base.Target):
+ if isinstance(target, gen_base.TargetExe):
+ if target.install == "test" or target.install == "sub-test":
+ return "test"
+ else:
+ return "exe"
+ if isinstance(target, gen_base.TargetSWIG):
+ return "swig"
+ if isinstance(target, gen_base.TargetSWIGProject):
+ return "swig-project"
+ if isinstance(target, gen_base.TargetSWIGLib):
+ return "swig-lib"
+ if isinstance(target, gen_base.TargetLib):
+ return "lib"
+ else:
+ return str(type(target))
+
+class Generator(gen_base.GeneratorBase):
+ _extension_map = {
+ ('exe', 'target'): '.exe',
+ ('exe', 'object'): '.obj',
+ ('lib', 'target'): '.dll',
+ ('lib', 'object'): '.obj',
+ ('pyd', 'target'): '.pyd',
+ ('pyd', 'object'): '.obj',
+ ('so', 'target'): '.so',
+ ('so', 'object'): '.obj',
+ }
+
+ def __init__(self, fname, verfname, options=None):
+ gen_base.GeneratorBase.__init__(self, fname, verfname, options)
+
+ def write(self):
+ install_deps = self.graph.get_deps(gen_base.DT_INSTALL)
+
+ install_sources = self.get_install_sources()
+
+ # ensure consistency between runs
+ install_deps.sort()
+
+ targets = []
+ ra_modules = []
+ fs_modules = []
+
+ for target_ob in self.graph.get_sources(gen_base.DT_LINK, "ra-libs"):
+ name = target_ob.name
+ if name == "libsvn_ra_serf":
+ pass
+ else:
+ ra_modules.append(target_ob.name)
+ for target_ob in self.graph.get_sources(gen_base.DT_LINK, "fs-libs"):
+ name = target_ob.name
+ if name == "libsvn_fs_base":
+ pass
+ else:
+ fs_modules.append(target_ob.name)
+
+ for target_ob in install_sources:
+ target_ob: gen_base.Target
+
+ if isinstance(target_ob, gen_base.TargetScript):
+ # there is nothing to build
+ continue
+
+ sources = []
+ deps = []
+ libs = []
+
+ for link_dep in self.graph.get_sources(gen_base.DT_LINK, target_ob.name):
+ if isinstance(link_dep, gen_base.TargetJava):
+ deps.append(link_dep.name)
+ elif isinstance(link_dep, gen_base.TargetLinked):
+ if link_dep.external_lib:
+ name = link_dep.name
+ if name == "ra-libs":
+ libs += ra_modules
+ elif name == "fs-libs":
+ libs += fs_modules
+ elif name in ["apriconv",
+ "apr_memcache",
+ "magic",
+ "intl",
+ "macos-plist",
+ "macos-keychain",
+ "sasl"]:
+ pass
+ else:
+ libs.append("external-" + name)
+ elif link_dep.external_project:
+ pass
+ else:
+ libs.append(link_dep.name)
+ elif isinstance(link_dep, gen_base.ObjectFile):
+ deps = self.graph.get_sources(gen_base.DT_OBJECT,
+ link_dep,
+ gen_base.SourceFile)
+ for dep in deps:
+ sources.append(dep.filename)
+ elif isinstance(link_dep, gen_base.HeaderFile):
+ pass
+ else:
+ raise UnknownDependency
+
+ target_type = get_target_type(target_ob)
+ target_name = target_ob.name
+
+ if target_type == "exe" or target_type == "lib" or target_type == "test":
+ msvc_libs = []
+ for lib in target_ob.msvc_libs:
+ if lib == "setargv.obj":
+ pass
+ else:
+ msvc_libs.append(lib)
+
+ msvc_export = []
+ msvc_static = False
+ if isinstance(target_ob, gen_base.TargetLib):
+ for export in target_ob.msvc_export:
+ path = "subversion/include/" + export.replace("\\", "/")
+ msvc_export.append(path)
+ msvc_static = target_ob.msvc_static
+
+ targets.append(cmake_target(target_name, target_type,
+ deps, libs, sources,
+ msvc_libs, msvc_export, msvc_static))
+
+ data = _eztdata(
+ targets = targets,
+ )
+
+ template = ezt.Template(os.path.join('build', 'generator', 'templates',
+ 'CMakeLists.txt.ezt'),
+ compress_whitespace=False)
+ template.generate(open('CMakeLists.txt', 'w'), data)
+
+ def get_install_sources(self):
+ install_sources = self.graph.get_all_sources(gen_base.DT_INSTALL)
+ result = []
+
+ for target in install_sources:
+ target: gen_base.Target
+
+ if not self.check_ignore_target(target):
+ result.append(target)
+
+ result.sort(key = lambda s: s.name)
+
+ return result
+
+ def check_ignore_target(self, target: gen_base.Target):
+ ignore_names = [
+ "libsvn_fs_base",
+ "libsvn_auth_gnome_keyring",
+ "libsvn_auth_kwallet",
+ "libsvn_ra_serf",
+ "libsvnxx",
+ "svnxx-tests",
+ "mod_dav_svn",
+ "mod_authz_svn",
+ "mod_dontdothat",
+ "__JAVAHL__",
+ "__JAVAHL_TESTS__",
+ "libsvnjavahl",
+ ]
+
+ for name in ignore_names:
+ if target.name == name:
+ return True
+
+ if isinstance(target, gen_base.TargetExe):
+ if target.install == "bdb-test":
+ return True
+
+ return False
Property changes on: build/generator/gen_cmake.py
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: build/generator/templates/CMakeLists.txt.ezt
===================================================================
--- build/generator/templates/CMakeLists.txt.ezt (nonexistent)
+++ build/generator/templates/CMakeLists.txt.ezt (working copy)
@@ -0,0 +1,225 @@
+cmake_minimum_required(VERSION 3.5)
+
+project("Subversion")
+
+option(SVN_BUILD_PROGRAMS "Build Subversion programs (such as svn.exe)" ON)
+option(SVN_BUILD_TEST "Build Subversion test-suite" OFF)
+
+option(BUILD_SHARED_LIBS "Build using shared libraries" OFF)
+option(SVN_SQLITE_USE_AMALGAMATION "Use sqlite amalgamation" ON)
+set(SVN_SQLITE_AMALGAMATION_DIR "${CMAKE_SOURCE_DIR}/sqlite-amalgamation"
+ CACHE STRING "Directory with sqlite amalgamation"
+)
+option(SVN_USE_INTERNAL_UTF8PROC "Use internal version of utf8proc" ON)
+option(SVN_USE_INTERNAL_LZ4 "Use internal version of lz4" ON)
+
+find_package(Python COMPONENTS Interpreter REQUIRED)
+
+configure_file(
+ "${CMAKE_CURRENT_SOURCE_DIR}/subversion/svn_private_config.hw"
+ "${CMAKE_CURRENT_BINARY_DIR}/svn_private_config.h"
+)
+
+set(SVN_INCLUDE_DIRECTORIES
+ "${CMAKE_CURRENT_SOURCE_DIR}/subversion/include"
+ "${CMAKE_CURRENT_BINARY_DIR}"
+)
+
+if (WIN32)
+ add_compile_definitions(
+ "alloca=_alloca"
+ )
+endif()
+
+# APR config might not exist, so don't warn if it is not found.
+# It will be handled later.
+find_package(apr QUIET)
+
+if(apr_FOUND)
+ # It could rather APR-1 or APR-2
+ message("Found APR config.")
+else()
+ message("APR config not found. Looking for libraries and includes...")
+endif()
+
+# 1. Look for shared APR-2 config then for its static version.
+# 2. If APR-2 was not found, look for shared or static APR-1.
+# 3. If APR config was not found, import the target manually.
+# 4. Import APR-Util target manually, because it doesn't have a config.
+if(TARGET apr::libapr-2)
+ add_library(external-apr ALIAS apr::libapr-2)
+ add_library(external-aprutil ALIAS apr::libapr-2)
+ message("Using shared APR-2 as APR and APR-Util in config mode.")
+elseif(TARGET apr::apr-2)
+ add_library(external-apr ALIAS apr::apr-2)
+ add_library(external-aprutil ALIAS apr::apr-2)
+ message("Using static APR-2 as APR and APR-Util in config mode.")
+else()
+ # APR
+ if(TARGET apr::libapr-1)
+ add_library(external-apr ALIAS apr::libapr-1)
+ message("Using shared APR-1 as APR in config mode.")
+ elseif(TARGET apr::apr-1)
+ add_library(external-apr ALIAS apr::apr-1)
+ message("Using static APR-1 as APR in config mode.")
+ else()
+ find_library(APR_LIBRARY
+ NAMES
+ libapr-1
+ apr-1
+ PATH_SUFFIXES lib
+ )
+
+ if(${APR_LIBRARY} STREQUAL "APR_LIBRARY-NOTFOUND")
+ message(FATAL_ERROR "APR library not found")
+ else()
+ message("Found APR library: ${APR_LIBRARY}")
+ endif()
+
+ find_path(APR_INCLUDE_DIR
+ NAMES apr.h
+ PATH_SUFFIXES
+ include
+ include/apr-1
+ include/apr-2
+ )
+
+ if(${APR_INCLUDE_DIR} STREQUAL "APR_INCLUDE_DIR-NOTFOUND")
+ message(FATAL_ERROR "APR include dir not found")
+ else()
+ message("Found APR include dir: ${APR_LIBRARY}")
+ endif()
+
+ add_library(external-apr STATIC IMPORTED)
+
+ set_target_properties(external-apr PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES ${APR_INCLUDE_DIR}
+ IMPORTED_LOCATION ${APR_LIBRARY}
+ )
+ endif()
+
+ # APR-Util
+ find_library(APRUTIL_LIBRARY
+ NAMES
+ libaprutil-1
+ aprutil-1
+ PATHS ${CMAKE_PREFIX_PATH}
+ PATH_SUFFIXES lib
+ )
+
+ if(${APRUTIL_LIBRARY} STREQUAL "APRUTIL_LIBRARY-NOTFOUND")
+ message(FATAL_ERROR "APR-Util library not found")
+ else()
+ message("Found APR-Util library: ${APRUTIL_LIBRARY}")
+ endif()
+
+ find_path(APRUTIL_INCLUDE_DIR
+ NAMES apu.h
+ PATHS ${CMAKE_PREFIX_PATH}
+ PATH_SUFFIXES
+ include
+ include/aprutil-1 # Not yet in apr
+ )
+
+ if(${APRUTIL_INCLUDE_DIR} STREQUAL "APRUTIL_INCLUDE_DIR-NOTFOUND")
+ message(FATAL_ERROR "APR-Util include dir not found")
+ else()
+ message("Found APR-Util include dir: ${APRUTIL_INCLUDE_DIR}")
+ endif()
+
+ add_library(external-aprutil STATIC IMPORTED)
+
+ set_target_properties(external-aprutil PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES ${APRUTIL_INCLUDE_DIR}
+ IMPORTED_LOCATION ${APRUTIL_LIBRARY}
+ )
+endif()
+
+find_package(ZLIB)
+add_library(external-zlib ALIAS ZLIB::ZLIB)
+
+find_package(expat CONFIG REQUIRED)
+add_library(external-xml ALIAS expat::expat)
+
+if(SVN_USE_INTERNAL_LZ4)
+ add_library(external-lz4 STATIC "build/win32/empty.c")
+ target_compile_definitions(external-lz4 PUBLIC "SVN_INTERNAL_LZ4")
+else()
+ find_package(lz4 CONFIG REQUIRED)
+ add_library(external-lz4 ALIAS lz4::lz4)
+endif()
+
+if(SVN_USE_INTERNAL_UTF8PROC)
+ add_library(external-utf8proc STATIC "build/win32/empty.c")
+ target_compile_definitions(external-utf8proc PUBLIC "SVN_INTERNAL_UTF8PROC")
+else()
+ # TODO:
+ # find_package(utf8proc CONFIG REQUIRED)
+ # add_library(external-utf8proc ALIAS utf8proc)
+endif()
+
+if(SVN_SQLITE_USE_AMALGAMATION)
+ add_library(external-sqlite STATIC "build/win32/empty.c")
+ target_include_directories(external-sqlite
+ PUBLIC "${SVN_SQLITE_AMALGAMATION_DIR}")
+ target_compile_definitions(external-sqlite PUBLIC SVN_SQLITE_INLINE)
+else()
+ find_package(SQLite3 REQUIRED)
+ add_library(external-sqlite ALIAS SQLite::SQLite3)
+endif()
+
+install(DIRECTORY "subversion/include/" DESTINATION "include/subversion-1")
+[for targets][is targets.type "exe"]
+if (SVN_BUILD_PROGRAMS)
+ add_executable([targets.name][for targets.sources]
+ [targets.sources][end]
+ )
+ install(TARGETS [targets.name])
+ [end][is targets.type "lib"]
+if (TRUE) # TODO:
+ add_library([targets.name][if-any targets.msvc_static] STATIC[end][for
targets.sources]
+ [targets.sources][end]
+ )
+ if(BUILD_SHARED_LIBS)
+ set([targets.name]_HEADERS[for targets.msvc_export]
+ "[targets.msvc_export]"[end]
+ )
+ add_custom_command(
+ WORKING_DIRECTORY
+ ${CMAKE_SOURCE_DIR}
+ COMMAND
+ ${Python_EXECUTABLE}
+ ARGS
+ "build/generator/extractor.py"
+ ${[targets.name]_HEADERS}
+ ">${CMAKE_BINARY_DIR}/[targets.name].def"
+ OUTPUT
+ "${CMAKE_BINARY_DIR}/[targets.name].def"
+ DEPENDS
+ "build/generator/extractor.py"
+ ${[targets.name]_HEADERS}
+ )
+ target_sources([targets.name] PRIVATE
"${CMAKE_BINARY_DIR}/[targets.name].def")
+ endif()
+ target_include_directories([targets.name] PUBLIC ${SVN_INCLUDE_DIRECTORIES})
+ install(TARGETS [targets.name])
+[end][is targets.type "test"]
+if(SVN_BUILD_TEST)
+ add_executable([targets.name][for targets.sources] [targets.sources][end])
+ add_test([targets.name] [targets.name])
+[end] target_link_libraries([targets.name] PRIVATE[for targets.libs]
+ [targets.libs][end]
+ )[if-any targets.has_msvc_libs]
+ if(WIN32)
+ target_link_libraries([targets.name] PRIVATE[for targets.msvc_libs]
[targets.msvc_libs][end])
+ endif()[end]
+endif()
+[end]
+if(SVN_BUILD_TEST)
+ enable_testing()
+endif()
+
+# Enable globbing for all executables
+if(MSVC)
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} setargv.obj")
+endif()
Index: gen-make.py
===================================================================
--- gen-make.py (revision 1918202)
+++ gen-make.py (working copy)
@@ -49,6 +49,7 @@
gen_modules = {
'make' : ('gen_make', 'Makefiles for POSIX systems'),
'vcproj' : ('gen_vcnet_vcproj', 'VC.Net project files'),
+ 'cmake' : ('gen_cmake', 'CMake build system'),
}
def main(fname, gentype, verfname=None,