From ad989b4351ed420073344aef6279abcc88fb9955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Mon, 17 Nov 2025 20:01:47 +0100 Subject: [PATCH 1/4] What if version could be had from conan_data if unique? --- conan/internal/loader.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/conan/internal/loader.py b/conan/internal/loader.py index f3634ee9a6f..8e795931bc7 100644 --- a/conan/internal/loader.py +++ b/conan/internal/loader.py @@ -143,6 +143,13 @@ def load_named(self, conanfile_path, name, version, user, channel, graph_lock=No with conanfile_exception_formatter("conanfile.py", "set_version"): conanfile.set_version() + if not conanfile.version: + # Last chance, try to get version from conan_data. + # Only works if only one version is present + sources = getattr(conanfile, "conan_data", {}).get("sources", {}) + if len(sources) == 1: + conanfile.version = next(iter(sources)) + return conanfile def load_export(self, conanfile_path, name, version, user, channel, graph_lock=None, From 6ee1168b8df7c57496a8e0d464e98f640f4d65d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Mon, 17 Nov 2025 20:27:12 +0100 Subject: [PATCH 2/4] conan_data can be None --- conan/internal/loader.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conan/internal/loader.py b/conan/internal/loader.py index 8e795931bc7..aac2195c850 100644 --- a/conan/internal/loader.py +++ b/conan/internal/loader.py @@ -146,7 +146,8 @@ def load_named(self, conanfile_path, name, version, user, channel, graph_lock=No if not conanfile.version: # Last chance, try to get version from conan_data. # Only works if only one version is present - sources = getattr(conanfile, "conan_data", {}).get("sources", {}) + conan_data = getattr(conanfile, "conan_data", {}) + sources = conan_data.get("sources", {}) if conan_data else {} if len(sources) == 1: conanfile.version = next(iter(sources)) From a285b983205f9ae5287bface5f49525ee3a2524f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Tue, 18 Nov 2025 00:15:42 +0100 Subject: [PATCH 3/4] Dirty test, but covers the use-case idea --- test/integration/conanfile/conan_data_test.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/integration/conanfile/conan_data_test.py b/test/integration/conanfile/conan_data_test.py index caea9415264..982eded993d 100644 --- a/test/integration/conanfile/conan_data_test.py +++ b/test/integration/conanfile/conan_data_test.py @@ -483,3 +483,50 @@ def generate(self): '2.0': x: foo """) + + +@pytest.mark.parametrize("multiple_versions", [True, False]) +@pytest.mark.parametrize("command", [ + ("source", False), + ("graph info", False), + ("create", True), + ("export", True) +]) +def test_commands_auto_pick_version(multiple_versions, command): + c = TestClient(light=True) + conanfile = textwrap.dedent(""" + from conan import ConanFile + class Pkg(ConanFile): + name = "pkg" + + def source(self): + self.output.info("ok") + """) + if multiple_versions: + conandata = textwrap.dedent(""" + sources: + 1.0: + url: "url1" + sha256: "sha1" + 2.0: + url: "url2" + sha256: "sha2" + """) + else: + conandata = textwrap.dedent(""" + sources: + 1.0: + url: "url1" + sha256: "sha1" + """) + c.save({"conanfile.py": conanfile, + "conandata.yml": conandata}) + assert_error = multiple_versions and command[1] + c.run(command[0], assert_error=assert_error) + if multiple_versions: + if command[1]: + assert "ERROR: conanfile didn't specify version" in c.out + else: + assert "pkg/None" in c.out + else: + assert "pkg/1.0" in c.out From d3967e51ef5fea3be5ff3b8d1a0ed143c6e0473e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Wed, 19 Nov 2025 20:32:52 +0100 Subject: [PATCH 4/4] Multi-version --- conan/internal/loader.py | 7 +- test/integration/conanfile/conan_data_test.py | 70 +++++++++++-------- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/conan/internal/loader.py b/conan/internal/loader.py index aac2195c850..a2d715382d8 100644 --- a/conan/internal/loader.py +++ b/conan/internal/loader.py @@ -16,6 +16,7 @@ from conan.tools.cmake import cmake_layout from conan.tools.google import bazel_layout from conan.tools.microsoft import vs_layout +from conan.tools.scm import Version from conan.internal.errors import conanfile_exception_formatter, NotFoundException from conan.errors import ConanException from conan.internal.model.conan_file import ConanFile @@ -148,8 +149,10 @@ def load_named(self, conanfile_path, name, version, user, channel, graph_lock=No # Only works if only one version is present conan_data = getattr(conanfile, "conan_data", {}) sources = conan_data.get("sources", {}) if conan_data else {} - if len(sources) == 1: - conanfile.version = next(iter(sources)) + sources = sources or {} + sorted_versions = sorted(sources.keys(), key=lambda x: Version(x)) + if sorted_versions: + conanfile.version = sorted_versions.pop() return conanfile diff --git a/test/integration/conanfile/conan_data_test.py b/test/integration/conanfile/conan_data_test.py index 982eded993d..6277e32f534 100644 --- a/test/integration/conanfile/conan_data_test.py +++ b/test/integration/conanfile/conan_data_test.py @@ -485,14 +485,13 @@ def generate(self): """) -@pytest.mark.parametrize("multiple_versions", [True, False]) @pytest.mark.parametrize("command", [ - ("source", False), - ("graph info", False), - ("create", True), - ("export", True) + "source", + "graph info", + "create", + "export" ]) -def test_commands_auto_pick_version(multiple_versions, command): +def test_commands_auto_pick_version(command): c = TestClient(light=True) conanfile = textwrap.dedent(""" from conan import ConanFile @@ -502,31 +501,44 @@ class Pkg(ConanFile): def source(self): self.output.info("ok") """) - if multiple_versions: - conandata = textwrap.dedent(""" - sources: - 1.0: - url: "url1" - sha256: "sha1" - 2.0: - url: "url2" - sha256: "sha2" - """) - else: - conandata = textwrap.dedent(""" + conandata = textwrap.dedent(""" sources: 1.0: url: "url1" - sha256: "sha1" - """) + 2.0: + url: "url2" + """) + c.save({"conanfile.py": conanfile, "conandata.yml": conandata}) - assert_error = multiple_versions and command[1] - c.run(command[0], assert_error=assert_error) - if multiple_versions: - if command[1]: - assert "ERROR: conanfile didn't specify version" in c.out - else: - assert "pkg/None" in c.out - else: - assert "pkg/1.0" in c.out + c.run(command) + assert "pkg/2.0" in c.out + + +@pytest.mark.parametrize("conandata", [ + pytest.param( + textwrap.dedent(""" + sources: + # Empty, no content + """), id="empty sources"), + pytest.param( + textwrap.dedent(""" + # No sources key + foo: 1 + """), id="no sources"), +]) +def test_commands_auto_pick_version_no_version_conandata(conandata): + c = TestClient(light=True) + conanfile = textwrap.dedent(""" + from conan import ConanFile + class Pkg(ConanFile): + name = "pkg" + + def source(self): + self.output.info("ok") + """) + + c.save({"conanfile.py": conanfile, + "conandata.yml": conandata}) + c.run("create", assert_error=True) + assert "conanfile didn't specify version" in c.out