Skip to content

Commit de63a46

Browse files
Martin Hatinaj-mracek
authored andcommitted
introduce easy installation of specs
1 parent 32fdc9a commit de63a46

File tree

8 files changed

+160
-32
lines changed

8 files changed

+160
-32
lines changed

dnf/base.py

Lines changed: 107 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
from __future__ import print_function
2525
from __future__ import unicode_literals
2626

27+
import argparse
28+
2729
import libdnf.transaction
2830

2931
from dnf.comps import CompsQuery
@@ -32,7 +34,7 @@
3234
from dnf.module.metadata_loader import ModuleMetadataLoader
3335
from dnf.module.repo_module_dict import RepoModuleDict
3436
from dnf.module.repo_module_version import RepoModuleVersion
35-
from dnf.util import first
37+
from dnf.util import _parse_specs
3638
from dnf.db.history import SwdbInterface
3739
from dnf.yum import misc
3840
from functools import reduce
@@ -113,6 +115,7 @@ def __init__(self, conf=None):
113115
self._allow_erasing = False
114116
self._repo_set_imported_gpg_keys = set()
115117
self.repo_module_dict = RepoModuleDict(self)
118+
self.output = None
116119

117120
def __enter__(self):
118121
return self
@@ -577,6 +580,8 @@ def reset(self, sack=False, repos=False, goal=False):
577580
self._goal = None
578581
if self._sack is not None:
579582
self._goal = dnf.goal.Goal(self._sack)
583+
if self._module_persistor is not None:
584+
self._module_persistor.reset()
580585
self.history.close()
581586
self._comps_trans = dnf.comps.TransactionBunch()
582587
self._transaction = None
@@ -1581,13 +1586,13 @@ def reason_fn(pkgname):
15811586

15821587
return dnf.comps.Solver(self.history, self._comps, reason_fn)
15831588

1584-
def environment_install(self, env_id, types, exclude=None, strict=True):
1589+
def environment_install(self, env_id, types, exclude=None, strict=True, exclude_groups=None):
15851590
assert dnf.util.is_string_type(env_id)
15861591
solver = self._build_comps_solver()
15871592
types = self._translate_comps_pkg_types(types)
15881593
trans = dnf.comps.install_or_skip(solver._environment_install,
15891594
env_id, types, exclude or set(),
1590-
strict)
1595+
strict, exclude_groups)
15911596
if not trans:
15921597
return 0
15931598
return self._add_comps_trans(trans)
@@ -1643,7 +1648,7 @@ def _pattern_to_pkgname(pattern):
16431648
grp_id, trans.install)
16441649
return self._add_comps_trans(trans)
16451650

1646-
def env_group_install(self, patterns, types, strict=True):
1651+
def env_group_install(self, patterns, types, strict=True, exclude=None, exclude_groups=None):
16471652
q = CompsQuery(self.comps, self.history,
16481653
CompsQuery.ENVIRONMENTS | CompsQuery.GROUPS,
16491654
CompsQuery.AVAILABLE | CompsQuery.INSTALLED)
@@ -1657,9 +1662,10 @@ def env_group_install(self, patterns, types, strict=True):
16571662
done = False
16581663
continue
16591664
for group_id in res.groups:
1660-
cnt += self.group_install(group_id, types, strict=strict)
1665+
cnt += self.group_install(group_id, types, exclude=exclude, strict=strict)
16611666
for env_id in res.environments:
1662-
cnt += self.environment_install(env_id, types, strict=strict)
1667+
cnt += self.environment_install(env_id, types, exclude=exclude, strict=strict,
1668+
exclude_groups=exclude_groups)
16631669
if not done and strict:
16641670
raise dnf.exceptions.Error(_('Nothing to do.'))
16651671
return cnt
@@ -1754,6 +1760,10 @@ def _install_multiarch(self, query, reponame=None, strict=True):
17541760
self._goal.install(select=sltr, optional=(not strict))
17551761
return len(available)
17561762

