Skip to content

Commit 972c01f

Browse files
authored
Merge pull request mickem#1312 from mickem/feature/allow_prefix_install_on_linux
feature: add support for custom install prefix on Linux builds
2 parents f67b6c7 + 8cccbdd commit 972c01f

26 files changed

Lines changed: 520 additions & 202 deletions

.github/workflows/build-debian.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ jobs:
111111
-DBUILD_VERSION=${{ inputs.version }} \
112112
-DCPACK_GENERATOR=DEB \
113113
-DCMAKE_BUILD_TYPE=Release \
114+
-DCMAKE_INSTALL_PREFIX=/usr \
114115
-DNSCP_WEB_BACKEND=beast \
115116
-DNSCP_BOOST_PYTHON_VERSION=${{ matrix.python_version }} \
116117
-DLIBRARY_ROOT_FOLDER="$GITHUB_WORKSPACE" \

.github/workflows/build-redhat.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ jobs:
125125
-DBUILD_VERSION=${{ inputs.version }} \
126126
-DCPACK_GENERATOR=RPM \
127127
-DCMAKE_BUILD_TYPE=Release \
128+
-DCMAKE_INSTALL_PREFIX=/usr \
128129
-DNSCP_WEB_BACKEND=beast \
129130
-DNSCP_BOOST_PYTHON_VERSION=${{ matrix.python_version }} \
130131
-DLIBRARY_ROOT_FOLDER="$GITHUB_WORKSPACE" \

CMakeLists.txt

Lines changed: 134 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ if(WIN32)
9393
)
9494
set(VSTUDIO_EXPRESS ON)
9595
endif(FOUND_ATL)
96-
else()
97-
set(CMAKE_INSTALL_PREFIX "usr")
9896
endif()
97+
# On Linux/macOS CMAKE_INSTALL_PREFIX is honoured (GNU default /usr/local).
98+
# Distribution package builds pass -DCMAKE_INSTALL_PREFIX=/usr explicitly;
9999

100100
set(BUILD_PYTHON_FOLDER "${CMAKE_SOURCE_DIR}/build/python")
101101
set(BUILD_CMAKE_FOLDER "${CMAKE_SOURCE_DIR}/build/cmake")
@@ -123,6 +123,79 @@ set(BIN_TARGET_FOLDER usr/bin)
123123
set(LOG_TARGET_FOLDER var/log)
124124
set(ETC_TARGET_FOLDER etc)
125125

