On 12. 6. 25 22:36, Timofei Zhakov wrote:
On Thu, Jun 12, 2025 at 7:51 PM Branko Čibej <br...@apache.org> wrote:
On 12. 6. 25 19:10, Daniel Sahlberg wrote:
Den tors 12 juni 2025 kl 00:34 skrev Timofei Zhakov
<t...@chemodax.net>:
Revert most of the changes in svn_utf.h from r1926293.
Instead, tell the
CMake build to expose APR and APR-Util include paths
everywhere.
(...)
* CMakeLists.txt: Add APR and APR-Util include paths to
the top-level
directory properties.
(...)
--- subversion/trunk/CMakeLists.txt (original)
+++ subversion/trunk/CMakeLists.txt Wed Jun 11 15:40:43 2025
@@ -289,6 +289,12 @@ else()
endif()
endif()
+# APR and APR-Util include directories must be available
to all our souroces,
+# not just those that happen to link with one or the
other of these libraries.
+get_target_property(_apr_include external-apr
INTERFACE_INCLUDE_DIRECTORIES)
+get_target_property(_apu_include external-aprutil
INTERFACE_INCLUDE_DIRECTORIES)
+include_directories(${_apr_include} ${_apu_include})
+
This is wrong. You can't modify global include directories of
the entire project to force it to use apr everywhere. This is
a hack, not a fix.
As far as I understand CMake would add the include directories
automatically on targets that link APR (or APR-U... havn't
wrapped my head around what goes where yet), but the issue is
that it doesn't do that when not linking the libraries - is that
correct?
CMake has something called "imported targets", i.e., libraries
that the project uses but doesn't build. CMake targets have
various properties, one of them (the above
INTERFACE_INCLUDE_DIRECTORIES) says, basically, "code that uses
this library will find the needed headers here"). That's
simplified, but for C and C++, it's what the property boils down to.
These include paths are only injected into the compiler options
when a target is a dependency of the source that's being compiled.
In our case, what that means is that if one of subversion's
libraries or tools directly links apr-util, for example, the
apr-util include path will be added to CFLAGS (or rather,
C_INCLUDES in cmake's world) when the sources of that library are
compiled.
If a source doesn't link with apr-util but /does/ include one of
the apr-util headers, then we have a problem. It wasn't noticed
before because all the GH builders, and probably all the current
developers' setups, have APR and APR-Util installed in the same
place; and since all of our artefacts link with libapr, the
apr-util headers were always available -- even though the the
build script doesn't take that into account.
Homebrew on the Mac installs APR and APR-Util in different
directories, so this issue showed up when I tried to use the CMake
build on a mac (in order to test the patch to FindSerf.cmake with
serf-2).
The proximal reason being that some libraries that don't link with
apr_util do use the svn_utf.h header, which includes apr_xlate.h
from apr-util, which wasn't available, which caused a 💥. Those
libraries and tools only use apr-util indirectly, thorough
libsvn_subr.
Three possible solutions came to mind:
* Change build.conf, where the library dependencies are defined,
to add the dependency on apr-util. This would cause those
targets to link libaprutil, even when they're not using any
symbols from it. Thad didn't seem right.
* Change svn_utf.h to avoid that dependency ... which we know we
can't, due to backward-compatibility concerns.
* Fix the include paths in CMake.
That last part has several possible solutions. I implemented the
most obvious one, i.e., adding APR and APR-Util include paths to
all Subversion's build targets. It's an obvious solution because
APR and APR-Util are essentially part of Subversion's public
interface (because of our use of pools in the API, and APR's
integer types, and containers, etc.).
[An aside: if we decided to export our CMake targets to make them
directly usable by downstream, those apr-util include paths will
have to be included in our targets' INTERFACE_INCLUDE_DIRECTORIES.
This is essentially what we do in the pkg-config files, too, which
are the equivalent of cmake's exported targets.)
A better way would be to convince CMake to take the whole
transitive dependency tree into account, but, after having spent
way too much time chasing down CMake (3.12) documentation, I admit
that I don't know how to do that.
What is the CMake-ish solution here?
Since we generate the CMake target code from build.conf - can we
add a new parameter that add these include directories only on
the targets that actually require them?
Before someone jumps head-first in to the spaghetti that is our
build.conf generator, answer this question: why should we optimize
the include paths to the absolute minimum required, when that is
clearly a problem now and can't be anything but a problem down the
road? I'm talking specifically about APR and APR-Util include
paths, which merge into one in apr-2 in any case.
-- Brane
Thanks for your explanation!
I'd add a few points from myself.
When a library is linked into another target through
target_link_libraries(), it can be either PUBLIC, PRIVATE, or INTERFACE:
- PRIVATE means that the dependency will be entirely used in the
following target (both include directories and link flags are copied
to the new target). For example, when APR is linked into
libsvn_something as a PRIVATE dependency, this library gains access to
APR functions and headers. However, it doesn't spread to the target
depending on our libsvn_something (like our main svn.exe). Actually,
there is an exception that copies link flags in a static build, but
it's a different story. I mean it's not the main point.
As for another, more real world example, libsvn_fs_fs is a completely
internal library without any public APIs. I mean those used in our
programs. They usually only link against let's say libsvn_fs (in the
most abstract scenario). libsvn_fs does link libsvn_fs_fs, but doesn't
use it in its public api. This all means that if it's linked as a
PRIVATE dependency, no includes of libsvn_fs_fs will be available for
libsvn_fs. (let's imagine they would be in different directories for
clarity)
Also, answering Branko's question, optimising include directories
might be helpful to explicitly control whether the dependency is used
or not. For example, our libsvn_diff doesn't use apr util. So if
someone would call apr util api on accident, we will notice this
because the build would no longer be passing.
Yup. And libsvn_diff doesn't link apr-util, build.conf says so and all
our generated target dependencies derive from that. However:
trunk/subversion/libsvn_diff$ grep svn_utf.h *.c
deprecated.c:#include "svn_utf.h"
diff_file.c:#include "svn_utf.h"
diff_memory.c:#include "svn_utf.h"
parse-diff.c:#include "svn_utf.h"
util.c:#include "svn_utf.h"
So it needs -I/some/path/to/apr-util/include/apr-1 but it doesn't need
libaprutil.so (or .a or .lib or .dylib etc.). This is exactly why I
didn't want to change the dependency lists in build.conf.
- INTERFACE linkage only affects the targets dependent on the library.
It does literally nothing on the target itself, but spreads onto
dependent libraries.
- PUBLIC dependencies are a sort of combination of those both types.
For example, apr is the best example of a PUBLIC dependency, because
it is used in the library itself and required in the public header
files (for example libsvn_client exports svn_client.h, which includes
apr.h. Meaning that apr.h is a part of libsvn_client public api).
Right now libsvn_subr has the following dependencies:
target_link_libraries(libsvn_subr PRIVATE
external-aprutil
external-apr
external-xml
external-zlib
external-sqlite
external-intl
external-lz4
external-utf8proc
)
I think it should be like:
target_link_libraries(libsvn_subr PUBLIC
external-aprutil
external-apr
)
target_link_libraries(libsvn_subr PRIVATE
external-xml
external-zlib
external-sqlite
external-intl
external-lz4
external-utf8proc
)
Initially, I thought that all dependencies are explicitly listed in
the build.conf, so I didn't take the effort that time. Should I rework
the generator to produce such targets?
The link dependencies are in build.conf. The public/private distinction
isn't (since that doesn't affect linking a target). The autotools build,
based on build.conf, and also the generated Visual Studio files, make
all include paths available to all targets. I'd have to double-check,
but I think the exceptions to this rule are the (Swig) bindings, which
get some extra help.
This would be the cmake-ish way to fix this problem. And just an
improvement.
Well, I'd hate to add an explicit flag in build.conf just to distinguish
between public and private dependencies. Having said that, we only have
issues with external-aprutil (and, to some degree, external-apr, since
they should be treated in the same way). So how about adding this
knowledge directly into build/generator/gen_cmake.py? It is, after all,
its job is to generate correct dependencies for cmake. It already adds
the "external-" prefix to some of the library names; it may as well put
apr and apr-util into the PUBLIC space.
The attached patch appears to work for me.
-- Brane
Index: CMakeLists.txt
===================================================================
--- CMakeLists.txt (revision 1926360)
+++ CMakeLists.txt (working copy)
@@ -289,12 +289,6 @@
endif()
endif()
-# APR and APR-Util include directories must be available to all our souroces,
-# not just those that happen to link with one or the other of these libraries.
-get_target_property(_apr_include external-apr INTERFACE_INCLUDE_DIRECTORIES)
-get_target_property(_apu_include external-aprutil
INTERFACE_INCLUDE_DIRECTORIES)
-include_directories(${_apr_include} ${_apu_include})
-
### ZLIB
if(SVN_USE_PKG_CONFIG)
Index: build/generator/gen_cmake.py
===================================================================
--- build/generator/gen_cmake.py (revision 1926359)
+++ build/generator/gen_cmake.py (working copy)
@@ -53,6 +53,10 @@
return name[7:].upper()
+# APR and APR-Util are part of our public interface and should be
+# declared PUBLIC in library target dependencies.
+PUBLIC_LIB_DEPENDS = frozenset(["external-apr", "external-aprutil"])
+
def get_output_name(target):
if target.name.startswith("lib"):
return target.name[3:] + "-1"
@@ -146,7 +150,8 @@
msvc_export.append("subversion/include/" + export)
sources = []
- libs = []
+ private_libs = []
+ public_libs = []
for dep in self.get_dependencies(target.name):
enable_condition += get_target_conditions(dep)
@@ -157,9 +162,9 @@
elif isinstance(dep, gen_base.TargetLinked):
if dep.external_lib:
if dep.name == "ra-libs":
- libs.append("ra-libs")
+ private_libs.append("ra-libs")
elif dep.name == "fs-libs":
- libs.append("fs-libs")
+ private_libs.append("fs-libs")
elif dep.name in ["apriconv",
"apr_memcache",
"magic",
@@ -170,9 +175,14 @@
# TODO:
pass
else:
- libs.append("external-" + dep.name)
+ dep_name = "external-" + dep.name
+ if (dep_name in PUBLIC_LIB_DEPENDS
+ and not isinstance(target, gen_base.TargetExe)):
+ public_libs.append(dep_name)
+ else:
+ private_libs.append(dep_name)
else:
- libs.append(dep.name)
+ private_libs.append(dep.name)
elif isinstance(dep, gen_base.ObjectFile):
for source in self.graph.get_sources(gen_base.DT_OBJECT, dep,
gen_base.SourceFile):
@@ -212,7 +222,9 @@
output_name = get_output_name(target),
type = target_type,
sources = sources,
- libs = libs,
+ libs = public_libs + private_libs,
+ public_libs = public_libs,
+ private_libs = private_libs,
msvc_libs = msvc_libs,
msvc_objects = msvc_objects,
msvc_export = msvc_export,
Index: build/generator/templates/targets.cmake.ezt
===================================================================
--- build/generator/templates/targets.cmake.ezt (revision 1926359)
+++ build/generator/templates/targets.cmake.ezt (working copy)
@@ -69,9 +69,12 @@
WORKING_DIRECTORY $<TARGET_FILE_DIR:[targets.name]>
)
set_tests_properties([targets.namespace].[targets.name] PROPERTIES
ENVIRONMENT LD_LIBRARY_PATH=$<TARGET_FILE_DIR:[targets.name]>)
- [end]target_link_libraries([targets.name] PRIVATE[for targets.libs]
- [targets.libs][end]
- )[if-any targets.msvc_libs]
+ [end][if-any targets.public_libs]target_link_libraries([targets.name]
PUBLIC[for targets.public_libs]
+ [targets.public_libs][end]
+ )
+ [end][if-any targets.private_libs]target_link_libraries([targets.name]
PRIVATE[for targets.private_libs]
+ [targets.private_libs][end]
+ )[end][if-any targets.msvc_libs]
if (WIN32)
target_link_libraries([targets.name] PRIVATE[for targets.msvc_libs]
[targets.msvc_libs][end])
endif()[end][if-any targets.msvc_objects]