diff --git a/hab/parsers/distro.py b/hab/parsers/distro.py index 0b26a3e..9757725 100644 --- a/hab/parsers/distro.py +++ b/hab/parsers/distro.py @@ -24,6 +24,13 @@ def matching_versions(self, specification): """Returns a list of versions available matching the version specification. See `packaging.requirements` for details on valid requirements, but it should be the same as pip requirements. + + This respects `self.resolver.prereleases`, so pre-releases will only be + returned if that is set to True or if this specification uses an + "Inclusive ordered comparison"(`<=`, `>=`) and the specification + contains any of the pre-release specifiers (`.dev1`). You will need + to enable prereleases to use "Exclusive ordered comparison"(`<`, `>`)s. + This is consistent with how pip handles these options. """ if isinstance(specification, Requirement): specifier = specification.specifier @@ -31,9 +38,11 @@ def matching_versions(self, specification): specifier = specification else: specifier = Requirement(specification).specifier - return specifier.filter( - self.versions.keys(), prereleases=self.resolver.prereleases - ) + # If a pre-release specifier was provided, it should enable pre-releases + # even if the site doesn't. This replicates explicitly passing a pre-release + # version to pip even if you don't pass `--pre`. + prereleases = self.resolver.prereleases or specifier.prereleases + return specifier.filter(self.versions.keys(), prereleases=prereleases) @property def versions(self): diff --git a/tests/distros/pre-release/1.0.dev1/.hab.json b/tests/distros/pre-release/1.0.dev1/.hab.json new file mode 100644 index 0000000..beb25a2 --- /dev/null +++ b/tests/distros/pre-release/1.0.dev1/.hab.json @@ -0,0 +1,19 @@ +{ + "name": "pre-release", + "aliases": { + "windows": [ + [ + "pre", { + "cmd": "python" + } + ] + ], + "linux": [ + [ + "pre", { + "cmd": "python" + } + ] + ] + } +} diff --git a/tests/distros/pre-release/1.0/.hab.json b/tests/distros/pre-release/1.0/.hab.json new file mode 100644 index 0000000..beb25a2 --- /dev/null +++ b/tests/distros/pre-release/1.0/.hab.json @@ -0,0 +1,19 @@ +{ + "name": "pre-release", + "aliases": { + "windows": [ + [ + "pre", { + "cmd": "python" + } + ] + ], + "linux": [ + [ + "pre", { + "cmd": "python" + } + ] + ] + } +} diff --git a/tests/distros/pre-release/1.1.dev2/.hab.json b/tests/distros/pre-release/1.1.dev2/.hab.json new file mode 100644 index 0000000..beb25a2 --- /dev/null +++ b/tests/distros/pre-release/1.1.dev2/.hab.json @@ -0,0 +1,19 @@ +{ + "name": "pre-release", + "aliases": { + "windows": [ + [ + "pre", { + "cmd": "python" + } + ] + ], + "linux": [ + [ + "pre", { + "cmd": "python" + } + ] + ] + } +} diff --git a/tests/distros/pre-release2/1.0/.hab.json b/tests/distros/pre-release2/1.0/.hab.json new file mode 100644 index 0000000..670a835 --- /dev/null +++ b/tests/distros/pre-release2/1.0/.hab.json @@ -0,0 +1,19 @@ +{ + "name": "pre-release2", + "aliases": { + "windows": [ + [ + "pre2", { + "cmd": "python" + } + ] + ], + "linux": [ + [ + "pre2", { + "cmd": "python" + } + ] + ] + } +} diff --git a/tests/distros/pre-release2/1.1.dev2/.hab.json b/tests/distros/pre-release2/1.1.dev2/.hab.json new file mode 100644 index 0000000..670a835 --- /dev/null +++ b/tests/distros/pre-release2/1.1.dev2/.hab.json @@ -0,0 +1,19 @@ +{ + "name": "pre-release2", + "aliases": { + "windows": [ + [ + "pre2", { + "cmd": "python" + } + ] + ], + "linux": [ + [ + "pre2", { + "cmd": "python" + } + ] + ] + } +} diff --git a/tests/site_main_check.habcache b/tests/site_main_check.habcache index 4baebb9..46aaf07 100644 --- a/tests/site_main_check.habcache +++ b/tests/site_main_check.habcache @@ -1404,6 +1404,116 @@ }, "version": "2024.0" }, + "{config-root}/distros/pre-release2/1.0/.hab.json": { + "name": "pre-release2", + "aliases": { + "windows": [ + [ + "pre2", + { + "cmd": "python" + } + ] + ], + "linux": [ + [ + "pre2", + { + "cmd": "python" + } + ] + ] + }, + "version": "1.0" + }, + "{config-root}/distros/pre-release2/1.1.dev2/.hab.json": { + "name": "pre-release2", + "aliases": { + "windows": [ + [ + "pre2", + { + "cmd": "python" + } + ] + ], + "linux": [ + [ + "pre2", + { + "cmd": "python" + } + ] + ] + }, + "version": "1.1.dev2" + }, + "{config-root}/distros/pre-release/1.0.dev1/.hab.json": { + "name": "pre-release", + "aliases": { + "windows": [ + [ + "pre", + { + "cmd": "python" + } + ] + ], + "linux": [ + [ + "pre", + { + "cmd": "python" + } + ] + ] + }, + "version": "1.0.dev1" + }, + "{config-root}/distros/pre-release/1.0/.hab.json": { + "name": "pre-release", + "aliases": { + "windows": [ + [ + "pre", + { + "cmd": "python" + } + ] + ], + "linux": [ + [ + "pre", + { + "cmd": "python" + } + ] + ] + }, + "version": "1.0" + }, + "{config-root}/distros/pre-release/1.1.dev2/.hab.json": { + "name": "pre-release", + "aliases": { + "windows": [ + [ + "pre", + { + "cmd": "python" + } + ] + ], + "linux": [ + [ + "pre", + { + "cmd": "python" + } + ] + ] + }, + "version": "1.1.dev2" + }, "{config-root}/distros/the_dcc/1.0/.hab.json": { "name": "the_dcc", "environment": {}, diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 34ec46d..961b178 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -8,6 +8,7 @@ from packaging.requirements import Requirement from hab import NotSet, Resolver, Site, utils +from hab.errors import InvalidRequirementError from hab.solvers import Solver @@ -245,6 +246,13 @@ def test_distros(self, resolver): " maya2020==2020.1", "maya2024", " maya2024==2024.0", + "pre-release", + " pre-release==1.0", + " pre-release==1.0.dev1", + " pre-release==1.1.dev2", + "pre-release2", + " pre-release2==1.0", + " pre-release2==1.1.dev2", "the_dcc", " the_dcc==1.0", " the_dcc==1.1", @@ -293,6 +301,13 @@ def test_distros_truncate(self, resolver): " maya2020==2020.1", "maya2024", " maya2024==2024.0", + "pre-release", + " pre-release==1.0", + " ...", + " pre-release==1.1.dev2", + "pre-release2", + " pre-release2==1.0", + " pre-release2==1.1.dev2", "the_dcc", " the_dcc==1.0", " ...", @@ -563,6 +578,61 @@ def test_forced_requirements( for i, v in enumerate(versions): assert v.name == check_versions[i] + @pytest.mark.parametrize( + "requirements,prereleases,check", + ( + # Full release requirements respect `--pre` flag + (["pre-release"], False, ["pre-release==1.0"]), + (["pre-release"], True, ["pre-release==1.1.dev2"]), + (["pre-release==1.0"], True, ["pre-release==1.0"]), + (["pre-release==1.0"], False, ["pre-release==1.0"]), + (["pre-release<=2.0"], True, ["pre-release==1.1.dev2"]), + (["pre-release<=2.0"], False, ["pre-release==1.0"]), + (["pre-release>=1.0"], True, ["pre-release==1.1.dev2"]), + (["pre-release>=1.0"], False, ["pre-release==1.0"]), + # Forced pre-release requirements will find pre-release distros + # regardless of the `--pre` flag. + (["pre-release==1.0rc99"], True, False), + (["pre-release==1.0rc99"], False, False), + (["pre-release==1.0.dev1"], True, ["pre-release==1.0.dev1"]), + (["pre-release==1.0.dev1"], False, ["pre-release==1.0.dev1"]), + (["pre-release<=1.0rc99"], True, ["pre-release==1.0.dev1"]), + (["pre-release<=1.0rc99"], False, ["pre-release==1.0.dev1"]), + # Forced pre-release requirements don't affect other requirements + # regardless of the `--pre` flag. + ( + ["pre-release<=1.0rc99", "pre-release2"], + False, + ["pre-release==1.0.dev1", "pre-release2==1.0"], + ), + ( + ["pre-release<=1.0rc99", "pre-release2"], + True, + ["pre-release==1.0.dev1", "pre-release2==1.1.dev2"], + ), + ), + ) + def test_pre_releases(self, habcached_resolver, requirements, prereleases, check): + """Check how prereleases and specifying a version with prereleases works. + + If the global prereleases is False, ignore any pre-release versions + unless the version specifier itself specifies a pre-release. See + `hab.parsers.distro.Distro.matching_versions` for details. + """ + habcached_resolver.prereleases = prereleases + uri = "not_set/no_distros" + + if check is False: + with pytest.raises(InvalidRequirementError): + habcached_resolver.resolve(uri, forced_requirements=requirements) + else: + cfg = habcached_resolver.resolve(uri, forced_requirements=requirements) + versions = cfg.versions + + assert len(versions) == len(check) + for i, v in enumerate(versions): + assert v.name == check[i] + def test_forced_requirements_uri(self, resolver, helpers): resolver_forced = Resolver( site=resolver.site,