126+
# ##############################################################################
127+
#
128+
# Linux install layout (GNUInstallDirs)
129+
#
130+
# These NSCP_* variables are the single source of truth for where the daemon,
131+
# its private libraries, modules, data, config, state and logs are installed
132+
# AND for the runtime defaults baked into config.h, so the same value drives
133+
# both the install(...) rules and the path_manager runtime defaults.
134+
#
135+
# Everything derives from CMAKE_INSTALL_PREFIX via GNUInstallDirs, so the same
136+
# source tree honours --prefix=/usr, /usr/local, /opt/nsclient, ... without a
137+
# recompile. GNUInstallDirs handles the FHS quirk where --prefix=/usr redirects
138+
# SYSCONFDIR to /etc and LOCALSTATEDIR to /var.
139+
#
140+
# Windows keeps the existing exe-relative model and never sees these.
141+
#
142+
# ##############################################################################
143+
if(NOT WIN32)
144+
include(GNUInstallDirs)
145+
146+
set(NSCP_SBINDIR "${CMAKE_INSTALL_FULL_SBINDIR}")
147+
set(NSCP_BINDIR "${CMAKE_INSTALL_FULL_BINDIR}")
148+
149+
# NSCP_LIBDIR is the base for NSCP_PKGLIBDIR only - nothing installs into it
150+
# directly. It deliberately uses a plain "<prefix>/lib" rather than
151+
# CMAKE_INSTALL_FULL_LIBDIR: on Debian/Ubuntu the latter expands to the
152+
# multiarch path (/usr/lib/<triplet>), and putting our private libraries +
153+
# modules there is exactly what makes dpkg/lintian treat them as public
154+
# (SONAME deps, shlibs/symbols, -dev split, ldconfig triggers). The project
155+
# ships no public ABI, so the libraries live in a package-private
156+
# "<prefix>/lib/nsclient" off the default loader path. See the RPATH set in
157+
# NSCP_MAKE_LIBRARY / NSCP_INSTALL_MODULE.
158+
set(NSCP_LIBDIR "${CMAKE_INSTALL_PREFIX}/lib")
159+
set(NSCP_PKGLIBDIR "${NSCP_LIBDIR}/nsclient")
160+
161+
set(NSCP_PKGDATADIR "${CMAKE_INSTALL_FULL_DATAROOTDIR}/nsclient")
162+
set(NSCP_SYSCONFDIR "${CMAKE_INSTALL_FULL_SYSCONFDIR}")
163+
set(NSCP_PKGSYSCONFDIR "${NSCP_SYSCONFDIR}/nsclient")
164+
set(NSCP_PKGSTATEDIR "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/nsclient")
165+
set(NSCP_LOGDIR "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/nsclient")
166+
167+
# systemd system unit directory. Defaults to the historical
168+
# /lib/systemd/system; a packager who wants the pkg-config-reported
169+
# location passes
170+
# -DNSCP_SYSTEMD_UNITDIR=$(pkg-config systemd --variable=systemdsystemunitdir)
171+
if(NOT NSCP_SYSTEMD_UNITDIR)
172+
set(NSCP_SYSTEMD_UNITDIR "/lib/systemd/system")
173+
endif()
174+
175+
# Map the historical *_TARGET_FOLDER install-destination variables (defined
176+
# above as Windows/fallback defaults) onto the prefix-derived NSCP_* values.
177+
# These are absolute now, so they ignore CMAKE_INSTALL_PREFIX at install
178+
# time - which is correct, the prefix is already baked into NSCP_*.
179+
set(MODULE_TARGET_FOLDER "${NSCP_PKGLIBDIR}/modules")
180+
set(LIB_TARGET_FOLDER "${NSCP_PKGLIBDIR}") # Phase 2b: private libs next to modules
181+
set(SYSTEMD_TARGET_FOLDER "${NSCP_SYSTEMD_UNITDIR}")
182+
set(SBIN_TARGET_FOLDER "${NSCP_SBINDIR}")
183+
set(BIN_TARGET_FOLDER "${NSCP_BINDIR}")
184+
set(LOG_TARGET_FOLDER "${NSCP_LOGDIR}")
185+
set(ETC_TARGET_FOLDER "${NSCP_SYSCONFDIR}")
186+
187+
# $ORIGIN-relative RPATHs so the private libraries resolve regardless of
188+
# prefix. Executables derive theirs per-target in NSCP_MAKE_EXE (sbin and
189+
# bin are both one level under the prefix). Modules live in
190+
# NSCP_PKGLIBDIR/modules, so the libraries sit one dir up.
191+
set(NSCP_RPATH_MODULE "$ORIGIN/..")
192+
# The private libraries are co-located in NSCP_PKGLIBDIR; their inter-deps
193+
# resolve via $ORIGIN. This is required because modern toolchains emit
194+
# DT_RUNPATH (not the deprecated, transitive DT_RPATH), so each object must
195+
# carry an RPATH covering its own direct dependencies.
196+
set(NSCP_RPATH_LIB "$ORIGIN")
197+
endif()
198+
126199
set(NSCP_GLOBAL_DEFINES
127200
${NSCP_GLOBAL_DEFINES}
128201
-DBOOST_PHOENIX_STL_TUPLE_H_
@@ -806,15 +879,24 @@ if(WIN32)
806879
"PAth for log file"
807880
)
808881
else()
809-
set(INSTALL_FILES_BASE "usr/lib/nsclient/")
810-
811-
set(CONFIG_DEFAULT_CONFIG_FOLDER
812-
"/etc/nsclient"
813-
CACHE STRING
814-
"Base location for ini files"
815-
)
816-
817-
set(CONFIG_BOOT_CONF_LOCATION "${CONFIG_DEFAULT_CONFIG_FOLDER}/boot.ini")
882+
# Files installed via copy_single_file() land under the package-private
883+
# shared dir (NSCP_PKGLIBDIR, e.g. /usr/lib/nsclient). This is also what
884+
# ${shared-path} resolves to at runtime, so scripts/web/security stay where
885+
# the daemon looks for them.
886+
set(INSTALL_FILES_BASE "${NSCP_PKGLIBDIR}/")
887+
888+
# Absolute, prefix-dependent values. These are intentionally NOT CACHE
889+
# variables: GNUInstallDirs already gives packagers the override knobs
890+
# (CMAKE_INSTALL_SYSCONFDIR, ...), and caching would pin a value from a
891+
# previous configure so it could no longer track CMAKE_INSTALL_PREFIX.
892+
set(CONFIG_DEFAULT_CONFIG_FOLDER "${NSCP_PKGSYSCONFDIR}")
893+
894+
# boot.ini's location is now a path_manager token (${boot-conf}) so an
895+
# operator can relocate it with --path-override boot-conf=... . The token's
896+
# default (NSCP_PKGSYSCONFDIR/boot.ini) is registered in path_manager.
897+
# Escaped so configure_file(@ONLY) emits the literal token, not an empty
898+
# CMake expansion.
899+
set(CONFIG_BOOT_CONF_LOCATION "\${boot-conf}")
818900
set(CONFIG_DEFAULT_CONF_LOCATION
819901
"ini"
820902
CACHE STRING
@@ -826,16 +908,9 @@ else()
826908
CACHE STRING
827909
"modules folder"
828910
)
829-
set(UNIX_SHARED_PATH_FOLDER
830-
"/usr/lib/nsclient"
831-
CACHE STRING
832-
"Shared files on linux"
833-
)
834-
set(UNIX_DATA_PATH_FOLDER
835-
"/var/lib/nsclient"
836-
CACHE STRING
837-
"Data path on linux"
838-
)
911+
set(UNIX_SHARED_PATH_FOLDER "${NSCP_PKGLIBDIR}")
912+
set(UNIX_DATA_PATH_FOLDER "${NSCP_PKGSTATEDIR}")
913+
set(CONFIG_ETC_FOLDER "${NSCP_SYSCONFDIR}")
839914
set(CONFIG_WEB_FOLDER "\${shared-path}/web" CACHE STRING "web folder")
840915
set(CONFIG_SCRIPTS_FOLDER
841916
"\${shared-path}/scripts"
@@ -1204,25 +1279,57 @@ else()
12041279
option(USE_SYSTEMD "Install systemd service files" ON)
12051280
option(USE_INITD "Install legacy init.d scripts" OFF)
12061281

1282+
# Install destinations are absolute (prefix-derived NSCP_* values), so the
1283+
# CPack staging prefix must be the filesystem root - it is only consulted
1284+
# for relative destinations, of which we now have none.
12071285
set(CPACK_PACKAGING_INSTALL_PREFIX "/")
12081286

1287+
# The maintainer scripts and conffiles reference install paths, so they are
1288+
# templated through configure_file with the NSCP_* values rather than baked
1289+
# to /usr. (prerm touches no paths, so it ships verbatim.)
1290+
configure_file(
1291+
${CMAKE_CURRENT_SOURCE_DIR}/files/deb/postinst.in
1292+
${PROJECT_BINARY_DIR}/files/deb/postinst
1293+
@ONLY
1294+
)
1295+
configure_file(
1296+
${CMAKE_CURRENT_SOURCE_DIR}/files/deb/postrm.in
1297+
${PROJECT_BINARY_DIR}/files/deb/postrm
1298+
@ONLY
1299+
)
1300+
configure_file(
1301+
${CMAKE_CURRENT_SOURCE_DIR}/files/deb/conffiles.in
1302+
${PROJECT_BINARY_DIR}/files/deb/conffiles
1303+
@ONLY
1304+
)
1305+
configure_file(
1306+
${CMAKE_CURRENT_SOURCE_DIR}/files/rpm/postinstall.sh.in
1307+
${PROJECT_BINARY_DIR}/files/rpm/postinstall.sh
1308+
@ONLY
1309+
)
1310+
configure_file(
1311+
${CMAKE_CURRENT_SOURCE_DIR}/files/rpm/preuninstall.sh.in
1312+
${PROJECT_BINARY_DIR}/files/rpm/preuninstall.sh
1313+
@ONLY
1314+
)
1315+
12091316
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA
1210-
"${CMAKE_CURRENT_SOURCE_DIR}/files/deb/postinst;${CMAKE_CURRENT_SOURCE_DIR}/files/deb/prerm;${CMAKE_CURRENT_SOURCE_DIR}/files/deb/postrm;${CMAKE_CURRENT_SOURCE_DIR}/files/deb/conffiles"
1317+
"${PROJECT_BINARY_DIR}/files/deb/postinst;${CMAKE_CURRENT_SOURCE_DIR}/files/deb/prerm;${PROJECT_BINARY_DIR}/files/deb/postrm;${PROJECT_BINARY_DIR}/files/deb/conffiles"
12111318
)
12121319
set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE
1213-
"${CMAKE_CURRENT_SOURCE_DIR}/files/rpm/postinstall.sh"
1320+
"${PROJECT_BINARY_DIR}/files/rpm/postinstall.sh"
12141321
)
12151322
set(CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE
1216-
"${CMAKE_CURRENT_SOURCE_DIR}/files/rpm/preuninstall.sh"
1323+
"${PROJECT_BINARY_DIR}/files/rpm/preuninstall.sh"
12171324
)
12181325

