Gentoo Archives: gentoo-dev

From: "Michał Górny" <mgorny@g.o>
To: gentoo-dev@l.g.o
Cc: "Michał Górny" <mgorny@g.o>
Subject: [gentoo-dev] [PATCH 1/2] phase-functions: Rework INSTALL_MASK to apply as a merge filter
Date: Sun, 18 Mar 2018 10:51:02
Message-Id: 20180318105045.21595-1-mgorny@gentoo.org
1 Rework the INSTALL_MASK handler to filter installed files while merging
2 instead of removing them from the installation image.
3
4 The INSTALL_MASK handling is now moved to Python side of code, with bash
5 being only used to evaluate the value of INSTALL_MASK (with respect to
6 bashrc).
7
8 The evaluated value of INSTALL_MASK is used to test for collisions,
9 and afterwards to skip masked files from being installed. It is also
10 used in uninstall code to properly remove newly-masked files that were
11 installed previously.
12 ---
13 bin/misc-functions.sh | 13 ++--
14 bin/phase-functions.sh | 3 +-
15 pym/portage/dbapi/vartree.py | 138 +++++++++++++++++++++++++++++++------------
16 3 files changed, 108 insertions(+), 46 deletions(-)
17
18 diff --git a/bin/misc-functions.sh b/bin/misc-functions.sh
19 index 7643af7b5..5e11eadb4 100755
20 --- a/bin/misc-functions.sh
21 +++ b/bin/misc-functions.sh
22 @@ -383,12 +383,13 @@ preinst_mask() {
23 fi
24 done
25
26 - install_mask "${ED}" "${INSTALL_MASK}"
27 -
28 - # remove share dir if unnessesary
29 - if has nodoc $FEATURES || has noman $FEATURES || has noinfo $FEATURES; then
30 - rmdir "${ED%/}/usr/share" &> /dev/null
31 - fi
32 + # Store the final value of INSTALL_MASK in build-info
33 + local x
34 + set -f
35 + local IFS=$' \t\n\r'
36 + x=$(echo ${INSTALL_MASK})
37 + [[ -n $x ]] && echo "$x" > "${PORTAGE_BUILDDIR}"/build-info/INSTALL_MASK
38 + set +f
39 }
40
41 preinst_sfperms() {
42 diff --git a/bin/phase-functions.sh b/bin/phase-functions.sh
43 index 3aae3ef56..7f7f6fa11 100644
44 --- a/bin/phase-functions.sh
45 +++ b/bin/phase-functions.sh
46 @@ -666,7 +666,8 @@ __dyn_install() {
47 ASFLAGS CBUILD CC CFLAGS CHOST CTARGET CXX \
48 CXXFLAGS EXTRA_ECONF EXTRA_EINSTALL EXTRA_MAKE \
49 LDFLAGS LIBCFLAGS LIBCXXFLAGS QA_CONFIGURE_OPTIONS \
50 - QA_DESKTOP_FILE QA_PREBUILT PROVIDES_EXCLUDE REQUIRES_EXCLUDE ; do
51 + QA_DESKTOP_FILE QA_PREBUILT PROVIDES_EXCLUDE REQUIRES_EXCLUDE \
52 + PKG_INSTALL_MASK ; do
53
54 x=$(echo -n ${!f})
55 [[ -n $x ]] && echo "$x" > $f
56 diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py
57 index bed76d80f..12137a0a4 100644
58 --- a/pym/portage/dbapi/vartree.py
59 +++ b/pym/portage/dbapi/vartree.py
60 @@ -1876,6 +1876,7 @@ class dblink(object):
61 for pos, e in errors:
62 writemsg(_("!!! line %d: %s\n") % (pos, e), noiselevel=-1)
63 self.contentscache = pkgfiles
64 +
65 return pkgfiles
66
67 def _prune_plib_registry(self, unmerge=False,
68 @@ -1953,7 +1954,7 @@ class dblink(object):
69 @_slot_locked
70 def unmerge(self, pkgfiles=None, trimworld=None, cleanup=True,
71 ldpath_mtimes=None, others_in_slot=None, needed=None,
72 - preserve_paths=None):
73 + preserve_paths=None, install_mask=[]):
74 """
75 Calls prerm
76 Unmerges a given package (CPV)
77 @@ -1978,6 +1979,10 @@ class dblink(object):
78 LinkageMap, since they are not registered in the
79 PreservedLibsRegistry yet.
80 @type preserve_paths: set
81 + @param install_mask: List of INSTALL_MASK values for the install
82 + enforcing cleanup. This is needed to let unmerge() clean old
83 + files that now are filtered via INSTALL_MASK.
84 + @type install_mask: list
85 @rtype: Integer
86 @return:
87 1. os.EX_OK if everything went well.
88 @@ -2121,7 +2126,7 @@ class dblink(object):
89
90 self.vartree.dbapi._fs_lock()
91 try:
92 - self._unmerge_pkgfiles(pkgfiles, others_in_slot)
93 + self._unmerge_pkgfiles(pkgfiles, others_in_slot, install_mask)
94 finally:
95 self.vartree.dbapi._fs_unlock()
96 self._clear_contents_cache()
97 @@ -2267,7 +2272,7 @@ class dblink(object):
98 self._display_merge("%s %s %s %s\n" % \
99 (zing, desc.ljust(8), file_type, file_name))
100
101 - def _unmerge_pkgfiles(self, pkgfiles, others_in_slot):
102 + def _unmerge_pkgfiles(self, pkgfiles, others_in_slot, install_mask):
103 """
104
105 Unmerges the contents of a package from the liveFS
106 @@ -2277,6 +2282,8 @@ class dblink(object):
107 @type pkgfiles: Dictionary { filename: [ 'type', '?', 'md5sum' ] }
108 @param others_in_slot: all dblink instances in this slot, excluding self
109 @type others_in_slot: list
110 + @param install_mask: List of values in INSTALL_MASK.
111 + @type install_maks: list
112 @rtype: None
113 """
114
115 @@ -2491,7 +2498,10 @@ class dblink(object):
116 (statobj.st_dev, statobj.st_ino),
117 []).append(relative_path)
118
119 - if is_owned:
120 + # if the file was replaced by new version, keep it
121 + # unless it is also INSTALL_MASK-ed in the new version
122 + # (then it was not really installed and we want to clean it)
123 + if is_owned and not self._is_install_masked(relative_path[1:], install_mask):
124 show_unmerge("---", unmerge_desc["replaced"], file_type, obj)
125 continue
126 elif relative_path in cfgfiledict:
127 @@ -3689,6 +3699,24 @@ class dblink(object):
128 def _emerge_log(self, msg):
129 emergelog(False, msg)
130
131 + def _is_install_masked(self, relative_path, install_mask):
132 + ret = False
133 + for pattern in install_mask:
134 + # absolute path pattern
135 + if pattern.startswith('/'):
136 + # match either exact path or one of parent dirs
137 + # the latter is done via matching pattern/*
138 + if (fnmatch.fnmatch(relative_path, pattern[1:])
139 + or fnmatch.fnmatch(relative_path, pattern[1:] + '/*')):
140 + ret = True
141 + break
142 + # filename
143 + else:
144 + if fnmatch.fnmatch(os.path.basename(relative_path), pattern):
145 + ret = True
146 + break
147 + return ret
148 +
149 def treewalk(self, srcroot, destroot, inforoot, myebuild, cleanup=0,
150 mydbapi=None, prev_mtimes=None, counter=None):
151 """
152 @@ -3841,7 +3869,7 @@ class dblink(object):
153 max_dblnk = dblnk
154 self._installed_instance = max_dblnk
155
156 - # Apply INSTALL_MASK before collision-protect, since it may
157 + # Update INSTALL_MASK before collision-protect, since it may
158 # be useful to avoid collisions in some scenarios.
159 # We cannot detect if this is needed or not here as INSTALL_MASK can be
160 # modified by bashrc files.
161 @@ -3851,6 +3879,17 @@ class dblink(object):
162 phase.start()
163 phase.wait()
164
165 + try:
166 + print('!!!\nRead INSTALL_MASK from %s\n!!!' % os.path.join(inforoot, "INSTALL_MASK"))
167 + with io.open(_unicode_encode(
168 + os.path.join(inforoot, "INSTALL_MASK"),
169 + encoding=_encodings['fs'], errors='strict'),
170 + mode='r', encoding=_encodings['repo.content'],
171 + errors='replace') as f:
172 + install_mask = f.read().split()
173 + except EnvironmentError as e:
174 + install_mask = self.settings.get('INSTALL_MASK', '').split()
175 +
176 # We check for unicode encoding issues after src_install. However,
177 # the check must be repeated here for binary packages (it's
178 # inexpensive since we call os.walk() here anyway).
179 @@ -3922,6 +3961,10 @@ class dblink(object):
180
181 relative_path = fpath[srcroot_len:]
182
183 + # filter on INSTALL_MASK
184 + if self._is_install_masked(relative_path, install_mask):
185 + continue
186 +
187 if line_ending_re.search(relative_path) is not None:
188 paths_with_newlines.append(relative_path)
189
190 @@ -4270,7 +4313,8 @@ class dblink(object):
191 else:
192 cfgfiledict["IGNORE"]=0
193
194 - rval = self._merge_contents(srcroot, destroot, cfgfiledict)
195 + rval = self._merge_contents(srcroot, destroot, cfgfiledict,
196 + install_mask)
197 if rval != os.EX_OK:
198 return rval
199 finally:
200 @@ -4351,7 +4395,7 @@ class dblink(object):
201 dblnk.settings.backup_changes("REPLACED_BY_VERSION")
202 unmerge_rval = dblnk.unmerge(ldpath_mtimes=prev_mtimes,
203 others_in_slot=others_in_slot, needed=needed,
204 - preserve_paths=preserve_paths)
205 + preserve_paths=preserve_paths, install_mask=install_mask)
206 dblnk.settings.pop("REPLACED_BY_VERSION", None)
207
208 if unmerge_rval == os.EX_OK:
209 @@ -4523,7 +4567,7 @@ class dblink(object):
210
211 return backup_p
212
213 - def _merge_contents(self, srcroot, destroot, cfgfiledict):
214 + def _merge_contents(self, srcroot, destroot, cfgfiledict, install_mask):
215
216 cfgfiledict_orig = cfgfiledict.copy()
217
218 @@ -4550,7 +4594,8 @@ class dblink(object):
219 # we do a first merge; this will recurse through all files in our srcroot but also build up a
220 # "second hand" of symlinks to merge later
221 if self.mergeme(srcroot, destroot, outfile, secondhand,
222 - self.settings["EPREFIX"].lstrip(os.sep), cfgfiledict, mymtime):
223 + self.settings["EPREFIX"].lstrip(os.sep), cfgfiledict,
224 + mymtime, install_mask):
225 return 1
226
227 # now, it's time for dealing our second hand; we'll loop until we can't merge anymore. The rest are
228 @@ -4562,7 +4607,7 @@ class dblink(object):
229
230 thirdhand = []
231 if self.mergeme(srcroot, destroot, outfile, thirdhand,
232 - secondhand, cfgfiledict, mymtime):
233 + secondhand, cfgfiledict, mymtime, install_mask):
234 return 1
235
236 #swap hands
237 @@ -4576,7 +4621,7 @@ class dblink(object):
238 if len(secondhand):
239 # force merge of remaining symlinks (broken or circular; oh well)
240 if self.mergeme(srcroot, destroot, outfile, None,
241 - secondhand, cfgfiledict, mymtime):
242 + secondhand, cfgfiledict, mymtime, install_mask):
243 return 1
244
245 #restore umask
246 @@ -4597,7 +4642,8 @@ class dblink(object):
247
248 return os.EX_OK
249
250 - def mergeme(self, srcroot, destroot, outfile, secondhand, stufftomerge, cfgfiledict, thismtime):
251 + def mergeme(self, srcroot, destroot, outfile, secondhand, stufftomerge,
252 + cfgfiledict, thismtime, install_mask):
253 """
254
255 This function handles actual merging of the package contents to the livefs.
256 @@ -4651,6 +4697,7 @@ class dblink(object):
257 while mergelist:
258
259 relative_path = mergelist.pop()
260 + instmasked = self._is_install_masked(relative_path, install_mask)
261 mysrc = join(srcroot, relative_path)
262 mydest = join(destroot, relative_path)
263 # myrealdest is mydest without the $ROOT prefix (makes a difference if ROOT!="/")
264 @@ -4737,7 +4784,7 @@ class dblink(object):
265 destmd5 = None
266
267 moveme = True
268 - if protected:
269 + if protected and not instmasked:
270 mydest, protected, moveme = self._protect(cfgfiledict,
271 protect_if_modified, mymd5, myto, mydest,
272 myrealdest, mydmode, destmd5, mydest_link)
273 @@ -4765,7 +4812,7 @@ class dblink(object):
274 # we can simply test for existence of this file to see if the target has been merged yet
275 myrealto = normalize_path(os.path.join(destroot, myabsto))
276 if mydmode is not None and stat.S_ISDIR(mydmode):
277 - if not protected:
278 + if not protected and not instmasked:
279 # we can't merge a symlink over a directory
280 newdest = self._new_backup_path(mydest)
281 msg = []
282 @@ -4785,26 +4832,32 @@ class dblink(object):
283 # it later.
284 secondhand.append(mysrc[len(srcroot):])
285 continue
286 - # unlinking no longer necessary; "movefile" will overwrite symlinks atomically and correctly
287 - if moveme:
288 - zing = ">>>"
289 - mymtime = movefile(mysrc, mydest, newmtime=thismtime,
290 - sstat=mystat, mysettings=self.settings,
291 - encoding=_encodings['merge'])
292
293 - try:
294 - self._merged_path(mydest, os.lstat(mydest))
295 - except OSError:
296 - pass
297 + if instmasked:
298 + zing = "###"
299 + # pass mymtime through from initial stat
300 + else:
301 + # unlinking no longer necessary; "movefile" will overwrite symlinks atomically and correctly
302 + if moveme:
303 + zing = ">>>"
304 + mymtime = movefile(mysrc, mydest, newmtime=thismtime,
305 + sstat=mystat, mysettings=self.settings,
306 + encoding=_encodings['merge'])
307 +
308 + try:
309 + self._merged_path(mydest, os.lstat(mydest))
310 + except OSError:
311 + pass
312
313 if mymtime != None:
314 - # Use lexists, since if the target happens to be a broken
315 - # symlink then that should trigger an independent warning.
316 - if not (os.path.lexists(myrealto) or
317 - os.path.lexists(join(srcroot, myabsto))):
318 - self._eqawarn('preinst',
319 - [_("QA Notice: Symbolic link /%s points to /%s which does not exist.")
320 - % (relative_path, myabsto)])
321 + if not instmasked:
322 + # Use lexists, since if the target happens to be a broken
323 + # symlink then that should trigger an independent warning.
324 + if not (os.path.lexists(myrealto) or
325 + os.path.lexists(join(srcroot, myabsto))):
326 + self._eqawarn('preinst',
327 + [_("QA Notice: Symbolic link /%s points to /%s which does not exist.")
328 + % (relative_path, myabsto)])
329
330 showMessage("%s %s -> %s\n" % (zing, mydest, myto))
331 if sys.hexversion >= 0x3030000:
332 @@ -4819,7 +4872,9 @@ class dblink(object):
333 return 1
334 elif stat.S_ISDIR(mymode):
335 # we are merging a directory
336 - if mydmode != None:
337 + if instmasked:
338 + showMessage("### %s/\n" % mydest)
339 + elif mydmode != None:
340 # destination exists
341
342 if bsd_chflags:
343 @@ -4906,10 +4961,11 @@ class dblink(object):
344 os.chown(mydest, mystat[4], mystat[5])
345 showMessage(">>> %s/\n" % mydest)
346
347 - try:
348 - self._merged_path(mydest, os.lstat(mydest))
349 - except OSError:
350 - pass
351 + if not instmasked:
352 + try:
353 + self._merged_path(mydest, os.lstat(mydest))
354 + except OSError:
355 + pass
356
357 outfile.write("dir "+myrealdest+"\n")
358 # recurse and merge this directory
359 @@ -4918,7 +4974,7 @@ class dblink(object):
360
361 elif stat.S_ISREG(mymode):
362 # we are merging a regular file
363 - if not protected and \
364 + if not protected and not instmasked and \
365 mydmode is not None and stat.S_ISDIR(mydmode):
366 # install of destination is blocked by an existing directory with the same name
367 newdest = self._new_backup_path(mydest)
368 @@ -4932,9 +4988,11 @@ class dblink(object):
369 self._eerror("preinst", msg)
370 mydest = newdest
371
372 + if instmasked:
373 + zing = "###"
374 # whether config protection or not, we merge the new file the
375 # same way. Unless moveme=0 (blocking directory)
376 - if moveme:
377 + elif moveme:
378 # Create hardlinks only for source files that already exist
379 # as hardlinks (having identical st_dev and st_ino).
380 hardlink_key = (mystat.st_dev, mystat.st_ino)
381 @@ -4967,7 +5025,9 @@ class dblink(object):
382 else:
383 # we are merging a fifo or device node
384 zing = "!!!"
385 - if mydmode is None:
386 + if instmasked:
387 + zing = "###"
388 + elif mydmode is None:
389 # destination doesn't exist
390 if movefile(mysrc, mydest, newmtime=thismtime,
391 sstat=mystat, mysettings=self.settings,
392 --
393 2.16.2

Replies