1763+
def enable_module(self, specs, save_immediately=False):
1764+
for spec in specs:
1765+
self.repo_module_dict.enable(spec, save_immediately)
1766+
17571767
def install_module(self, specs, strict=True):
17581768
"""
17591769
Install module based on provided specs
@@ -1763,6 +1773,97 @@ def install_module(self, specs, strict=True):
17631773
"""
17641774
return self.repo_module_dict.install(specs, strict)
17651775

1776+
def _categorize_specs(self, install, exclude):
1777+
"""
1778+
Categorize :param install and :param exclude list into two groups each (packages and groups)
1779+
1780+
:param install: list of specs, whether packages ('foo') or groups/modules ('@bar')
1781+
:param exclude: list of specs, whether packages ('foo') or groups/modules ('@bar')
1782+
:return: categorized install and exclude specs (stored in argparse.Namespace class)
1783+
1784+
To access packages use: specs.pkg_specs,
1785+
to access groups use: specs.grp_specs
1786+
"""
1787+
install_specs = argparse.Namespace()
1788+
exclude_specs = argparse.Namespace()
1789+
_parse_specs(install_specs, install)
1790+
_parse_specs(exclude_specs, exclude)
1791+
1792+
return install_specs, exclude_specs
1793+
1794+
def _exclude_package_specs(self, exclude_specs):
1795+
glob_excludes = [exclude for exclude in exclude_specs.pkg_specs
1796+
if dnf.util.is_glob_pattern(exclude)]
1797+
excludes = [exclude for exclude in exclude_specs.pkg_specs
1798+
if exclude not in glob_excludes]
1799+
1800+
exclude_query = self.sack.query().filter(name=excludes)
1801+
glob_exclude_query = self.sack.query().filter(name__glob=glob_excludes)
1802+
1803+
self.sack.add_excludes(exclude_query)
1804+
self.sack.add_excludes(glob_exclude_query)
1805+
1806+
def _exclude_groups(self, group_specs):
1807+
group_excludes = []
1808+
1809+
for group_spec in group_specs:
1810+
if '/' in group_spec:
1811+
split = group_spec.split('/')
1812+
group_spec = split[0]
1813+
1814+
environment = self.comps.environment_by_pattern(group_spec)
1815+
if environment:
1816+
for group in environment.groups_iter():
1817+
for pkg in group.packages_iter():
1818+
group_excludes.append(pkg.name)
1819+
else:
1820+
group = self.comps.group_by_pattern(group_spec)
1821+
if not group:
1822+
continue
1823+
1824+
for pkg in group.packages_iter():
1825+
group_excludes.append(pkg.name)
1826+
1827+
exclude_query = self.sack.query().filter(name=group_excludes)
1828+
self.sack.add_excludes(exclude_query)
1829+
1830+
def _install_groups(self, group_specs, excludes, skipped, strict=True):
1831+
for group_spec in group_specs:
1832+
try:
1833+
types = self.conf.group_package_types
1834+
1835+
if '/' in group_spec:
1836+
split = group_spec.split('/')
1837+
group_spec = split[0]
1838+
types = split[1].split(',')
1839+
1840+
self.env_group_install([group_spec], types, strict, excludes.pkg_specs,
1841+
excludes.grp_specs)
1842+
except dnf.exceptions.Error:
1843+
skipped.append("@" + group_spec)
1844+
1845+
def install_specs(self, install, exclude=None, reponame=None, strict=True, forms=None):
1846+
if exclude is None:
1847+
exclude = []
1848+
1849+
skipped = []
1850+
install_specs, exclude_specs = self._categorize_specs(install, exclude)
1851+
1852+
self._exclude_package_specs(exclude_specs)
1853+
for spec in install_specs.pkg_specs:
1854+
try:
1855+
self.install(spec, reponame=reponame, strict=strict, forms=forms)
1856+
except dnf.exceptions.Error:
1857+
skipped.append(spec)
1858+
1859+
groups = self.install_module(install_specs.grp_specs, strict)
1860+
1861+
self.read_comps(arch_filter=True)
1862+
self._exclude_groups(exclude_specs.grp_specs)
1863+
self._install_groups(groups, exclude_specs, skipped, strict)
1864+
1865+
return skipped
1866+
17661867
def install(self, pkg_spec, reponame=None, strict=True, forms=None):
17671868
# :api
17681869
"""Mark package(s) given by pkg_spec and reponame for installation."""

