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