12191326
set(CPACK_RPM_PACKAGE_CONFIG_FILES
1220-
"/etc/nsclient/boot.ini"
1221-
"/etc/nsclient/nsclient.ini"
1327+
"${NSCP_PKGSYSCONFDIR}/boot.ini"
1328+
"${NSCP_PKGSYSCONFDIR}/nsclient.ini"
12221329
)
12231330

12241331
# Ensure empty directories are included in the RPM
1225-
set(CPACK_RPM_USER_FILELIST "%dir /var/log/nsclient")
1332+
set(CPACK_RPM_USER_FILELIST "%dir ${NSCP_LOGDIR}")
12261333

12271334
set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION
12281335
"/bin"

build.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,51 @@ cmake $SOURCE_ROOT \
622622
make -j$(nproc)
623623
```
624624

625+
### Choosing an install prefix (Linux)
626+
627+
The Linux build honours `CMAKE_INSTALL_PREFIX` like any other CMake project, so
628+
the same source tree can target `/usr`, `/usr/local`, `/opt/nsclient`, or a
629+
throwaway staging tree without recompiling per destination. Everything — the
630+
daemon, its private libraries and modules, config, state, logs and the systemd
631+
unit — is derived from the prefix through **GNUInstallDirs**, and the *same*
632+
values are baked into the runtime path defaults, so `install(...)` and the
633+
running daemon always agree.
634+
635+
```bash
636+
# Distribution package layout (what the .deb/.rpm CI builds pass):
637+
cmake $SOURCE_ROOT -DCMAKE_INSTALL_PREFIX=/usr ...
638+
# binary -> /usr/sbin/nscp
639+
# modules -> /usr/lib/nsclient/modules
640+
# libs -> /usr/lib/nsclient (package-private, $ORIGIN RPATH)
641+
# data -> /usr/lib/nsclient/{web,scripts,security}
642+
# config -> /etc/nsclient (FHS: prefix=/usr redirects to /etc)
643+
# state -> /var/lib/nsclient
644+
# logs -> /var/log/nsclient
645+
646+
# A self-contained tree under /opt:
647+
cmake $SOURCE_ROOT -DCMAKE_INSTALL_PREFIX=/opt/nsclient ...
648+
```
649+
650+
The default prefix when you pass nothing is CMake's standard `/usr/local` (this
651+
changed — older trees forced a bare `usr/` subdirectory in the build dir).
652+
653+
Standard GNUInstallDirs knobs work and need no NSCP-specific names:
654+
`-DCMAKE_INSTALL_SBINDIR=...`, `-DCMAKE_INSTALL_SYSCONFDIR=...`,
655+
`-DCMAKE_INSTALL_LOCALSTATEDIR=...`, etc. The systemd unit directory is
656+
`-DNSCP_SYSTEMD_UNITDIR=...` (defaults to `/lib/systemd/system`; pass
657+
`$(pkg-config systemd --variable=systemdsystemunitdir)` to follow the distro).
658+
659+
`DESTDIR=/tmp/stage cmake --install build/` stages a relocatable tree at any
660+
prefix, exactly as the package builds rely on.
661+
662+
> **Upgrade note for packagers patching old hardcoded paths.** The runtime
663+
> defaults used to be compile-time constants (`/usr/lib/nsclient`,
664+
> `/etc/nsclient`, …). They are now prefix-derived, so a patch that rewrote
665+
> those constants is no longer needed — pass `-DCMAKE_INSTALL_PREFIX` (and any
666+
> `CMAKE_INSTALL_*DIR` overrides) instead. To relocate just `boot.ini` at
667+
> runtime there is now a path token: `--path-override boot-conf=/path/boot.ini`
668+
> (highest precedence, applied before `boot.ini` is read).
669+
625670
### Trimming the build
626671
627672
The [Dependencies](#dependencies) and [Build options](#build-options) sections

build/cmake/functions.cmake

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,15 @@ macro(NSCP_INSTALL_MODULE _TARGET)
308308
install(TARGETS ${_TARGET} LIBRARY DESTINATION ${_FOLDER})
309309
else()
310310
set(_FOLDER ${MODULE_TARGET_FOLDER})
311+
# Modules sit in NSCP_PKGLIBDIR/modules and load the project's private
312+
# shared libraries from NSCP_PKGLIBDIR one directory up. Carry an
313+
# $ORIGIN-relative RPATH so they resolve regardless of install prefix.
314+
set_target_properties(
315+
${_TARGET}
316+
PROPERTIES
317+
INSTALL_RPATH
318+
"${NSCP_RPATH_MODULE}"
319+
)
311320
install(TARGETS ${_TARGET} LIBRARY DESTINATION ${_FOLDER})
312321
endif()
313322
if(MSVC11 OR MSVC12 OR MSVC13 OR MSVC14 OR APPLE)
@@ -339,8 +348,19 @@ macro(NSCP_INSTALL_MODULE _TARGET)
339348
endmacro()
340349

341350
macro(NSCP_MAKE_LIBRARY _TARGET _SRCS)
351+
# Optional trailing EXCLUDE_FROM_ALL keyword: keep the target out of the
352+
# default build so it is only built when another target depends on it (e.g.
353+
# nscp_mongoose, which is only needed by WEBServer). When excluded, the
354+
# install rule is made OPTIONAL so `make install` doesn't fail if the
355+
# library was never built.
356+
set(_NSCP_LIB_EXCLUDE "")
357+
set(_NSCP_INSTALL_OPTIONAL "")
358+
if("${ARGN}" STREQUAL "EXCLUDE_FROM_ALL")
359+
set(_NSCP_LIB_EXCLUDE EXCLUDE_FROM_ALL)
360+
set(_NSCP_INSTALL_OPTIONAL OPTIONAL)
361+
endif()
342362
if(USE_STATIC_RUNTIME)
343-
add_library(${_TARGET} STATIC ${_SRCS})
363+
add_library(${_TARGET} STATIC ${_NSCP_LIB_EXCLUDE} ${_SRCS})
344364
nscp_apply_pic(${_TARGET})
345365
set_target_properties(
346366
${_TARGET}
@@ -349,16 +369,22 @@ macro(NSCP_MAKE_LIBRARY _TARGET _SRCS)
349369
"${NSCP_LIB_VERSION}"
350370
)
351371
else(USE_STATIC_RUNTIME)
352-
add_library(${_TARGET} SHARED ${_SRCS})
372+
add_library(${_TARGET} SHARED ${_NSCP_LIB_EXCLUDE} ${_SRCS})
353373
SET_LIBRARY_OUT_FOLDER(${_TARGET})
354-
set_target_properties(
355-
${_TARGET}
356-
PROPERTIES
357-
VERSION
358-
"${NSCP_LIB_VERSION}"
359-
SOVERSION
360-
"${NSCP_LIB_VERSION}"
361-
)
374+
# These are package-PRIVATE libraries they install under NSCP_PKGLIBDIR alongside the modules, not the public
375+
# libdir, and ship no public ABI. So no SOVERSION/VERSION symlink chain (dead weight + a lintian remark for a
376+
# private lib). On Windows the VERSION property is harmless but equally unnecessary here.
377+
#
378+
# $ORIGIN RPATH: the libraries depend on each other and are co-located,
379+
# and DT_RUNPATH is non-transitive, so each must find its siblings.
380+
if(NOT WIN32)
381+
set_target_properties(
382+
${_TARGET}
383+
PROPERTIES
384+
INSTALL_RPATH
385+
"${NSCP_RPATH_LIB}"
386+
)
387+
endif()
362388
endif(USE_STATIC_RUNTIME)
363389
set_target_properties(
364390
${_TARGET}
@@ -372,13 +398,20 @@ macro(NSCP_MAKE_LIBRARY _TARGET _SRCS)
372398
install(
373399
TARGETS
374400
${_TARGET}
401+
${_NSCP_INSTALL_OPTIONAL}
375402
RUNTIME
376403
DESTINATION .
377404
LIBRARY
378405
DESTINATION .
379406
)
380407
else()
381-
install(TARGETS ${_TARGET} LIBRARY DESTINATION ${LIB_TARGET_FOLDER})
408+
install(
409+
TARGETS
410+
${_TARGET}
411+
${_NSCP_INSTALL_OPTIONAL}
412+
LIBRARY
413+
DESTINATION ${LIB_TARGET_FOLDER}
414+
)
382415
endif()
383416
if(MSVC11 OR MSVC12 OR MSVC13 OR MSVC14 OR APPLE)
384417
set_target_properties(
@@ -415,6 +448,23 @@ macro(NSCP_MAKE_EXE _TARGET _SRCS _FOLDER)
415448
if(WIN32)
416449
install(TARGETS ${_TARGET} RUNTIME DESTINATION .)
417450
else()
451+
# Executables (the nscp daemon in sbin, client tools in bin) load the
452+
# project's private shared libraries from NSCP_PKGLIBDIR. Carry an
453+
# $ORIGIN-relative RPATH derived from this target's install folder so it
454+
# resolves regardless of prefix (sbin and bin are both one level under
455+
# the prefix, so this is typically $ORIGIN/../lib/nsclient).
456+
file(
457+
RELATIVE_PATH
458+
_NSCP_EXE_TO_PKGLIB
459+
"${_FOLDER}"
460+
"${NSCP_PKGLIBDIR}"
461+
)
462+
set_target_properties(
463+
${_TARGET}
464+
PROPERTIES
465+
INSTALL_RPATH
466+
"$ORIGIN/${_NSCP_EXE_TO_PKGLIB}"
467+
)
418468
install(TARGETS ${_TARGET} RUNTIME DESTINATION ${_FOLDER})
419469
endif()
420470
if(MSVC11 OR MSVC12 OR MSVC13 OR MSVC14 OR APPLE)

0 commit comments

Comments
 (0)