dnf/cli/option_parser.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from __future__ import unicode_literals
2222
from dnf.i18n import _
23+
from dnf.util import _parse_specs
2324

2425
import argparse
2526
import dnf.exceptions
@@ -118,19 +119,7 @@ def __call__(self, parser, namespace, values, opt_str):
118119

119120
class ParseSpecGroupFileCallback(argparse.Action):
120121
def __call__(self, parser, namespace, values, opt_str):
121-
setattr(namespace, "filenames", [])
122-
setattr(namespace, "grp_specs", [])
123-
setattr(namespace, "pkg_specs", [])
124-
for value in values:
125-
schemes = dnf.pycomp.urlparse.urlparse(value)[0]
126-
if value.endswith('.rpm'):
127-
namespace.filenames.append(value)
128-
elif schemes and schemes in ('http', 'ftp', 'file', 'https'):
129-
namespace.filenames.append(value)
130-
elif value.startswith('@'):
131-
namespace.grp_specs.append(value[1:])
132-
else:
133-
namespace.pkg_specs.append(value)
122+
_parse_specs(namespace, values)
134123

135124
class PkgNarrowCallback(argparse.Action):
136125
def __init__(self, *args, **kwargs):

dnf/comps.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,14 @@ def _fn_display_order(group):
8383

8484

8585
def install_or_skip(install_fnc, grp_or_env_id, types, exclude=None,
86-
strict=True):
86+
strict=True, exclude_groups=None):
8787
"""Either mark in persistor as installed given `grp_or_env` (group
8888
or environment) or skip it (if it's already installed).
8989
`install_fnc` has to be Solver._group_install
9090
or Solver._environment_install.
9191
"""
9292
try:
93-
return install_fnc(grp_or_env_id, types, exclude, strict)
93+
return install_fnc(grp_or_env_id, types, exclude, strict, exclude_groups)
9494
except dnf.comps.CompsError as e:
9595
logger.warning("%s, %s", ucd(e)[:-1], _("skipping."))
9696

@@ -550,18 +550,22 @@ def _removable_grp(self, group_id):
550550
assert dnf.util.is_string_type(group_id)
551551
return self.history.env.is_removable_group(group_id)
552552

553-
def _environment_install(self, env_id, pkg_types, exclude, strict=True):
553+
def _environment_install(self, env_id, pkg_types, exclude, strict=True, exclude_groups=None):
554554
assert dnf.util.is_string_type(env_id)
555555
comps_env = self.comps._environment_by_id(env_id)
556556
swdb_env = self.history.env.new(env_id, comps_env.name, comps_env.ui_name, pkg_types)
557557
self.history.env.install(swdb_env)
558558

559559
trans = TransactionBunch()
560560
for comps_group in comps_env.mandatory_groups:
561+
if exclude_groups and comps_group in exclude_groups:
562+
continue
561563
trans += self._group_install(comps_group.id, pkg_types, exclude, strict)
562564
swdb_env.addGroup(comps_group.id, True, MANDATORY)
563565

564566
for comps_group in comps_env.optional_groups:
567+
if exclude_groups and comps_group in exclude_groups:
568+
continue
565569
swdb_env.addGroup(comps_group.id, False, OPTIONAL)
566570
# TODO: if a group is already installed, mark it as installed?
567571
return trans
@@ -611,7 +615,7 @@ def _environment_upgrade(self, env_id):
611615
self.history.env.upgrade(swdb_env)
612616
return trans
613617

614-
def _group_install(self, group_id, pkg_types, exclude=None, strict=True):
618+
def _group_install(self, group_id, pkg_types, exclude=None, strict=True, exclude_groups=None):
615619
assert dnf.util.is_string_type(group_id)
616620
comps_group = self.comps._group_by_id(group_id)
617621
if not comps_group:

