Skip to content

Commit 070b35a

Browse files
Refactor uninstall-script generation (#1380)
* New CMake-based uninstall generation * Fix: Don't use genex in places where old CMake can't use it Co-authored-by: Roberto C. Sánchez <[email protected]>
1 parent 2c8ed74 commit 070b35a

File tree

6 files changed

+293
-328
lines changed

6 files changed

+293
-328
lines changed

CMakeLists.txt

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -550,26 +550,16 @@ install (FILES COPYING NEWS README.rst THIRD_PARTY_NOTICES
550550
)
551551

552552
if (ENABLE_UNINSTALL)
553-
if (WIN32)
554-
if (ENABLE_MONGOC)
555-
set (UNINSTALL_PROG "uninstall.cmd")
556-
else ()
557-
set (UNINSTALL_PROG "uninstall-bson.cmd")
558-
endif ()
559-
else ()
560-
if (ENABLE_MONGOC)
561-
set (UNINSTALL_PROG "uninstall.sh")
562-
else ()
563-
set (UNINSTALL_PROG "uninstall-bson.sh")
564-
endif ()
565-
endif ()
566-
set (UNINSTALL_PROG_DIR "${CMAKE_INSTALL_DATADIR}/mongo-c-driver")
567-
568553
# Create uninstall program and associated uninstall target
569554
#
570555
# This needs to be last (after all other add_subdirectory calls) to ensure that
571556
# the generated uninstall program is complete and correct
572-
add_subdirectory (generate_uninstall)
557+
if (NOT ENABLE_MONGOC)
558+
# Generate a different script name for uninstalling libbson only:
559+
set (UNINSTALL_SCRIPT_NAME "uninstall-bson")
560+
endif ()
561+
set (UNINSTALL_PROG_DIR "${CMAKE_INSTALL_DATADIR}/mongo-c-driver")
562+
include (GenerateUninstaller)
573563
endif ()
574564

575565
# Spit out some information regarding the generated build system

build/cmake/GeneratePkgConfig.cmake

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ All named parameters accept generator expressions.
111111
112112
]==]
113113
function(mongo_generate_pkg_config target)
114+
list(APPEND CMAKE_MESSAGE_CONTEXT "mongo_generate_pkg_config" "${target}")
114115
# Collect some target properties:
115116
# The name:
116117
_genex_escape(proj_name "${PROJECT_NAME}")
@@ -148,17 +149,20 @@ function(mongo_generate_pkg_config target)
148149
set(ARG_FILENAME "$<TARGET_FILE_BASE_NAME:${target}>.pc")
149150
endif()
150151
endif()
152+
message(DEBUG "FILENAME: ${ARG_FILENAME}")
151153

152154
# The defalut CONDITION is just "1" (true)
153155
if(NOT DEFINED ARG_CONDITION)
154156
set(ARG_CONDITION 1)
155157
endif()
158+
message(DEBUG "CONDITION: ${ARG_CONDITION}")
156159
_bind_genex_to_target(gx_cond ${target} "${ARG_CONDITION}")
157160

