Skip to content

Commit 5a67746

Browse files
AbrilRBSmemsharded
andauthored
Add example test for forwards compat of custom setting (#17524)
* Add example test for forwards compat of custom setting * Cleanup * Cppstd compat * MSVC combination tested too * refactored compatibility.py for easier extension * add new checks to msvc 194->193 * fix test combinatorics --------- Co-authored-by: memsharded <[email protected]>
1 parent ac0e86e commit 5a67746

File tree

2 files changed

+97
-28
lines changed

2 files changed

+97
-28
lines changed

conans/client/graph/compatibility.py

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,40 @@
1515
1616
1717
def compatibility(conanfile):
18-
configs = cppstd_compat(conanfile)
19-
# TODO: Append more configurations for your custom compatibility rules
20-
return configs
18+
# By default, different compiler.cppstd are compatible
19+
# factors is a list of lists
20+
factors = cppstd_compat(conanfile)
21+
22+
# MSVC 194->193 fallback compatibility
23+
compiler = conanfile.settings.get_safe("compiler")
24+
compiler_version = conanfile.settings.get_safe("compiler.version")
25+
if compiler == "msvc":
26+
msvc_fallback = {"194": "193"}.get(compiler_version)
27+
if msvc_fallback:
28+
factors.append([{"compiler.version": msvc_fallback}])
29+
30+
# Append more factors for your custom compatibility rules here
31+
32+
# Combine factors to compute all possible configurations
33+
combinations = _factors_combinations(factors)
34+
# Final compatibility settings combinations to check
35+
return [{"settings": [(k, v) for k, v in comb.items()]} for comb in combinations]
36+
37+
38+
def _factors_combinations(factors):
39+
combinations = []
40+
for factor in factors:
41+
if not combinations:
42+
combinations = factor
43+
continue
44+
new_combinations = []
45+
for comb in combinations:
46+
for f in factor:
47+
new_comb = comb.copy()
48+
new_comb.update(f)
49+
new_combinations.append(new_comb)
50+
combinations.extend(new_combinations)
51+
return combinations
2152
"""
2253

2354

@@ -51,29 +82,7 @@ def cppstd_compat(conanfile):
5182
conanfile.output.warning(f'No cstd compatibility defined for compiler "{compiler}"')
5283
else:
5384
factors.append([{"compiler.cstd": v} for v in cstd_possible_values if v != cstd])
54-
55-
if compiler == "msvc":
56-
msvc_fallback = {"194": "193"}.get(compiler_version)
57-
if msvc_fallback:
58-
factors.append([{"compiler.version": msvc_fallback}])
59-
60-
combinations = []
61-
for factor in factors:
62-
if not combinations:
63-
combinations = factor
64-
continue
65-
new_combinations = []
66-
for comb in combinations:
67-
for f in factor:
68-
new_comb = comb.copy()
69-
new_comb.update(f)
70-
new_combinations.append(new_comb)
71-
combinations.extend(new_combinations)
72-
73-
ret = []
74-
for comb in combinations:
75-
ret.append({"settings": [(k, v) for k, v in comb.items()]})
76-
return ret
85+
return factors
7786
"""
7887

7988

test/integration/package_id/compatible_test.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,13 +443,18 @@ def test_compatibility_msvc_and_cppstd(self):
443443
compiler.runtime=dynamic
444444
""")
445445
tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0").with_setting("compiler"),
446-
"conanfile.py": GenConanfile("app", "1.0").with_require("dep/1.0").with_setting("compiler"),
447446
"profile": profile})
448447

449448
tc.run("create dep -pr=profile -s compiler.cppstd=20")
450-
tc.run("create . -pr=profile -s compiler.cppstd=17")
449+
tc.run("install --requires=dep/1.0 -pr=profile -s compiler.cppstd=17")
451450
tc.assert_listed_binary({"dep/1.0": ("b6d26a6bc439b25b434113982791edf9cab4d004", "Cache")})
452451

452+
tc.run("remove * -c")
453+
tc.run("create dep -pr=profile -s compiler.version=193 -s compiler.cppstd=20")
454+
tc.run("install --requires=dep/1.0 -pr=profile -s compiler.cppstd=17")
455+
assert "compiler.cppstd=20, compiler.version=193" in tc.out
456+
tc.assert_listed_binary({"dep/1.0": ("535899bb58c3ca7d80a380313d31f4729e735d1c", "Cache")})
457+
453458

454459
class TestCompatibleBuild:
455460
def test_build_compatible(self):
@@ -706,3 +711,58 @@ def validate_build(self):
706711
pkga = liba["packages"][0][pkg_index]
707712
assert pkga["info"]["compatibility_delta"] == {"settings": [["compiler.cppstd", "14"]]}
708713
assert pkga["build_args"] == "--requires=liba/0.1 --build=compatible:liba/0.1"
714+
715+
716+
def test_compatibility_new_setting_forwards_compat():
717+
""" This test tries to reflect the following scenario:
718+
- User adds a new setting (libc.version in this case)
719+
- This setting is forward compatible
720+
How is it solved with compatibility.py? Like this:
721+
"""
722+
tc = TestClient()
723+
tc.save_home({"settings_user.yml": "libc_version: [1, 2, 3]"})
724+
tc.save({"conanfile.py": GenConanfile("dep", "1.0").with_settings("libc_version", "compiler")})
725+
# The extra cppstd and compiler versions are for later demonstrations of combinations of settings
726+
# The cppstd=17 and compiler.version=193 are used thought until the last 2 install calls
727+
tc.run("create . -s=libc_version=2 -s=compiler.cppstd=17")
728+
dep_package_id = tc.created_package_id("dep/1.0")
729+
tc.run("install --requires=dep/1.0 -s=libc_version=3 -s=compiler.cppstd=17", assert_error=True)
730+
# We can't compile, because the dep is not compatible
731+
assert "Missing prebuilt package for 'dep/1.0'" in tc.out
732+
733+
# Let's create a compatibility extensions
734+
libc_compat = textwrap.dedent("""
735+
from conan.tools.scm import Version
736+
737+
def libc_compat(conanfile):
738+
# Do we have the setting?
739+
libc_version = conanfile.settings.get_safe("libc_version")
740+
if libc_version is None:
741+
return []
742+
available_libc_versions = conanfile.settings.libc_version.possible_values()
743+
ret = []
744+
for possible_libc_version in available_libc_versions:
745+
if Version(possible_libc_version) < Version(libc_version):
746+
ret.append({"libc_version": possible_libc_version})
747+
return ret
748+
""")
749+
compat = tc.load_home("extensions/plugins/compatibility/compatibility.py")
750+
compat = "from libc_compat import libc_compat\n" + compat
751+
compat = compat.replace("# Append more factors for your custom compatibility rules here",
752+
"factors.append(libc_compat(conanfile))")
753+
tc.save_home({"extensions/plugins/compatibility/libc_compat.py": libc_compat,
754+
"extensions/plugins/compatibility/compatibility.py": compat})
755+
756+
# Now we try again, this time app will find the compatible dep with libc_version 2
757+
tc.run("install --requires=dep/1.0 -s=libc_version=3 -s=compiler.cppstd=17")
758+
assert f"dep/1.0: Found compatible package '{dep_package_id}'" in tc.out
759+
760+
# And now we try to create the app with libc_version 1, which is still not compatible
761+
tc.run("install --requires=dep/1.0 -s=libc_version=1 -s=compiler.cppstd=17", assert_error=True)
762+
assert "Missing prebuilt package for 'dep/1.0'" in tc.out
763+
764+
# Now we try again, this time app will find the compatible dep with libc_version 2
765+
# And see how we're also compatible over a different cppstd
766+
tc.run("install --requires=dep/1.0 -s=libc_version=3 -s=compiler.cppstd=14")
767+
assert f"dep/1.0: Found compatible package '{dep_package_id}': compiler.cppstd=17, " \
768+
f"libc_version=2" in tc.out

0 commit comments

Comments
 (0)