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 |