158161
# The default LIBDIR comes from GNUInstallDirs.cmake
159162
if(NOT ARG_LIBDIR)
160163
set(ARG_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
161164
endif()
165+
message(DEBUG "LIBDIR: ${ARG_LIBDIR}")
162166
_bind_genex_to_target(gx_libdir ${target} "${ARG_LIBDIR}")
163167

164168
# Evaluate the filename genex in the context of the target:
@@ -168,6 +172,7 @@ function(mongo_generate_pkg_config target)
168172
else()
169173
get_filename_component(gx_output "${CMAKE_CURRENT_BINARY_DIR}/${gx_filename}" ABSOLUTE)
170174
endif()
175+
message(DEBUG "Generating build-tree file: ${gx_output}")
171176

172177
# Generate the content of the file:
173178
_generate_pkg_config_content(content
@@ -187,13 +192,15 @@ function(mongo_generate_pkg_config target)
187192
CONDITION "${gx_cond}")
188193
if(NOT "INSTALL" IN_LIST ARGN)
189194
# Nothing more to do here.
195+
message(DEBUG "(Not installing)")
190196
return()
191197
endif()
192198

193199
# Installation handling:
194200
# Use file(GENERATE) to generate a temporary file to be picked up at install-time.
195201
# (For some reason, injecting the content directly into install(CODE) fails in corner cases)
196202
set(gx_tmpfile "${CMAKE_CURRENT_BINARY_DIR}/_pkgconfig/${target}-$<LOWER_CASE:$<CONFIG>>-for-install.txt")
203+
message(DEBUG "Generate for-install: ${gx_tmpfile}")
197204
file(GENERATE OUTPUT "${gx_tmpfile}"
198205
CONTENT "${gx_content}"
199206
CONDITION "${gx_cond}")
@@ -206,6 +213,8 @@ function(mongo_generate_pkg_config target)
206213
if(NOT DEFINED inst_RENAME)
207214
set(inst_RENAME "${ARG_FILENAME}")
208215
endif()
216+
message(DEBUG "INSTALL DESTINATION: ${inst_DESTINATION}")
217+
message(DEBUG "INSTALL RENAME: ${inst_RENAME}")
209218
# install(CODE) will write a simple temporary file:
210219
set(inst_tmp "${CMAKE_CURRENT_BINARY_DIR}/${target}-pkg-config-tmp.txt")
211220
_genex_escape(esc_cond "${ARG_CONDITION}")
@@ -226,7 +235,13 @@ function(mongo_generate_pkg_config target)
226235
]==] code @ONLY)
227236
install(CODE "${code}")
228237
_bind_genex_to_target(gx_dest ${target} "${inst_DESTINATION}")
229-
_bind_genex_to_target(gx_rename ${target} "${inst_RENAME}")
238+
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.20")
239+
_bind_genex_to_target(gx_rename ${target} "${inst_RENAME}")
240+
else()
241+
# Note: CMake 3.20 is required for using generator expresssions in install(RENAME).
242+
# if we are older than that, just treat RENAME as a plain value.
243+
set(gx_rename "${inst_RENAME}")
244+
endif()
230245
# Wrap the filename to install with the same condition used to generate it. If the condition
231246
# is not met, then the FILES list will be empty, and nothing will be installed.
232247
install(FILES "$<${gx_cond}:${inst_tmp}>"

build/cmake/GenerateUninstaller.cmake

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
cmake_policy(VERSION 3.15)
2+
3+
if(NOT CMAKE_SCRIPT_MODE_FILE)
4+
# We are being included from within a project, so we should generate the install rules
5+
# The script name is "uninstall" by default:
6+
if(NOT DEFINED UNINSTALL_SCRIPT_NAME)
7+
set(UNINSTALL_SCRIPT_NAME "uninstall")
8+
endif()
9+
# We need a directory where we should install the script:
10+
if(NOT UNINSTALL_PROG_DIR)
11+
message(SEND_ERROR "We require an UNINSTALL_PROG_DIR to be defined")
12+
endif()
13+
# Platform dependent values:
14+
if(WIN32)
15+
set(_script_ext "cmd")
16+
set(_script_runner cmd.exe /c)
17+
else()
18+
set(_script_ext "sh")
19+
set(_script_runner sh -e -u)
20+
endif()
21+
# The script filename and path:
22+
set(_script_filename "${UNINSTALL_SCRIPT_NAME}.${_script_ext}")
23+
get_filename_component(_uninstaller_script "${CMAKE_CURRENT_BINARY_DIR}/${_script_filename}" ABSOLUTE)
24+
# Code that will do the work at install-time:
25+
string(CONFIGURE [==[
26+
function(__generate_uninstall)
27+
set(UNINSTALL_IS_WIN32 "@WIN32@")
28+
set(UNINSTALL_WRITE_FILE "@_uninstaller_script@")
29+
set(UNINSTALL_SCRIPT_SELF "@UNINSTALL_PROG_DIR@/@_script_filename@")
30+
include("@CMAKE_CURRENT_LIST_FILE@")
31+
endfunction()
32+
__generate_uninstall()
33+
]==] code @ONLY ESCAPE_QUOTES)
34+
install(CODE "${code}")
35+
# Add a rule to install that file:
36+
install(
37+
FILES "${_uninstaller_script}"
38+
DESTINATION "${UNINSTALL_PROG_DIR}"
39+
PERMISSIONS
40+
OWNER_READ OWNER_WRITE OWNER_EXECUTE
41+
GROUP_READ GROUP_EXECUTE
42+
WORLD_READ WORLD_EXECUTE
43+
)
44+
45+
# If applicable, generate an "uninstall" target to run the uninstaller:
46+
if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR OR PROJECT_IS_TOP_LEVEL)
47+
add_custom_target(
48+
uninstall
49+
COMMAND ${_script_runner} "${_uninstaller_script}"
50+
COMMENT Uninstalling...
51+
)
52+
endif()
53+
# Stop here: The rest of the file is for install-time
54+
return()
55+
endif()
56+
57+
# We get here if running in script mode (e.g. at CMake install-time)
58+
if(NOT DEFINED CMAKE_INSTALL_MANIFEST_FILES)
59+
message(FATAL_ERROR "This file is only for use with CMake's install(CODE/SCRIPT) command")
60+
endif()
61+
if(NOT DEFINED UNINSTALL_WRITE_FILE)
62+
message(FATAL_ERROR "Expected a variable “UNINSTALL_WRITE_FILE” to be defined")
63+
endif()
64+
65+
# Clear out the uninstall script before we begin writing:
66+
file(WRITE "${UNINSTALL_WRITE_FILE}" "")
67+
68+
# Append a line to the uninstall script file. Single quotes will be replaced with doubles,
69+
# and an appropriate newline will be added.
70+
function(append_line line)
71+
string(REPLACE "'" "\"" line "${line}")
72+
file(APPEND "${UNINSTALL_WRITE_FILE}" "${line}\n")
73+
endfunction()
74+
75+
# The copyright header:
76+
set(header [[
77+
Mongo C Driver uninstall program, generated with CMake
78+
79+
Copyright 2018-present MongoDB, Inc.
80+
81+
Licensed under the Apache License, Version 2.0 (the \"License\");
82+
you may not use this file except in compliance with the License.
83+
You may obtain a copy of the License at
84+
85+
http://www.apache.org/licenses/LICENSE-2.0
86+
87+
Unless required by applicable law or agreed to in writing, software
88+
distributed under the License is distributed on an \"AS IS\" BASIS,
89+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
90+
See the License for the specific language governing permissions and
91+
limitations under the License.
92+
]])
93+
string(STRIP header "${header}")
94+
string(REPLACE "\n" ";" header_lines "${header}")
95+
96+
# Prefix for the Batch script:
97+
set(bat_preamble [[
98+
call :init
99+
100+
:print
101+
<nul set /p_=%~1
102+
exit /b
103+
104+
:rmfile
105+
set f=%__prefix%\%~1
106+
call :print "Remove file %f% "
107+
if EXIST "%f%" (
108+
del /Q /F "%f%" || exit /b %errorlevel%
109+
call :print " - ok"
110+
) else (
111+
call :print " - skipped: not present"
112+
)
113+
echo(
114+
exit /b
115+
116+
:rmdir
117+
set f=%__prefix%\%~1
118+
call :print "Remove directory: %f% "
119+
if EXIST "%f%" (
120+
rmdir /Q "%f%" 2>nul
121+
if ERRORLEVEL 0 (
122+
call :print "- ok"
123+
) else (
124+
call :print "- skipped (non-empty?)"
125+
)
126+
) else (
127+
call :print " - skipped: not present"
128+
)
129+
echo(
130+
exit /b
131+
132+
:init
133+
setlocal EnableDelayedExpansion
134+
setlocal EnableExtensions
135+
if /i "%~dp0" NEQ "%TEMP%\" (
136+
set tmpfile=%TEMP%\mongoc-%~nx0
137+
copy "%~f0" "!tmpfile!" >nul
138+
call "!tmpfile!" & del "!tmpfile!"
139+
exit /b
140+
)
141+
]])
142+
143+
# Prefix for the shell script:
144+
set(sh_preamble [[
145+
set -eu
146+
147+
__rmfile() {
148+
set -eu
149+
abs=$__prefix/$1
150+
printf "Remove file %s: " "$abs"
151+
if test -f "$abs" || test -L "$abs"
152+
then
153+
rm -- "$abs"
154+
echo "ok"
155+
else
156+
echo "skipped: not present"
157+
fi
158+
}
159+
160+
__rmdir() {
161+
set -eu
162+
abs=$__prefix/$1
163+
printf "Remove directory %s: " "$abs"
164+
if test -d "$abs"
165+
then
166+
list=$(ls --almost-all "$abs")
167+
if test "$list" = ""
168+
then
169+
rmdir -- "$abs"
170+
echo "ok"
171+
else
172+
echo "skipped: not empty"
173+
fi
174+
else
175+
echo "skipped: not present"
176+
fi
177+
}
178+
]])
179+
180+
# Convert the install prefix to an absolute path with the native path format:
181+
get_filename_component(install_prefix "${CMAKE_INSTALL_PREFIX}" ABSOLUTE)
182+
file(TO_NATIVE_PATH "${install_prefix}" install_prefix)
183+
# Handling DESTDIR requires careful handling of root path redirection:
184+
set(root_path)
185+
set(relative_prefix "${install_prefix}")
186+
if(COMMAND cmake_path)
187+
cmake_path(GET install_prefix ROOT_PATH root_path)
188+
cmake_path(GET install_prefix RELATIVE_PART relative_prefix)
189+
endif()
190+
191+
# The first lines that will be written to the script:
192+
set(init_lines)
193+
194+
if(UNINSTALL_IS_WIN32)
195+
# Comment the header:
196+
list(TRANSFORM header_lines PREPEND "rem ")
197+
# Add the preamble
198+
list(APPEND init_lines
199+
"@echo off"
200+
"${header_lines}"
201+
"${bat_preamble}"
202+
"if \"%DESTDIR%\"==\"\" ("
203+
" set __prefix=${install_prefix}"
204+
") else ("
205+
" set __prefix=!DESTDIR!\\${relative_prefix}"
206+
")"
207+
"")
208+
set(__rmfile "call :rmfile")
209+
set(__rmdir "call :rmdir")
210+
else()
211+
# Comment the header:
212+
list(TRANSFORM header_lines PREPEND "# * ")
213+
# Add the preamble
214+
list(APPEND init_lines
215+
"#!/bin/sh"
216+
"${header_lines}"
217+
"${sh_preamble}"
218+
"__prefix=\${DESTDIR:-}${install_prefix}"
219+
"")
220+
set(__rmfile "__rmfile")
221+
set(__rmdir "__rmdir")
222+
endif()
223+
224+
# Add the first lines to the file:
225+
string(REPLACE ";" "\n" init "${init_lines}")
226+
append_line("${init}")
227+
228+
# Generate a "remove a file" command
229+
function(add_rmfile filename)
230+
file(TO_NATIVE_PATH "${filename}" native)
231+
append_line("${__rmfile} '${native}'")
232+
endfunction()
233+
234+
# Generate a "remove a directory" command
235+
function(add_rmdir dirname)
236+
file(TO_NATIVE_PATH "${dirname}" native)
237+
append_line("${__rmdir} '${native}'")
238+
endfunction()
239+
240+
set(script_self "${install_prefix}/${UNINSTALL_SCRIPT_SELF}")
241+
set(dirs_to_remove)
242+
foreach(installed IN LISTS CMAKE_INSTALL_MANIFEST_FILES script_self)
243+
# Get the relative path from the prefix (the uninstaller will fix it up later)
244+
file(RELATIVE_PATH relpath "${install_prefix}" "${installed}")
245+
# Add a removal:
246+
add_rmfile("${relpath}")
247+
# Climb the path and collect directories:
248+
while("1")
249+
get_filename_component(installed "${installed}" DIRECTORY)
250+
file(TO_NATIVE_PATH "${installed}" installed)
251+
get_filename_component(parent "${installed}" DIRECTORY)
252+
file(TO_NATIVE_PATH "${parent}" parent)
253+
# Don't account for the prefix or direct children of the prefix:
254+
if(installed STREQUAL install_prefix OR parent STREQUAL install_prefix)
255+
break()
256+
endif()
257+
# Keep track of this directory for later:
258+
list(APPEND dirs_to_remove "${installed}")
259+
endwhile()
260+
endforeach()
261+
262+
# Now generate commands to remove (empty) directories:
263+
list(REMOVE_DUPLICATES dirs_to_remove)
264+
# Order them by depth so that we remove subdirectories before their parents:
265+
list(SORT dirs_to_remove ORDER DESCENDING)
266+
foreach(dir IN LISTS dirs_to_remove)
267+
file(RELATIVE_PATH relpath "${install_prefix}" "${dir}")
268+
add_rmdir("${relpath}")
269+
endforeach()
270+
271+
message(STATUS "Generated uninstaller: ${UNINSTALL_WRITE_FILE}")

0 commit comments

Comments
 (0)