-
Notifications
You must be signed in to change notification settings - Fork 38
Add conan ext:check-prevs command
#242
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| from conan.api.conan_api import ConanAPI | ||
| from conan.api.model import MultiPackagesList, ListPattern, PackagesList, PkgReference | ||
| from conan.api.output import ConanOutput | ||
|
|
||
| from conan.cli import make_abs_path | ||
| from conan.cli.command import conan_command, OnceArgument | ||
| from conan.cli.commands.list import print_list_text, print_list_json, print_list_compact | ||
| from conan.cli.formatters.list import list_packages_html | ||
|
|
||
| from conan.errors import ConanException | ||
|
|
||
|
|
||
| @conan_command(group="Extension", formatters={"text": print_list_text, | ||
| "json": print_list_json, | ||
| "html": list_packages_html, | ||
| "compact": print_list_compact}) | ||
| def check_prevs(conan_api: ConanAPI, parser, *args): | ||
| """ | ||
| Ensure that the selected references only contains 1 package revision in the given remotes | ||
| """ | ||
| parser.add_argument('pattern', nargs="?", | ||
| help="A pattern in the form 'pkg/version#revision:package_id#revision', " | ||
| "e.g: \"zlib/1.2.13:*\" means all binaries for zlib/1.2.13. " | ||
| "If revision is not specified, it is assumed latest one.") | ||
| parser.add_argument("-l", "--list", help="Package list file") | ||
| parser.add_argument('-p', '--package-query', default=None, action=OnceArgument, | ||
| help="Only upload packages matching a specific query. e.g: os=Windows AND " | ||
| "(arch=x86 OR compiler=gcc)") | ||
| parser.add_argument("-r", "--remote", action="append", default=None, | ||
| help='Look in the specified remote or remotes server') | ||
|
|
||
| args = parser.parse_args(*args) | ||
| remotes = conan_api.remotes.list(args.remote) | ||
|
|
||
| if args.pattern is None and args.list is None: | ||
| raise ConanException("Missing pattern or package list file") | ||
| if args.pattern and args.list: | ||
| raise ConanException("Cannot define both the pattern and the package list file") | ||
| if args.package_query and args.list: | ||
| raise ConanException("Cannot define package-query and the package list file") | ||
|
|
||
| result = MultiPackagesList() | ||
|
|
||
| for remote in remotes: | ||
| if args.list: | ||
| listfile = make_abs_path(args.list) | ||
| multi_package_list = MultiPackagesList.load(listfile) | ||
| if remote.name not in multi_package_list.lists: | ||
| ConanOutput().warning(f"No packages for remote '{remote.name}' were found " | ||
| "in the package list, skipping it.") | ||
| continue | ||
| pkglist = multi_package_list[remote.name] | ||
| else: | ||
| ref_pattern = ListPattern(args.pattern, rrev=None, prev="*") | ||
| if not ref_pattern.package_id: | ||
| raise ConanException("The pattern must include a package_id, e.g: " | ||
| "\"zlib/1.2.13:*\" means all binaries for zlib/1.2.13") | ||
| pkglist = conan_api.list.select(ref_pattern, args.package_query, remote) | ||
| remote_pkglist = PackagesList() | ||
| # Can't iterate packages because we might have been not given a package revision | ||
| for ref, _ in pkglist.items(): | ||
| recipe_dict = pkglist.recipe_dict(ref) | ||
| for package_id, pkg_info in recipe_dict.get("packages", {}).items(): | ||
| prevs = pkg_info.get("revisions", {}) | ||
| revisions = [] | ||
| if len(prevs) > 1: | ||
| # No need to ask the server (again if coming from the select endpoint) | ||
| # we already know there are multiple revisions for this package_id | ||
| for prev in prevs: | ||
| revisions.append(PkgReference(ref, package_id, prev)) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An optimization when the pkglist already contains more than 1 prev. The only downside is that the resulting pkglist won't contain possible extra prevs that might have been present in the remote |
||
| else: | ||
| revisions = conan_api.list.package_revisions(PkgReference(ref, package_id), | ||
| remote) | ||
| if len(revisions) > 1: | ||
| remote_pkglist.add_ref(ref) | ||
| for pref in revisions: | ||
| remote_pkglist.add_pref(pref) | ||
| result.add(remote.name, remote_pkglist) | ||
|
|
||
| return { | ||
| "conan_error": "Multiple package revisions found" if result.lists else None, | ||
| "results": result.serialize(), | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| import os | ||
| import textwrap | ||
|
|
||
| import pytest | ||
| from conan.test.utils.tools import TestClient | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Uploaded as-is to test if we can use normal Conan test suite classes here |
||
|
|
||
|
|
||
| @pytest.fixture(scope="package") | ||
| def client(): | ||
| tc = TestClient(light=True, | ||
| default_server_user=True, | ||
| custom_commands_folder=os.path.join(os.path.dirname(os.path.realpath(__file__)), | ||
| os.path.pardir, "extensions", "commands")) | ||
| conanfile = textwrap.dedent(""" | ||
| import os | ||
| import time | ||
| from conan import ConanFile | ||
| from conan.tools.files import save | ||
|
|
||
| class Pkg(ConanFile): | ||
| version = "1.0" | ||
| def package(self): | ||
| save(self, os.path.join(self.package_folder, "file.txt"), str(time.time())) | ||
| """) | ||
| tc.save({"conanfile.py": conanfile}) | ||
|
|
||
| tc.run("create --name=hello") | ||
| tc.run("create --name=hello") | ||
| tc.run("create --name=greetings") | ||
| tc.run("create --name=greetings") | ||
|
|
||
| tc.run("create --name=bye") | ||
| tc.run("upload '*:*#*' -c -r default") | ||
| return tc | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("remote", [True, False]) | ||
| @pytest.mark.parametrize("pattern", ["*:*", "*:*#*"]) | ||
| def test_check_prev_patterns_find(client, pattern, remote): | ||
| remote = "-r default" if remote else "" | ||
| client.run(f"ext:check-prevs {pattern} {remote}", assert_error=True) | ||
| assert "ERROR: Multiple package revisions found" in client.out | ||
| assert "bye/1.0" not in client.out | ||
| assert "hello/1.0" in client.out | ||
| assert "greetings/1.0" in client.out | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("pattern", ["bye/1.0:*", "bye/1.0:*#*"]) | ||
| def test_check_prev_patterns_no_find(client, pattern): | ||
| client.run(f"ext:check-prevs {pattern} -r default") | ||
| assert "ERROR: Multiple package revisions found" not in client.out | ||
| assert "bye/1.0" not in client.out | ||
| assert "hello/1.0" not in client.out | ||
| assert "greetings/1.0" not in client.out | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("pattern", ["*:*", "*:*#*"]) | ||
| def test_check_prev_pkglist_find(client, pattern): | ||
| client.run(f"list {pattern} -r default -f=json", redirect_stdout="list.json") | ||
| client.run(f"ext:check-prevs -l list.json", assert_error=True) | ||
| assert "ERROR: Multiple package revisions found" in client.out | ||
| assert "bye/1.0" not in client.out | ||
| assert "hello/1.0" in client.out | ||
| assert "greetings/1.0" in client.out | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("pattern", ["bye/1.0:*", "bye/1.0:*#*"]) | ||
| def test_check_prev_pkglist_no_find(client, pattern): | ||
| client.run(f"list {pattern} -r default -f=json", redirect_stdout="list.json") | ||
| client.run(f"ext:check-prevs -l list.json") | ||
| assert "ERROR: Multiple package revisions found" not in client.out | ||
| assert "bye/1.0" not in client.out | ||
| assert "hello/1.0" not in client.out | ||
| assert "greetings/1.0" not in client.out | ||
|
|
||
|
|
||
| def test_check_prev_errors(client): | ||
| client.run("ext:check-prevs * -l list.json", assert_error=True) | ||
| assert 'ERROR: Cannot define both the pattern and the package list file' in client.out | ||
|
|
||
| client.run(f"ext:check-prevs hello/1.0 -r default", assert_error=True) | ||
| assert "ERROR: Multiple package revisions found" not in client.out | ||
| assert "ERROR: The pattern must include a package_id" in client.out | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not clear, maybe we want to check that no different package revisions exists in different remotes?
Maybe the input should be "remote-less", that is a
PackageList, not aMultiPackageListwith origin or something like that?