Skip to content

Commit 5b0d787

Browse files
authored
Merge pull request #449 from citrus-it/pkgdepend
pkgdepend resolve uses too much memory
2 parents 5d3b531 + 343f856 commit 5b0d787

File tree

5 files changed

+118
-56
lines changed

5 files changed

+118
-56
lines changed

src/modules/portable/__init__.py

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,39 +20,40 @@
2020
# CDDL HEADER END
2121
#
2222
# Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
23+
# Copyright 2023 OmniOS Community Edition (OmniOSce) Association.
2324
#
2425

2526
# The portable module provide access to methods that require operating system-
2627
# specific implementations. The module initialization logic selects the right
2728
# implementation the module is loaded. The module methods then
28-
# delegate to the implementation class object.
29+
# delegate to the implementation class object.
2930
#
3031
# The documentation for the methods is provided in this module. To support
3132
# another operating system, each of these methods must be implemented by the
32-
# class for that operating system even if it is effectively a no-op.
33+
# class for that operating system even if it is effectively a no-op.
3334
#
3435
# The module and class must be named using os_[impl], where
3536
# [impl] corresponds to the OS distro, name, or type of OS
3637
# the class implements. For example, to add specific support
3738
# for mandrake linux (above and beyond existing support for
3839
# generic unix), one would create os_mandrake.py.
39-
#
40+
#
4041
# The following high-level groups of methods are defined in this module:
41-
#
42+
#
4243
# - Platform Attribute Methods: These methods give access to
4344
# attributes of the underlying platform not available through
4445
# existing python libraries. For example, the list of implemented
4546
# ISAs of a given platform.
46-
#
47+
#
4748
# - Account access: Retrieval of account information (users and
4849
# groups), in some cases for dormant, relocated OS images.
49-
#
50+
#
5051
# - Miscellaneous filesystem operations: common operations that
5152
# differ in implementation or are only available on a subset
52-
# of OS or filesystem implementations, such as chown() or rename().
53+
# of OS or filesystem implementations, such as chown() or rename().
5354

54-
# This module exports the methods defined below. They are defined here as
55-
# not implemented to avoid pylint errors. The included OS-specific module
55+
# This module exports the methods defined below. They are defined here as
56+
# not implemented to avoid pylint errors. The included OS-specific module
5657
# redefines the methods with an OS-specific implementation.
5758

