Skip to content

Commit 602d9b3

Browse files
author
Gene C
committed
* soname handling has been re-written from scratch and improved substantially.
It now identifies every soname versioned library in elf executables along with their full path. It also properly handles executables built with *--rpath* loader options. Previous versions relied on makepkg soname output which, unfortunately, only lists sonames if they are also listed as a PKGBUILD dependency. We need every soname versioned library to ensure we do the right thing and rebuild when needed. So it was a mistake to rely on this. Can also specify how to handle version comparisons similar to the way package version comparisons are done (e.g. soname > major) * Old options now deprecated * (*--mpk-xxx*) * (*--soname-build*) : use *--soname-comp* instead
1 parent b389569 commit 602d9b3

17 files changed

+540
-368
lines changed

Docs/conf.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
project = "mkpkg"
1010
copyright = '2023, Gene C'
1111
author = 'Gene C'
12-
release = '5.0.0'
12+
release = '6.0.0'
1313

1414
# -- General configuration ---------------------------------------------------
1515
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

README.rst

+69-13
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,26 @@ Tool to rebuild Arch packages based on dependency triggers.
1212
New / Interesting
1313
==================
1414

15-
* Version comparisons now use pyalpm instead of packaging
16-
packaging.version barfs on systemd package version 255rc2.1
15+
* Major update: soname handling has been re-written from scratch and improved substantially.
16+
17+
It now identifies every soname versioned library in elf executables
18+
along with their full path. It also properly handles executables
19+
built with *--rpath* loader options.
20+
21+
Previous versions relied on makepkg soname output
22+
which, unfortunately, only lists sonames if they are also listed as a PKGBUILD dependency.
23+
We need every soname versioned library to ensure we do the right thing
24+
and rebuild when needed. So it was a mistake to rely on this.
25+
26+
Can also specify how to handle version comparisons similar to the way
27+
package version comparisons are done (e.g. soname > major)
28+
29+
If you're interested, the soname info is saved into the file *.mkp_dep_soname*
30+
31+
* Old options now deprecated
32+
33+
* (*--mpk-xxx*)
34+
* (*--soname-build*) : use *--soname-comp* instead
1735

