Skip to content

Commit

Permalink
add optimization criterion to prefer arch over noarch when otherwise … (
Browse files Browse the repository at this point in the history
conda#8627)

add optimization criterion to prefer arch over noarch when otherwise …
  • Loading branch information
msarahan authored May 9, 2019
2 parents 006e711 + 8d0908c commit 1e13462
Show file tree
Hide file tree
Showing 2 changed files with 239 additions and 18 deletions.
42 changes: 28 additions & 14 deletions conda/resolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,10 +631,11 @@ def version_key(self, prec, vtype=None):
version_comparator = VersionOrder(prec.get('version', ''))
build_number = prec.get('build_number', 0)
build_string = prec.get('build')
noarch = - int(prec.subdir == 'noarch')
if self._channel_priority != ChannelPriority.DISABLED:
vkey = [valid, -channel_priority, version_comparator, build_number]
vkey = [valid, -channel_priority, version_comparator, build_number, noarch]
else:
vkey = [valid, version_comparator, -channel_priority, build_number]
vkey = [valid, version_comparator, -channel_priority, build_number, noarch]
if self._solver_ignore_timestamps:
vkey.append(build_string)
else:
Expand Down Expand Up @@ -794,6 +795,7 @@ def generate_version_metrics(self, C, specs, include0=False):
eqc = {} # channel
eqv = {} # version
eqb = {} # build number
eqa = {} # arch/noarch
eqt = {} # timestamp

sdict = {} # Dict[package_name, PackageRecord]
Expand All @@ -819,20 +821,24 @@ def generate_version_metrics(self, C, specs, include0=False):
if targets and any(prec == t for t in targets):
continue
if pkey is None:
ic = iv = ib = it = 0
ic = iv = ib = it = ia = 0
# valid package, channel priority
elif pkey[0] != version_key[0] or pkey[1] != version_key[1]:
ic += 1
iv = ib = it = 0
iv = ib = it = ia = 0
# version
elif pkey[2] != version_key[2]:
iv += 1
ib = it = 0
ib = it = ia = 0
# build number
elif pkey[3] != version_key[3]:
ib += 1
it = ia = 0
# arch/noarch
elif pkey[4] != version_key[4]:
ia += 1
it = 0
elif not self._solver_ignore_timestamps and pkey[4] != version_key[4]:
elif not self._solver_ignore_timestamps and pkey[5] != version_key[5]:
it += 1

prec_sat_name = self.to_sat_name(prec)
Expand All @@ -842,11 +848,13 @@ def generate_version_metrics(self, C, specs, include0=False):
eqv[prec_sat_name] = iv
if ib or include0:
eqb[prec_sat_name] = ib
if ia or include0:
eqa[prec_sat_name] = ia
if it or include0:
eqt[prec_sat_name] = it
pkey = version_key

return eqc, eqv, eqb, eqt
return eqc, eqv, eqb, eqa, eqt

def dependency_sort(self, must_have):
# type: (Dict[package_name, PackageRecord]) -> List[PackageRecord]
Expand Down Expand Up @@ -1133,7 +1141,7 @@ def is_converged(solution):

# Requested packages: maximize versions
log.debug("Solve: maximize versions of requested packages")
eq_req_c, eq_req_v, eq_req_b, eq_req_t = r2.generate_version_metrics(C, specr)
eq_req_c, eq_req_v, eq_req_b, eq_req_a, eq_req_t = r2.generate_version_metrics(C, specr)
solution, obj3a = C.minimize(eq_req_c, solution)
solution, obj3 = C.minimize(eq_req_v, solution)
log.debug('Initial package channel/version metric: %d/%d', obj3a, obj3)
Expand All @@ -1160,6 +1168,11 @@ def is_converged(solution):
solution, obj4 = C.minimize(eq_req_b, solution)
log.debug('Initial package build metric: %d', obj4)

# prefer arch packages where available for requested specs
log.debug("Solve: prefer arch over noarch for requested packages")
solution, noarch_obj = C.minimize(eq_req_a, solution)
log.debug('Noarch metric: %d', noarch_obj)

# Optional installations: minimize count
if not _remove:
log.debug("Solve: minimize number of optional installations")
Expand All @@ -1174,22 +1187,23 @@ def is_converged(solution):
log.debug('Dependency update count: %d', obj50)

# Remaining packages: maximize versions, then builds
log.debug("Solve: maximize versions and builds of indirect dependencies")
eq_c, eq_v, eq_b, eq_t = r2.generate_version_metrics(C, speca)
log.debug("Solve: maximize versions and builds of indirect dependencies. "
"Prefer arch over noarch where equivalent.")
eq_c, eq_v, eq_b, eq_a, eq_t = r2.generate_version_metrics(C, speca)
solution, obj5a = C.minimize(eq_c, solution)
solution, obj5 = C.minimize(eq_v, solution)
solution, obj6 = C.minimize(eq_b, solution)
log.debug('Additional package channel/version/build metrics: %d/%d/%d',
obj5a, obj5, obj6)
solution, obj6a = C.minimize(eq_a, solution)
log.debug('Additional package channel/version/build/noarch metrics: %d/%d/%d/%d',
obj5a, obj5, obj6, obj6a)

# Prune unnecessary packages
log.debug("Solve: prune unnecessary packages")
eq_c = r2.generate_package_count(C, specm)
solution, obj7 = C.minimize(eq_c, solution, trymax=True)
log.debug('Weak dependency count: %d', obj7)

converged = is_converged(solution)
if not converged:
if not is_converged(solution):
# Maximize timestamps
eq_t.update(eq_req_t)
solution, obj6t = C.minimize(eq_t, solution)
Expand Down
215 changes: 211 additions & 4 deletions tests/test_resolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ def test_generate_eq_1():
reduced_index = r.get_reduced_index((MatchSpec('anaconda'), ))
r2 = Resolve(reduced_index, True)
C = r2.gen_clauses()
eqc, eqv, eqb, eqt = r2.generate_version_metrics(C, list(r2.groups.keys()))
eqc, eqv, eqb, eqa, eqt = r2.generate_version_metrics(C, list(r2.groups.keys()))
# Should satisfy the following criteria:
# - lower versions of the same package should should have higher
# coefficients.
Expand Down Expand Up @@ -1160,7 +1160,7 @@ def test_channel_priority_2():
dists = this_r.get_reduced_index(spec)
r2 = Resolve(dists, True, channels=channels)
C = r2.gen_clauses()
eqc, eqv, eqb, eqt = r2.generate_version_metrics(C, list(r2.groups.keys()))
eqc, eqv, eqb, eqa, eqt = r2.generate_version_metrics(C, list(r2.groups.keys()))
eqc = {key: value for key, value in iteritems(eqc)}
pprint(eqc)
assert eqc == {
Expand Down Expand Up @@ -1312,7 +1312,7 @@ def test_channel_priority_2():
r2 = Resolve(dists, True, channels=channels)
C = r2.gen_clauses()

eqc, eqv, eqb, eqt = r2.generate_version_metrics(C, list(r2.groups.keys()))
eqc, eqv, eqb, eqa, eqt = r2.generate_version_metrics(C, list(r2.groups.keys()))
eqc = {key: value for key, value in iteritems(eqc)}
assert eqc == {}, eqc
installed_w_strict = [prec.dist_str() for prec in this_r.install(spec)]
Expand All @@ -1336,7 +1336,7 @@ def test_channel_priority_2():
dists = this_r.get_reduced_index(spec)
r2 = Resolve(dists, True, channels=channels)
C = r2.gen_clauses()
eqc, eqv, eqb, eqt = r2.generate_version_metrics(C, list(r2.groups.keys()))
eqc, eqv, eqb, eqa, eqt = r2.generate_version_metrics(C, list(r2.groups.keys()))
eqc = {key: value for key, value in iteritems(eqc)}
pprint(eqc)
assert eqc == {
Expand Down Expand Up @@ -1882,3 +1882,210 @@ def test_get_reduced_index_broadening_preferred_solution():
assert d.version == '2.0', "top version should be 2.0, but is {}".format(d.version)
elif d.name == 'bottom':
assert d.version == '2.5', "bottom version should be 2.5, but is {}".format(d.version)


def test_arch_preferred_over_noarch_when_otherwise_equal():
index = (
PackageRecord(**{
"build": "py36_0",
"build_number": 0,
"date": "2016-12-17",
"license": "BSD",
"md5": "9b4568068e3a7ac81be87902827d949e",
"name": "itsdangerous",
"size": 19688,
"version": "0.24"
}),
PackageRecord(**{
"arch": None,
"binstar": {
"channel": "main",
"owner_id": "58596cc93d1b550ffad38672",
"package_id": "5898cb9d9aba4511169c383a"
},
"build": "py_0",
"build_number": 0,
"has_prefix": False,
"license": "BSD",
"machine": None,
"md5": "917e90ca4e80324b77e8df449d07eefc",
"name": "itsdangerous",
"noarch": "python",
"operatingsystem": None,
"platform": None,
"requires": [],
"size": 14098,
"subdir": "noarch",
"target-triplet": "None-any-None",
"version": "0.24"
}),
)
r = Resolve(OrderedDict((prec, prec) for prec in index))
install = r.install(['itsdangerous'])
for d in install:
assert d.subdir == context.subdir


def test_noarch_preferred_over_arch_when_version_greater():
index = (
PackageRecord(**{
'name': 'abc',
'version': '2.0',
'build': '0',
"subdir": "noarch",
'build_number': 0,
}),
PackageRecord(**{
'name': 'abc',
'version': '1.0',
'build': '0',
"subdir": context.subdir,
'build_number': 0,
}),
)
r = Resolve(OrderedDict((prec, prec) for prec in index))
install = r.install(['abc'])
for d in install:
assert d.subdir == 'noarch'
assert d.version == '2.0'


def test_noarch_preferred_over_arch_when_build_greater():
index = (
PackageRecord(**{
'name': 'abc',
'version': '1.0',
'build': '1',
"subdir": "noarch",
'build_number': 1,
}),
PackageRecord(**{
'name': 'abc',
'version': '1.0',
'build': '0',
"subdir": context.subdir,
'build_number': 0,
}),
)
r = Resolve(OrderedDict((prec, prec) for prec in index))
install = r.install(['abc'])
for d in install:
assert d.subdir == 'noarch'
assert d.build_number == 1


def test_arch_preferred_over_noarch_when_otherwise_equal_dep():
index = (
PackageRecord(**{
"build": "py36_0",
"build_number": 0,
"date": "2016-12-17",
"license": "BSD",
"md5": "9b4568068e3a7ac81be87902827d949e",
"name": "itsdangerous",
"size": 19688,
"version": "0.24"
}),
PackageRecord(**{
"arch": None,
"binstar": {
"channel": "main",
"owner_id": "58596cc93d1b550ffad38672",
"package_id": "5898cb9d9aba4511169c383a"
},
"build": "py_0",
"build_number": 0,
"has_prefix": False,
"license": "BSD",
"machine": None,
"md5": "917e90ca4e80324b77e8df449d07eefc",
"name": "itsdangerous",
"noarch": "python",
"operatingsystem": None,
"platform": None,
"requires": [],
"size": 14098,
"subdir": "noarch",
"target-triplet": "None-any-None",
"version": "0.24"
}),
PackageRecord(**{
'name': 'foo',
'version': '1.0',
'build': '0',
"subdir": context.subdir,
'build_number': 0,
'depends': ['itsdangerous'],
}),
)
r = Resolve(OrderedDict((prec, prec) for prec in index))
install = r.install(['foo'])
for d in install:
if d.name == 'itsdangerous':
assert d.subdir == context.subdir


def test_noarch_preferred_over_arch_when_version_greater_dep():
index = (
PackageRecord(**{
'name': 'abc',
'version': '2.0',
'build': '0',
"subdir": "noarch",
'build_number': 0,
}),
PackageRecord(**{
'name': 'abc',
'version': '1.0',
'build': '0',
"subdir": context.subdir,
'build_number': 0,
}),
PackageRecord(**{
'name': 'foo',
'version': '1.0',
'build': '0',
"subdir": context.subdir,
'build_number': 0,
'depends': ['abc'],
}),
)
r = Resolve(OrderedDict((prec, prec) for prec in index))
install = r.install(['foo'])
for d in install:
if d.name == 'abc':
assert d.subdir == 'noarch'
assert d.version == '2.0'


def test_noarch_preferred_over_arch_when_build_greater_dep():
index = (
PackageRecord(**{
'name': 'abc',
'version': '1.0',
'build': '1',
"subdir": "noarch",
'build_number': 1,
}),
PackageRecord(**{
'name': 'abc',
'version': '1.0',
'build': '0',
"subdir": context.subdir,
'build_number': 0,
}),
PackageRecord(**{
'name': 'foo',
'version': '1.0',
'build': '0',
"subdir": context.subdir,
'build_number': 0,
'depends': ['abc'],
}),
)
r = Resolve(OrderedDict((prec, prec) for prec in index))
install = r.install(['abc'])
for d in install:
if d.name == 'abc':
assert d.subdir == 'noarch'
assert d.build_number == 1

0 comments on commit 1e13462

Please sign in to comment.