5859
# Platform Methods
@@ -67,31 +68,37 @@ def get_release():
6768
must be a dot-separated set of integers (i.e. no alphabetic
6869
or punctuation)."""
6970
raise NotImplementedError
70-
71+
7172
def get_platform():
7273
""" Return a string representing the current hardware model
7374
information, e.g. "i86pc"."""
7475
raise NotImplementedError
7576

76-
def get_file_type(actions):
77-
""" Return a list containing the file type for each file in paths."""
77+
def get_file_type(path):
78+
""" Return a value indicating the type of file found at path.
79+
The return value is one of file type constants defined below."""
80+
raise NotImplementedError
81+
82+
def get_actions_file_type(actions):
83+
""" Return an iterator or list containing the file type for each file
84+
in the list of provided actions."""
7885
raise NotImplementedError
7986

8087
# Account access
8188
# --------------
8289
def get_group_by_name(name, dirpath, use_file):
8390
""" Return the group ID for a group name.
8491
If use_file is true, an OS-specific file from within the file tree
85-
rooted by dirpath will be consulted, if it exists. Otherwise, the
92+
rooted by dirpath will be consulted, if it exists. Otherwise, the
8693
group ID is retrieved from the operating system.
87-
Exceptions:
94+
Exceptions:
8895
KeyError if the specified group does not exist"""
8996
raise NotImplementedError
9097

9198
def get_user_by_name(name, dirpath, use_file):
9299
""" Return the user ID for a user name.
93100
If use_file is true, an OS-specific file from within the file tree
94-
rooted by dirpath will be consulted, if it exists. Otherwise, the
101+
rooted by dirpath will be consulted, if it exists. Otherwise, the
95102
user ID is retrieved from the operating system.
96103
Exceptions:
97104
KeyError if the specified group does not exist"""
@@ -100,7 +107,7 @@ def get_user_by_name(name, dirpath, use_file):
100107
def get_name_by_gid(gid, dirpath, use_file):
101108
""" Return the group name for a group ID.
102109
If use_file is true, an OS-specific file from within the file tree
103-
rooted by dirpath will be consulted, if it exists. Otherwise, the
110+
rooted by dirpath will be consulted, if it exists. Otherwise, the
104111
group name is retrieved from the operating system.
105112
Exceptions:
106113
KeyError if the specified group does not exist"""
@@ -109,7 +116,7 @@ def get_name_by_gid(gid, dirpath, use_file):
109116
def get_name_by_uid(uid, dirpath, use_file):
110117
""" Return the user name for a user ID.
111118
If use_file is true, an OS-specific file from within the file tree
112-
rooted by dirpath will be consulted, if it exists. Otherwise, the
119+
rooted by dirpath will be consulted, if it exists. Otherwise, the
113120
user name is retrieved from the operating system.
114121
Exceptions:
115122
KeyError if the specified group does not exist"""
@@ -144,7 +151,7 @@ def chown(path, owner, group):
144151
""" Change ownership of a file in an OS-specific way.
145152
The owner and group ownership information should be applied to
146153
the given file, if applicable on the current runtime OS.
147-
Exceptions:
154+
Exceptions:
148155
EnvironmentError (or subclass) if the path does not exist
149156
or ownership cannot be changed"""
150157
raise NotImplementedError
@@ -167,7 +174,7 @@ def link(src, dst):
167174
def remove(path):
168175
""" Remove the given file in an OS-specific way
169176
Exceptions:
170-
OSError (or subclass) if the source path does not exist or
177+
OSError (or subclass) if the source path does not exist or
171178
the file cannot be removed"""
172179
raise NotImplementedError
173180

@@ -183,7 +190,7 @@ def copyfile(src, dst):
183190
raise NotImplementedError
184191

185192
def split_path(path):
186-
""" Splits a path and gives back the components of the path.
193+
""" Splits a path and gives back the components of the path.
187194
This is intended to hide platform-specific details about splitting
188195
a path into its components. This interface is similar to
189196
os.path.split() except that the entire path is split, not just
@@ -195,7 +202,7 @@ def split_path(path):
195202
raise NotImplementedError
196203

197204
def get_root(path):
198-
""" Returns the 'root' of the given path.
205+
""" Returns the 'root' of the given path.
199206
This should include any and all components of a path up to the first
200207
non-platform-specific component. For example, on Windows,
201208
it should include the drive letter prefix.
@@ -208,7 +215,7 @@ def get_root(path):
208215
def assert_mode(path, mode):
209216
""" Checks that the file identified by path has the given mode to
210217
the extent possible by the host operating system. Otherwise raises
211-
an AssertionError where the mode attribute of the assertion is the
218+
an AssertionError where the mode attribute of the assertion is the
212219
mode of the file."""
213220
raise NotImplementedError
214221

@@ -231,7 +238,8 @@ def get_sysattr_dict():
231238

232239
# File type constants
233240
# -------------------
234-
ELF, EXEC, UNFOUND, SMF_MANIFEST = range(0, 4)
241+
ELF, EXEC, UNFOUND, SMF_MANIFEST, XMLDOC, EMPTYFILE, NOTFILE, UNKNOWN = \
242+
range(0, 8)
235243

236244
# String to be used for an action attribute created for the internal use of
237245
# dependency analysis.
@@ -267,7 +275,7 @@ def get_sysattr_dict():
267275

268276
# try the most-specific module name first (e.g. os_suse),
269277
# then try the more generic OS Name module (e.g. os_linux),
270-
# then the OS type module (e.g. os_unix)
278+
# then the OS type module (e.g. os_unix)
271279
try:
272280
exec('from .{0} import *'.format(modname))
273281
break

src/modules/portable/os_sunos.py

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
get_group_by_name, get_user_by_name, get_name_by_gid, get_name_by_uid, \
4040
get_usernames_by_gid, is_admin, get_userid, get_username, chown, rename, \
4141
remove, link, copyfile, split_path, get_root, assert_mode
42-
from pkg.portable import ELF, EXEC, PD_LOCAL_PATH, UNFOUND, SMF_MANIFEST
42+
from pkg.portable import PD_LOCAL_PATH, \
43+
ELF, EXEC, UNFOUND, SMF_MANIFEST, XMLDOC, EMPTYFILE, NOTFILE, UNKNOWN
4344

4445
import pkg.arch as arch
4546
from pkg.sysattr import fgetattr, fsetattr
@@ -54,32 +55,42 @@ def get_release():
5455
def get_platform():
5556
return arch.get_platform()
5657

57-
def get_file_type(actions):
58+
def get_file_type(path):
5859
from pkg.flavor.smf_manifest import is_smf_manifest
59-
for a in actions:
60-
lpath = a.attrs[PD_LOCAL_PATH]
61-
if os.stat(lpath).st_size == 0:
62-
# Some tests rely on this being identified
63-
yield "empty file"
64-
continue
65-
try:
66-
with open(lpath, 'rb') as f:
67-
magic = f.read(4)
68-
except FileNotFoundError:
69-
yield UNFOUND
70-
continue
71-
if magic == b'\x7fELF':
72-
yield ELF
73-
elif magic[:2] == b'#!':
74-
yield EXEC
75-
elif lpath.endswith('.xml'):
76-
if is_smf_manifest(lpath):
77-
yield SMF_MANIFEST
78-
else:
79-
# Some tests rely on this type being identified
80-
yield "XML document"
60+
61+
if not os.path.isfile(path):
62+
return NOTFILE
63+
64+
try:
65+
# Some tests rely on this being identified
66+
if os.stat(path).st_size == 0:
67+
return EMPTYFILE
68+
with open(path, 'rb') as f:
69+
magic = f.read(4)
70+
except FileNotFoundError:
71+
return UNFOUND
72+
except OSError:
73+
# Most likely EPERM
74+
return UNKNOWN
75+
76+
if magic == b'\x7fELF':
77+
return ELF
78+
79+
if magic[:2] == b'#!':
80+
return EXEC
81+
82+
if path.endswith('.xml'):
83+
if is_smf_manifest(path):
84+
return SMF_MANIFEST
8185
else:
82-
yield "unknown"
86+
# Some tests rely on this type being identified
87+
return XMLDOC
88+
89+
return UNKNOWN
90+
91+
def get_actions_file_type(actions):
92+
for a in actions:
93+
yield get_file_type(a.attrs[PD_LOCAL_PATH])
8394

8495
# Vim hints
8596
# vim:ts=8:sw=8:et:fdm=marker

src/modules/publish/dependencies.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ def list_implicit_deps_for_manifest(mfst, proto_dirs, pkg_vars, dyn_tok_conv,
413413
warnings = []
414414
pkg_attrs = {}
415415
act_list = list(mfst.gen_actions_by_type("file"))
416-
file_types = portable.get_file_type(act_list)
416+
file_types = portable.get_actions_file_type(act_list)
417417
var_dict = dict()
418418

419419
# Collect all variants that are used and not declared and emit a warning
@@ -1616,6 +1616,38 @@ def prune_debug_attrs(action):
16161616
if not k.startswith(base.Dependency.DEPEND_DEBUG_PREFIX))
16171617
return actions.depend.DependencyAction(**attrs)
16181618

1619+
# In order to resolve dependencies, we build a mapping of all files and
1620+
# symlinks delivered by installed packages. To reduce the size of that working
1621+
# set we apply some pre-filters to discard files which can never be a
1622+
# dependency target, either intrinsically (like a man page or image file), or
1623+
# because we don't implement a dependency parser for it (e.g. perl).
1624+
1625+
skip_prefix = (
1626+
'usr/share/man',
1627+
'opt/ooce/share/man',
1628+
)
1629+
1630+
skip_suffix = (
1631+
'.json', '.toml', '.txt', '.html',
1632+
'.mf', '.p5m',
1633+
'.png', '.jpg', '.gif',
1634+
'.pdf',
1635+
'.pl', '.pm',
1636+
'.h', '.c',
1637+
'.rs', '.go', '.js', '.d', '.lua', '.zig', '.rb', '.m4',
1638+
'.gz', '.zip',
1639+
'.cmake',
1640+
'.rst',
1641+
'.mo',
1642+
'.vim',
1643+
'.elc',
1644+
'.hpp',
1645+
# texinfo
1646+
'.tex', '.eps', '.ltx', '.def', '.md', '.ins', '.otf', '.tikz', '.dtx',
1647+
'.afm', '.fd', '.enc', '.sty', '.pfb', '.htf', '.vf', '.tfm',
1648+
1649+
)
1650+
16191651
def add_fmri_path_mapping(files_dict, links_dict, pfmri, mfst,
16201652
distro_vars=None, use_template=False):
16211653
"""Add mappings from path names to FMRIs and variants.
@@ -1639,13 +1671,22 @@ def add_fmri_path_mapping(files_dict, links_dict, pfmri, mfst,
16391671
dictionaries with VariantCombinationTemplates instead of
16401672
VariantCombinations."""
16411673

1674+
def filter(action):
1675+
path = action.attrs['path']
1676+
if path.startswith(skip_prefix):
1677+
return True
1678+
if path.endswith(skip_suffix):
1679+
return True
1680+
return False
1681+
16421682
assert not distro_vars or not use_template
16431683
if not use_template:
16441684
pvariants = mfst.get_all_variants()
16451685
if distro_vars:
16461686
pvariants.merge_unknown(distro_vars)
16471687

16481688
for f in mfst.gen_actions_by_type("file"):
1689+
if filter(f): continue
16491690
vc = f.get_variant_template()
16501691
if not use_template:
16511692
vc.merge_unknown(pvariants)
@@ -1655,6 +1696,7 @@ def add_fmri_path_mapping(files_dict, links_dict, pfmri, mfst,
16551696
(pfmri, vc))
16561697
for f in itertools.chain(mfst.gen_actions_by_type("hardlink"),
16571698
mfst.gen_actions_by_type("link")):
1699+
if filter(f): continue
16581700
vc = f.get_variant_template()
16591701
if not use_template:
16601702
vc.merge_unknown(pvariants)
@@ -1784,6 +1826,7 @@ def __merge_actvct_with_pkgvct(act_vct, pkg_vct):
17841826
]
17851827
files.installed[pth] = new_val
17861828
del tmp_files
1829+
17871830
# Populate the link dictionary using the installed packages'
17881831
# information.
17891832
for pth, l in six.iteritems(tmp_links):

src/tests/api/t_dependencies.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2024,7 +2024,7 @@ def _check_all_res(res):
20242024
if len(ms) != 1:
20252025
raise RuntimeError("Didn't get expected types of "
20262026
"missing files:\n{0}".format(ms))
2027-
self.assertEqual(list(ms.keys())[0], "empty file")
2027+
self.assertEqual(list(ms.keys())[0], portable.EMPTYFILE)
20282028
self.assertTrue(len(d_map) == 0)
20292029

20302030
# This should find the binary file first and thus produce
@@ -2263,7 +2263,7 @@ def test_broken_manifest(self):
22632263

22642264
# as it happens, file(1) isn't good at spotting broken
22652265
# XML documents, it only sniffs the header - so this file
2266-
# gets reported as an 'XML document' despite it being invalid
2266+
# gets reported as an XMLDOC despite it being invalid
22672267
# XML.
22682268
t_path = self.make_manifest(self.broken_smf_manf)
22692269
self.make_smf_test_files()
@@ -2275,14 +2275,14 @@ def test_broken_manifest(self):
22752275
self.assertEqual(len(ms), 1, "No unknown files reported during "
22762276
"analysis")
22772277

2278-
if "XML document" not in ms:
2278+
if portable.XMLDOC not in ms:
22792279
self.assertTrue(False, "Broken SMF manifest file not"
22802280
" declared")
22812281

22822282
broken_path = os.path.join(self.proto_dir, self.paths["broken"])
2283-
self.assertEqual(ms["XML document"], broken_path,
2283+
self.assertEqual(ms[portable.XMLDOC], broken_path,
22842284
"Did not detect broken SMF manifest file: {0} != {1}".format(
2285-
broken_path, ms["XML document"]))
2285+
broken_path, ms[portable.XMLDOC]))
22862286

22872287
# We should still be able to resolve the other dependencies
22882288
# though and it's important to check that the one broken SMF

src/tests/cli/t_pkgdep.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ def make_full_res_manf_1(self, proto_area, reason, include_os=False):
217217
depend {pfx}.file=syslog {pfx}.path=var/log fmri={dummy_fmri} type=require {pfx}.reason=usr/foo {pfx}.type=hardlink
218218
""".format(pfx=base.Dependency.DEPEND_DEBUG_PREFIX, dummy_fmri=base.Dependency.DUMMY_FMRI)
219219

220-
res_manf_2_missing = "unknown"
220+
res_manf_2_missing = str(portable.UNKNOWN)
221221

222222
resolve_error = """\
223223
{manf_path} has unresolved dependency '

0 commit comments

Comments
 (0)