Gentoo Archives: gentoo-portage-dev

From: Zac Medico <zmedico@g.o>
To: gentoo-portage-dev@l.g.o
Cc: "Michał Górny" <mgorny@g.o>, Zac Medico <zmedico@g.o>
Subject: [gentoo-portage-dev] [PATCH 3/4] {,PKG_}INSTALL_MASK: python implementation
Date: Tue, 27 Mar 2018 09:20:14
Message-Id: 20180327091348.4354-4-zmedico@gentoo.org
In Reply to: [gentoo-portage-dev] [PATCH 0/4] {,PKG_}INSTALL_MASK support for exclusions (bug 651214) by Zac Medico
1 The InstallMask.match code comes from the dblink _is_install_masked
2 method from portage-mgorny:
3
4 https://github.com/mgorny/portage/commit/f5ac3af3216d209618a2f232b4bf720bc8b520ad
5 ---
6 bin/misc-functions.sh | 73 --------------------------
7 pym/_emerge/PackagePhase.py | 13 ++++-
8 pym/portage/dbapi/vartree.py | 24 ++++++---
9 pym/portage/util/install_mask.py | 109 +++++++++++++++++++++++++++++++++++++++
10 4 files changed, 139 insertions(+), 80 deletions(-)
11 create mode 100644 pym/portage/util/install_mask.py
12
13 diff --git a/bin/misc-functions.sh b/bin/misc-functions.sh
14 index 4fe6ead17..ea2557724 100755
15 --- a/bin/misc-functions.sh
16 +++ b/bin/misc-functions.sh
17 @@ -317,72 +317,6 @@ postinst_qa_check() {
18 done < <(printf "%s\0" "${qa_checks[@]}" | LC_ALL=C sort -u -z)
19 }
20
21 -install_mask() {
22 - local root="$1"
23 - shift
24 - local install_mask="$*"
25 -
26 - # We think of $install_mask as a space-separated list of
27 - # globs. We don't want globbing in the "for" loop; that is, we
28 - # want to keep the asterisks in the indivual entries.
29 - local shopts=$-
30 - set -o noglob
31 - local no_inst
32 - for no_inst in ${install_mask}; do
33 - # Here, $no_inst is a single "entry" potentially
34 - # containing a glob. From now on, we *do* want to
35 - # expand it.
36 - set +o noglob
37 -
38 - # The standard case where $no_inst is something that
39 - # the shell could expand on its own.
40 - if [[ -e "${root}"/${no_inst} || -L "${root}"/${no_inst} ||
41 - "${root}"/${no_inst} != $(echo "${root}"/${no_inst}) ]] ; then
42 - __quiet_mode || einfo "Removing ${no_inst}"
43 - rm -Rf "${root}"/${no_inst} >&/dev/null
44 - fi
45 -
46 - # We also want to allow the user to specify a "bare
47 - # glob." For example, $no_inst="*.a" should prevent
48 - # ALL files ending in ".a" from being installed,
49 - # regardless of their location/depth. We achieve this
50 - # by passing the pattern to `find`.
51 - find "${root}" \( -path "${no_inst}" -or -name "${no_inst}" \) \
52 - -print0 2> /dev/null \
53 - | LC_ALL=C sort -z \
54 - | while read -r -d ''; do
55 - __quiet_mode || einfo "Removing /${REPLY#${root}}"
56 - rm -Rf "${REPLY}" >&/dev/null
57 - done
58 -
59 - done
60 - # set everything back the way we found it
61 - set +o noglob
62 - set -${shopts}
63 -}
64 -
65 -preinst_mask() {
66 - if [ -z "${D}" ]; then
67 - eerror "${FUNCNAME}: D is unset"
68 - return 1
69 - fi
70 -
71 - if ! ___eapi_has_prefix_variables; then
72 - local ED=${D}
73 - fi
74 -
75 - # Make sure $PWD is not ${D} so that we don't leave gmon.out files
76 - # in there in case any tools were built with -pg in CFLAGS.
77 - cd "${T}"
78 -
79 - install_mask "${ED}" "${INSTALL_MASK}"
80 -
81 - # remove share dir if unnessesary
82 - if has nodoc $FEATURES || has noman $FEATURES || has noinfo $FEATURES; then
83 - rmdir "${ED%/}/usr/share" &> /dev/null
84 - fi
85 -}
86 -
87 preinst_sfperms() {
88 if [ -z "${D}" ]; then
89 eerror "${FUNCNAME}: D is unset"
90 @@ -506,13 +440,6 @@ __dyn_package() {
91 # in there in case any tools were built with -pg in CFLAGS.
92 cd "${T}" || die
93
94 - if [[ -n ${PKG_INSTALL_MASK} ]] ; then
95 - # The caller makes ${D} refer to a temporary copy in this
96 - # case, so that this does not mask files from the normal
97 - # install image.
98 - install_mask "${D%/}${EPREFIX}/" "${PKG_INSTALL_MASK}"
99 - fi
100 -
101 local tar_options=""
102 [[ $PORTAGE_VERBOSE = 1 ]] && tar_options+=" -v"
103 has xattr ${FEATURES} && [[ $(tar --help 2> /dev/null) == *--xattrs* ]] && tar_options+=" --xattrs"
104 diff --git a/pym/_emerge/PackagePhase.py b/pym/_emerge/PackagePhase.py
105 index 083745059..35137532a 100644
106 --- a/pym/_emerge/PackagePhase.py
107 +++ b/pym/_emerge/PackagePhase.py
108 @@ -11,6 +11,8 @@ import portage
109 from portage import os
110 from portage import _encodings
111 from portage import _unicode_encode
112 +from portage.util._async.AsyncFunction import AsyncFunction
113 +from portage.util.install_mask import install_mask_dir, InstallMask
114
115
116 class PackagePhase(CompositeTask):
117 @@ -31,7 +33,7 @@ class PackagePhase(CompositeTask):
118 encoding=_encodings['fs'], errors='strict'),
119 mode='r', encoding=_encodings['repo.content'],
120 errors='replace') as f:
121 - self._pkg_install_mask = f.read().split()
122 + self._pkg_install_mask = InstallMask(f.read())
123 except OSError:
124 self._pkg_install_mask = None
125 if self._pkg_install_mask:
126 @@ -51,6 +53,15 @@ class PackagePhase(CompositeTask):
127 if self._default_exit(proc) != os.EX_OK:
128 self.wait()
129 else:
130 + self._start_task(AsyncFunction(
131 + target=install_mask_dir,
132 + args=(self._proot, self._pkg_install_mask)),
133 + self._pkg_install_mask_exit)
134 +
135 + def _pkg_install_mask_exit(self, proc):
136 + if self._default_exit(proc) != os.EX_OK:
137 + self.wait()
138 + else:
139 self._start_package_phase()
140
141 def _start_package_phase(self):
142 diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py
143 index bed76d80f..bf7bd8c2a 100644
144 --- a/pym/portage/dbapi/vartree.py
145 +++ b/pym/portage/dbapi/vartree.py
146 @@ -32,6 +32,7 @@ portage.proxy.lazyimport.lazyimport(globals(),
147 'grabdict,normalize_path,new_protect_filename',
148 'portage.util.digraph:digraph',
149 'portage.util.env_update:env_update',
150 + 'portage.util.install_mask:install_mask_dir,InstallMask',
151 'portage.util.listdir:dircache,listdir',
152 'portage.util.movefile:movefile',
153 'portage.util.path:first_existing,iter_parents',
154 @@ -3841,15 +3842,26 @@ class dblink(object):
155 max_dblnk = dblnk
156 self._installed_instance = max_dblnk
157
158 - # Apply INSTALL_MASK before collision-protect, since it may
159 + # Update INSTALL_MASK before collision-protect, since it may
160 # be useful to avoid collisions in some scenarios.
161 # We cannot detect if this is needed or not here as INSTALL_MASK can be
162 # modified by bashrc files.
163 - phase = MiscFunctionsProcess(background=False,
164 - commands=["preinst_mask"], phase="preinst",
165 - scheduler=self._scheduler, settings=self.settings)
166 - phase.start()
167 - phase.wait()
168 + try:
169 + with io.open(_unicode_encode(os.path.join(inforoot, "INSTALL_MASK"),
170 + encoding=_encodings['fs'], errors='strict'),
171 + mode='r', encoding=_encodings['repo.content'],
172 + errors='replace') as f:
173 + install_mask = InstallMask(f.read())
174 + except OSError:
175 + install_mask = None
176 +
177 + if install_mask:
178 + install_mask_dir(self.settings["ED"], install_mask)
179 + if any(x in self.settings.features for x in ('nodoc', 'noman', 'noinfo')):
180 + try:
181 + os.rmdir(os.path.join(self.settings["ED"], 'usr', 'share'))
182 + except OSError:
183 + pass
184
185 # We check for unicode encoding issues after src_install. However,
186 # the check must be repeated here for binary packages (it's
187 diff --git a/pym/portage/util/install_mask.py b/pym/portage/util/install_mask.py
188 new file mode 100644
189 index 000000000..64fe0b21a
190 --- /dev/null
191 +++ b/pym/portage/util/install_mask.py
192 @@ -0,0 +1,109 @@
193 +# Copyright 2018 Gentoo Foundation
194 +# Distributed under the terms of the GNU General Public License v2
195 +
196 +__all__ = ['install_mask_dir', 'InstallMask']
197 +
198 +import errno
199 +import fnmatch
200 +import os
201 +
202 +from portage.exception import (
203 + OperationNotPermitted, PermissionDenied, FileNotFound)
204 +from portage.util import normalize_path
205 +
206 +
207 +class InstallMask(object):
208 + def __init__(self, install_mask):
209 + """
210 + @param install_mask: INSTALL_MASK value
211 + @type install_mask: str
212 + """
213 + self._install_mask = install_mask.split()
214 +
215 + def match(self, path):
216 + """
217 + @param path: file path relative to ${ED}
218 + @type path: str
219 + @rtype: bool
220 + @return: True if path matches INSTALL_MASK, False otherwise
221 + """
222 + ret = False
223 + for pattern in self._install_mask:
224 + # absolute path pattern
225 + if pattern.startswith('/'):
226 + # match either exact path or one of parent dirs
227 + # the latter is done via matching pattern/*
228 + if (fnmatch.fnmatch(path, pattern[1:])
229 + or fnmatch.fnmatch(path, pattern[1:] + '/*')):
230 + ret = True
231 + break
232 + # filename
233 + else:
234 + if fnmatch.fnmatch(os.path.basename(path), pattern):
235 + ret = True
236 + break
237 + return ret
238 +
239 +
240 +_exc_map = {
241 + errno.ENOENT: FileNotFound,
242 + errno.EPERM: OperationNotPermitted,
243 + errno.EACCES: PermissionDenied,
244 +}
245 +
246 +
247 +def _raise_exc(e):
248 + """
249 + Wrap OSError with portage.exception wrapper exceptions, with
250 + __cause__ chaining when python supports it.
251 +
252 + @param e: os exception
253 + @type e: OSError
254 + @raise PortageException: portage.exception wrapper exception
255 + """
256 + wrapper_cls = _exc_map.get(e.errno)
257 + if wrapper_cls is None:
258 + raise
259 + wrapper = wrapper_cls(str(e))
260 + wrapper.__cause__ = e
261 + raise wrapper
262 +
263 +
264 +def install_mask_dir(base_dir, install_mask, onerror=None):
265 + """
266 + Remove files and directories matched by INSTALL_MASK.
267 +
268 + @param base_dir: directory path corresponding to ${ED}
269 + @type base_dir: str
270 + @param install_mask: INSTALL_MASK configuration
271 + @type install_mask: InstallMask
272 + """
273 + onerror = onerror or _raise_exc
274 + base_dir = normalize_path(base_dir)
275 + base_dir_len = len(base_dir) + 1
276 + dir_stack = []
277 +
278 + # Remove masked files.
279 + for parent, dirs, files in os.walk(base_dir, onerror=onerror):
280 + dir_stack.append(parent)
281 + for fname in files:
282 + abs_path = os.path.join(parent, fname)
283 + relative_path = abs_path[base_dir_len:]
284 + if install_mask.match(relative_path):
285 + try:
286 + os.unlink(abs_path)
287 + except OSError as e:
288 + onerror(e)
289 +
290 + # Remove masked dirs (unless non-empty due to exclusions).
291 + while True:
292 + try:
293 + dir_path = dir_stack.pop()
294 + except IndexError:
295 + break
296 +
297 + if install_mask.match(dir_path[base_dir_len:]):
298 + try:
299 + os.rmdir(dir_path)
300 + except OSError:
301 + pass
302 --
303 2.13.6