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? -- Timofei Zhakov
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": + # TODO: + 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,148 @@ +cmake_minimum_required(VERSION 3.5) + +project("Subversion") + +option(SVN_BUILD_LIBRARIES "Build Subversion libraries" ON) +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) + +if (NOT SVN_BUILD_LIBRARIES AND (SVN_BUILD_PROGRAMS OR SVN_BUILD_TEST)) + message(FATAL_ERROR "Libraries are required for programs or tests. +Please enable SVN_BUILD_LIBRARIES") +endif() + +find_package(Python COMPONENTS Interpreter REQUIRED) + +configure_file( + "subversion/svn_private_config.hw" + "svn_private_config.h" +) + +set(SVN_INCLUDE_DIRECTORIES + "${CMAKE_CURRENT_SOURCE_DIR}/subversion/include" + "${CMAKE_CURRENT_BINARY_DIR}" +) + +add_compile_definitions( + "alloca=_alloca" +) + +find_package(apr) + +# apr-2 (from trunk) +if(TARGET apr::libapr-2) + add_library(external-apr ALIAS apr::libapr-2) + add_library(external-aprutil ALIAS apr::libapr-2) +elseif(TARGET apr::apr-2) + add_library(external-apr ALIAS apr::apr-2) + add_library(external-aprutil ALIAS apr::apr-2) + +# apr-1 (doesn't work, because apr-util doesn't provide CMake targets.) +# TODO: +elseif(TARGET apr::libapr-1 AND apr::libaprutil-1) + add_library(external-apr ALIAS apr::libapr-1) + add_library(external-aprutil ALIAS apr::libaprutil-1) +elseif(TARGET apr::apr-1 AND apr::aprutil-1) + add_library(external-apr ALIAS apr::apr-1) + add_library(external-aprutil ALIAS apr::aprutil-1) + +# vcpkg (doesn't work, because apr-util doesn't provide CMake targets.) +elseif(TARGET unofficial::apr::libapr-1 AND unofficial::apr::libaprutil-1) + add_library(external-apr ALIAS unofficial::apr::libapr-1) + add_library(external-aprutil ALIAS unofficial::apr::libaprutil-1) +elseif(TARGET unofficial::apr::apr-1 AND unofficial::apr::aprutil-1) + add_library(external-apr ALIAS unofficial::apr::apr-1) + add_library(external-aprutil ALIAS unofficial::apr::aprutil-1) +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 (SVN_BUILD_LIBRARIES) + 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] +enable_testing() Index: gen-make.py =================================================================== --- gen-make.py (revision 1918171) +++ gen-make.py (working copy) @@ -49,6 +49,7 @@ sys.path.insert(1, 'build') 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, Index: . =================================================================== --- . (revision 1918171) +++ . (working copy) Property changes on: . ___________________________________________________________________ Modified: svn:ignore ## -65,3 +65,5 ## .swig_pl_checked .swig_py_checked .swig_rb_checked +out +CMakeLists.txt