dnf/module/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ def __init__(self, module_spec):
5151
super(EnabledStreamException, self).__init__(value)
5252

5353

54+
class InstallMultipleStreamsException(dnf.exceptions.Error):
55+
def __init__(self, module_spec):
56+
value = "Cannot install more streams from module '{}' at the same time".format(module_spec)
57+
super(InstallMultipleStreamsException, self).__init__(value)
58+
59+
5460
class DifferentStreamEnabledException(dnf.exceptions.Error):
5561
def __init__(self, module_spec):
5662
value = "Different stream enabled for module: {}".format(module_spec)

dnf/module/repo_module.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ def enable(self, stream, assumeyes=False):
8080
if self.conf.enabled._get() and self.conf.stream._get() == stream:
8181
return
8282

83+
if not self.parent.base.conf.assumeno and not self.parent.base.output:
84+
assumeyes = True
85+
8386
if self.conf.stream._get() is not "" and \
8487
str(self.conf.stream._get()) != str(stream) and \
8588
not assumeyes:
@@ -92,7 +95,8 @@ def enable(self, stream, assumeyes=False):
9295
else:
9396
raise EnabledStreamException("{}:{}".format(self.name, stream))
9497

95-
self.parent.base._module_persistor.set_data(self, stream=stream, enabled=True)
98+
self.parent.base._module_persistor.set_data(self, stream=stream, enabled=True,
99+
version=-1, profiles=[])
96100

97101
def disable(self):
98102
self.parent.base._module_persistor.set_data(self, enabled=False, profiles=[])

dnf/module/repo_module_dict.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from dnf.module.exceptions import NoStreamSpecifiedException, NoModuleException, \
2828
EnabledStreamException, ProfileNotInstalledException, NoProfileToRemoveException, \
2929
VersionLockedException, CannotLockVersionException, \
30-
DifferentStreamEnabledException
30+
DifferentStreamEnabledException, InstallMultipleStreamsException
3131
from dnf.module.repo_module import RepoModule
3232
from dnf.module.subject import ModuleSubject
3333
from dnf.selector import Selector
@@ -339,9 +339,11 @@ def get_best_versions(self, module_specs):
339339
skipped.append(module_spec)
340340
continue
341341

342-
key = "{}:{}".format(module_version.name, module_version.stream)
342+
key = module_version.name
343343
if key in best_versions:
344344
best_version, profiles, default_profiles = best_versions[key]
345+
if best_version.stream != module_version.stream:
346+
raise InstallMultipleStreamsException(module_version.name)
345347

346348
if module_form.profile:
347349
profiles.append(module_form.profile)

dnf/util.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,34 @@
4747
"""DNF Utilities."""
4848

4949

50+
def _parse_specs(namespace, values):
51+
"""
52+
Categorize :param values list into packages, groups and filenames
53+
54+
:param namespace: argparse.Namespace, where specs will be stored
55+
:param values: list of specs, whether packages ('foo') or groups/modules ('@bar')
56+
or filenames ('*.rmp', 'http://*', ...)
57+
58+
To access packages use: specs.pkg_specs,
59+
to access groups use: specs.grp_specs,
60+
to access filenames use: specs.filenames
61+
"""
62+
63+
setattr(namespace, "filenames", [])
64+
setattr(namespace, "grp_specs", [])
65+
setattr(namespace, "pkg_specs", [])
66+
for value in values:
67+
schemes = dnf.pycomp.urlparse.urlparse(value)[0]
68+
if value.endswith('.rpm'):
69+
namespace.filenames.append(value)
70+
elif schemes and schemes in ('http', 'ftp', 'file', 'https'):
71+
namespace.filenames.append(value)
72+
elif value.startswith('@'):
73+
namespace.grp_specs.append(value[1:])
74+
else:
75+
namespace.pkg_specs.append(value)
76+
77+
5078
def _non_repo_handle(conf=None):
5179
handle = librepo.Handle()
5280
handle.useragent = dnf.const.USER_AGENT

tests/modules/etc/dnf/repos.d/boltron.repo

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)