1836
#################
1937
mkpkg application
@@ -205,7 +223,7 @@ With the trigger conditions in the PKGBUID, then simply call mkpkg instead of ma
205223
Options for mkpkg are those before any double dash *--*. Any options following *--*
206224
are passed through to *makepkg* [#]_.
207225

208-
.. [#] The older style options using *--mkp-* are deprecated but are supported to ensure backward compatibility. They will be removed at some point in the future.
226+
.. [#] The older style options using *--mkp-* are now deprecated.
209227
210228
Options
211229
=======
@@ -224,9 +242,26 @@ The options currently supported by mkpkg are:
224242

225243
Attempts to update saved metadata files. Faster, if imperfect, alternative to rebuild.
226244
If there is no saved metadata, and build is up to date, will try refresh the build info.
227-
Files updated are *.mkp\_dep\_vers* and *.mkp_dep_soname*. The soname data can only be updated
228-
if the .PKGINFO file is still in the *pkg* directory. Forcing a rebuild is the
229-
slower alternative but is guaranteed to have information needed.
245+
Files updated are *.mkp\_dep\_vers* and *.mkp_dep_soname*.
246+
247+
Note that *sonames* are found by examining any executables in the *pkg* directory.
248+
If the *pkg* directory is empty, the refresh will not find any sonames.
249+
250+
* (**so-comp, --soname-comp**)
251+
252+
How to handle automatic soname changes. Default value is *last* - which uses the entire soname version
253+
when comparing to what's available.
254+
255+
* *never* : soname dependencies are ignored
256+
257+
* *newer* : if soname is newer then reubild (time based)
258+
259+
* *keep* : if soname library is still available, then dont rebuild even if newer version(s) are available
260+
261+
* *vcomp* : rebuild if soname version is greater than the *vcomp* version. *vcomp* is one of *major*, *minor*, *patch*, *extra* or *last* - same as for regular depenencies.
262+
263+
* *neverever* : Developer option - will not rebuild even if the soname library is no longer available.
264+
230265

231266
* (*--*)
232267

@@ -236,9 +271,9 @@ The options currently supported by mkpkg are:
236271

237272
Configs are looked for in first in /etc/mkpkg/config and then in
238273
~/.config/mkpkg/config. Config files are in TOML format.
239-
e.g. to change the default soname rebuild option::
274+
e.g. to change the default soname rebuild compare option from default of *last*::
240275

241-
soname_build = "newer"
276+
soname_comp = "newer"
242277

243278
How mkpkg works
244279
===============
@@ -488,6 +523,31 @@ Created by Gene C. and licensed under the terms of the MIT license.
488523
Some history
489524
============
490525

526+
Version 4.1.0
527+
-------------
528+
529+
* soname rewrite
530+
531+
New argument for how soname changes are treated : *-so-comp, --soname-comp*.
532+
533+
Can be *<compare>*, *newer*, *never* or key how to compare the soname versions.
534+
The comparison types are the same as for package dependencies described above.
535+
Default is *last* which means the entire soname version will be compared to
536+
whats available and rebuild will be triggered if a later version now available.
537+
538+
*<compare>* e.g. *>major* or *>minor*' or *last* etc.
539+
If the last built soname was 5.1, and now available is 5.2 then
540+
*minor* and *last* will trigger rebuild while *major* would not. *newer* triggers if the
541+
last modify time of the library is newer.
542+
543+
Previous version used sonmaes produced by makepkg - however this only generates
544+
sonames if they are listed as dependencies. We want to get every soname - so
545+
we started over from scratch. By using our own soname generate we catch
546+
every soname and its absolute path - this enables us to correctly treat soname
547+
changes. This approach will also correctly deal with any *rpath* loader flags
548+
causing executable to use shared library from path(s) specified at compile time.
549+
550+
491551
Version 4.1.0
492552
-------------
493553

@@ -498,17 +558,13 @@ Version 4.1.0
498558
compatibility the older *--mkp-* style arguments are honored, but the newer simpler
499559
ones are preferred. e.g. *-v, --verb* for verbose. Help availble via *-h*.
500560

501-
* New argument for how soname changes are treated : *-so-bld, --soname-build*.
502-
503-
Can be *missing*, *newer* or *never*. Default is missing - rebuild if soname
504-
no longer available.
505561

506562
* Config file now available.
507563

508564
Configs are looked for in /etc/mkpkg/config then ~/.config/mkpkg/config. It should
509565
be in TOML format. e.g. to change the default soname rebuild option::
510566

511-
soname_build = "newer"
567+
soname_comp = "newer"
512568

513569
Version 4.0.0
514570
-------------

packaging/PKGBUILD

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ pkgname='mkpkg'
1010
pkgdesc='Tool to rebuild packages based on dependency triggers'
1111
_gitname='Arch-mkpkg'
1212

13-
pkgver=5.0.0
13+
pkgver=6.0.0
1414
pkgrel=1
1515
url="https://github.com/gene-git/Arch-mkpkg"
1616

1717
arch=(any)
1818
license=(MIT)
1919
# tomli only needed for python < 3.11
20-
depends=('python>3.9' 'pyalpm' 'python-tomli' 'python-tomli-w')
20+
depends=('python>3.9' 'pyalpm' 'python-tomli' 'python-tomli-w' 'python-pyelftools')
2121

2222
# To build docs uncommont sphinx/texlive
2323
makedepends=('git' 'python-build' 'python-installer' 'python-wheel' 'python-hatch' 'rsync'

src/mkpkg/__about__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
"""
44
Project mkpkg
55
"""
6-
__version__ = "5.0.0"
6+
__version__ = "6.0.0"

src/mkpkg/lib/build.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
from .tools import check_package_exists
1515
from .build_makepkg import build_w_makepkg
1616
from .dep_vers import get_pkg_dep_vers_now
17-
from .soname import read_last_pkg_dep_soname
18-
from .soname import soname_rebuild_needed
17+
from .soname_deps import read_soname_deps
18+
from .soname_rebuild import soname_rebuild_needed
1919

2020
def _build_if_needed(pkg_vers_changed, soname_build, pkg_file_info, mkpkg):
2121
"""
@@ -140,7 +140,7 @@ def build(mkpkg):
140140
#
141141
# read the last soname data (if exists)
142142
#
143-
read_last_pkg_dep_soname(mkpkg)
143+
read_soname_deps(mkpkg)
144144
soname_build = soname_rebuild_needed(mkpkg)
145145

146146
#

src/mkpkg/lib/check_deps.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ def _most_recent_package_date(mkpkg):
3131
mod_time = os.path.getmtime(pkgfile)
3232
pkg_dtime = datetime.datetime.fromtimestamp(mod_time)
3333
if dtime :
34-
if pkg_dtime > dtime:
35-
dtime = pkg_dtime
34+
dtime = max(dtime, pkg_dtime)
3635
else:
3736
dtime = pkg_dtime
3837
return dtime

src/mkpkg/lib/class_config.py

+35-24
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ def __init__(self):
1616
self.verb = False
1717
self.force = False
1818
self.refresh = False
19-
self.use_makedepends = False # deprecated
20-
self.soname_build = 'missing'
19+
20+
self.soname_comp = 'last' # was previously soname_build
2121
self.makepkg_args = None
2222

2323
#
@@ -40,28 +40,7 @@ def __init__(self):
4040
for (opt,val) in conf.items():
4141
setattr(self, opt, val)
4242

43-
# Command line args
44-
opts = [
45-
[('-v', '--verb', '--mkp-verb'), {'action' : 'store_true',
46-
'help' : 'More verbose output',
47-
}
48-
],
49-
[('-f', '--force', '--mkp-force'), {'action' : 'store_true',
50-
'help' : 'Bump package release and rebuild'}
51-
],
52-
[('-r', '--refresh', '--mkp-refresh'), {'action' : 'store_true',
53-
'help' : 'update saved metadata files.'}
54-
],
55-
[('-so-bld', '--mkp-soname-build'),
56-
{'help' : f'Rebuild if soname missing, newer, never (default {self.soname_build})'}
57-
],
58-
[('--mkp-use_makedepends'), {'action' : 'store_true',
59-
'help' : 'Use makedepends array of no _mkpkg_xxx set (deprecated))'}
60-
],
61-
[('makepkg'), {'nargs' : '*',
62-
'help' : 'All remaining args after -- passed to makepkg'}
63-
],
64-
]
43+
opts = get_available_opts(self)
6544
par = argparse.ArgumentParser(description='mkpkg')
6645
for opt in opts:
6746
(opts), kwargs = opt
@@ -85,3 +64,35 @@ def __init__(self):
8564
del opt_dict['makepkg']
8665
for (opt, val) in opt_dict.items():
8766
setattr(self, opt, val)
67+
68+
def get_available_opts(conf) -> [[tuple, dict]]:
69+
"""
70+
Available options
71+
"""
72+
# Command line args
73+
opts = []
74+
act = 'action'
75+
act_on = 'store_true'
76+
77+
ohelp = 'More verbose output'
78+
opt = [('-v', '--verb'), {'help' : ohelp, act : act_on}]
79+
opts.append(opt)
80+
81+
ohelp = 'Bump package release and rebuild'
82+
opt = [('-f', '--force'), {'help' : ohelp, act : act_on}]
83+
opts.append(opt)
84+
85+
ohelp = 'Update saved metadata files.'
86+
opt = [('-r', '--refresh'), {'help' : ohelp, act : act_on}]
87+
opts.append(opt)
88+
89+
default = conf.soname_comp
90+
ohelp = 'When soname rebuilds: never, newer, keep or major/minor/last etc (default {default})'
91+
opt = [('-so-comp', '--soname-comp'), {'help' : ohelp, act : act_on, 'default' : default}]
92+
opts.append(opt)
93+
94+
ohelp = 'All remaining args after -- passed to makepkg'
95+
opt = [('makepkg'), {'help' : ohelp, 'nargs' : '*'}]
96+
opts.append(opt)
97+
98+
return opts

src/mkpkg/lib/class_mkpkg.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ class MkPkg
1616
from .tools import print_summary
1717
from .build import build
1818
from .dep_vers import write_current_pkg_dep_vers
19-
from .soname import write_current_pkg_dep_soname
20-
from .soname import pkginfo_soname_dep_info
19+
from .soname_deps import (write_soname_deps )
20+
from .soname import (get_current_soname_info )
2121

2222
class MkPkg:
2323
""" MkPkg wrapper class """
@@ -44,11 +44,14 @@ def __init__(self):
4444
self.verb = self.conf.verb # don't show normal makepkg output
4545
self.force = self.conf.force # run makepkg even if not necessary
4646
self.refresh = self.conf.refresh # refresh .mkpkg_dep_soname .mkpkg_dep_vers
47-
self.soname_build = self.conf.soname_build
48-
self.argv = self.conf.makepkg_args # passed down to makepkg
49-
self.use_makedepends = self.conf.use_makedepends # deprecated
5047

48+
# soname_build ~ 'never', 'newer', <compare-how>
49+
# These compare using greater than: 'major' or 'minor' or 'last' etc
50+
self.soname_comp = self.conf.soname_comp
5151
self.soname_info = {}
52+
self.avail_soname_info = {}
53+
54+
self.argv = self.conf.makepkg_args # passed down to makepkg
5255

5356
self.cwd = os.getcwd()
5457
self.mymsg = GcMsg()
@@ -77,6 +80,6 @@ def build(self):
7780
# of any depenencies (including sonames)
7881
#
7982
write_current_pkg_dep_vers(self)
80-
self.soname_info = pkginfo_soname_dep_info(self)
81-
write_current_pkg_dep_soname(self)
83+
self.soname_info = get_current_soname_info('pkg')
84+
write_soname_deps(self)
8285
print_summary(self)

src/mkpkg/lib/dep_vers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ def get_depends_versions(mkpkg, depends_vers):
158158
pkg_vers = dep_vers_now[pkg]
159159
last_vers = _get_pkg_dep_vers_last(mkpkg, pkg)
160160

161-
info = check_version_trigger(mkpkg, oper, vers_trigger, pkg_vers, last_vers)
161+
info = check_version_trigger(mkpkg.msg, oper, vers_trigger, pkg_vers, last_vers)
162162
(trigger, pvers_comp, lvers_comp) = info
163163

164164
this_one = [pkg, oper, vers_trigger, pvers_comp, lvers_comp, trigger]

src/mkpkg/lib/elf_utils.py

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# SPDX-License-Identifier: MIT
2+
# Copyright (c) 2022,2023 Gene C
3+
"""
4+
soname tools
5+
"""
6+
import os
7+
from elftools.elf.elffile import ELFFile
8+
from elftools.common.exceptions import ELFError
9+
from .run_prog import run_prog
10+
11+
def file_is_elf(filename):
12+
"""
13+
Return True if filename is elf executable
14+
"""
15+
is_elf = False
16+
if not os.path.exists(filename):
17+
return is_elf
18+
19+
with open(filename, 'rb') as fob:
20+
try:
21+
elffile = ELFFile(fob)
22+
header = elffile.header
23+
e_ident = header.get('e_ident')
24+
if e_ident:
25+
ei_class = e_ident.get('EI_CLASS')
26+
if ei_class and ei_class.startswith('ELFCLASS'):
27+
is_elf = True
28+
except ELFError :
29+
pass
30+
return is_elf
31+
32+
def sonames_in_elf_file(elf_file):
33+
"""
34+
Extract list of sonames
35+
eaech item is tuple (soname, vers, path)
36+
e.g. (libssl.so, 56, /usr/lib/libressl.so.56)
37+
"""
38+
sonames = []
39+
if not os.path.exists(elf_file):
40+
return sonames
41+
42+
pargs = ['/usr/bin/ldd', elf_file]
43+
[retc, output, _errors] = run_prog(pargs)
44+
if retc == 0 and output:
45+
rows = output.splitlines()
46+
for row in rows:
47+
if 'linux-vdso.so' in row or 'ld-linux' in row:
48+
continue
49+
50+
srow = row.strip().split()
51+
if len(srow) < 3:
52+
continue
53+
soname = srow[0]
54+
libname = srow[2]
55+
vsplit = soname.split('.so.')
56+
if len(vsplit) < 2:
57+
continue
58+
59+
#item = (soname, vers, libname)
60+
if libname not in sonames:
61+
sonames.append(libname)
62+
return sonames

0 commit comments

Comments
 (0)