Repository: conan-io/conan
Pull request #16003 (2024-04-02 20:44:02)

warning deprecate Python 36
Changelog: Fix: Print a warning for Python 3.6 usage which is EOL since 2021
Docs: Omit



Patch:
diff --git a/conan/cli/cli.py b/conan/cli/cli.py
index 7c9548212..a9a420c58 100644
--- a/conan/cli/cli.py
+++ b/conan/cli/cli.py
@@ -236,6 +236,14 @@ class Cli:
         return ERROR_UNEXPECTED
 
 
+def _warn_python_version():
+    version = sys.version_info
+    if version.minor == 6:
+        ConanOutput().warning("Python 3.6 is end-of-life since 2021. "
+                              "Conan future versions will drop support for it, "
+                              "please upgrade Python", warn_tag="deprecated")
+
+
 def main(args):
     """ main entry point of the conan application, using a Command to
     parse parameters
@@ -281,6 +289,7 @@ def main(args):
     error = SUCCESS
     try:
         cli.run(args)
+        _warn_python_version()
     except BaseException as e:
         error = cli.exception_exit_error(e)
     sys.exit(error)
diff --git a/conan/cli/commands/create.py b/conan/cli/commands/create.py
index 36c42372f..d5b2e186e 100644
--- a/conan/cli/commands/create.py
+++ b/conan/cli/commands/create.py
@@ -9,6 +9,7 @@ from conan.cli.formatters.graph import format_graph_json
 from conan.cli.printers import print_profiles
 from conan.cli.printers.graph import print_graph_packages, print_graph_basic
 from conan.errors import ConanException
+from conans.client.graph.graph import BINARY_BUILD
 from conans.util.files import mkdir
 
 
@@ -26,14 +27,19 @@ def create(conan_api, parser, *args):
     parser.add_argument("-tf", "--test-folder", action=OnceArgument,
                         help='Alternative test folder name. By default it is "test_package". '
                              'Use "" to skip the test stage')
+    parser.add_argument("-tm", "--test-missing", action='store_true', default=False,
+                        help='Run the test_package checks only if the package is built from source'
+                             ' but not if it already existed (using --build=missing)')
     parser.add_argument("-bt", "--build-test", action="append",
                         help="Same as '--build' but only for the test_package requires. By default"
                              " if not specified it will take the '--build' value if specified")
     args = parser.parse_args(*args)
 
+    if args.test_missing and args.test_folder == "":
+        raise ConanException('--test-folder="" is incompatible with --test-missing')
+
     cwd = os.getcwd()
     path = conan_api.local.get_conanfile_path(args.path, cwd, py=True)
-    test_conanfile_path = _get_test_conanfile_path(args.test_folder, path)
     overrides = eval(args.lockfile_overrides) if args.lockfile_overrides else None
     lockfile = conan_api.lockfile.get_lockfile(lockfile=args.lockfile,
                                                conanfile_path=path,
@@ -91,6 +97,14 @@ def create(conan_api, parser, *args):
         lockfile = conan_api.lockfile.update_lockfile(lockfile, deps_graph, args.lockfile_packages,
                                                       clean=args.lockfile_clean)
 
+    test_package_folder = getattr(conanfile, "test_package_folder", None) \
+        if args.test_folder is None else args.test_folder
+    test_conanfile_path = _get_test_conanfile_path(test_package_folder, path)
+    # If the user provide --test-missing and the binary was not built from source, skip test_package
+    if args.test_missing and deps_graph.root.dependencies\
+            and deps_graph.root.dependencies[0].dst.binary != BINARY_BUILD:
+        test_conanfile_path = None  # disable it
+
     if test_conanfile_path:
         # TODO: We need arguments for:
         #  - decide update policy "--test_package_update"


Test patch:
diff --git a/conans/test/integration/command/create_test.py b/conans/test/integration/command/create_test.py
index 1a8218d24..9158370df 100644
--- a/conans/test/integration/command/create_test.py
+++ b/conans/test/integration/command/create_test.py
@@ -811,3 +811,53 @@ def test_python_requires_with_test_package():
     c.run("create .")
     # Ensure that creating a deps graph does not break the testing
     assert "pyreq/1.0 (test package): 42!!!" in c.out
+
+
+def test_create_test_package_only_build():
+    c = TestClient()
+    c.save({"conanfile.py": GenConanfile("pkg", "0.1"),
+            "test_package/conanfile.py": GenConanfile().with_test("self.output.info('TEST1!!!')"),
+            "test_package2/conanfile.py": GenConanfile().with_test("self.output.info('TEST2!!!')")})
+    # As it doesn't exist, it builds and test it
+    c.run("create . -tm")
+    assert "Testing the package" in c.out
+    assert "TEST1!!!" in c.out
+    # this will not create the binary, so it won't test it
+    c.run("create . --build=missing --test-missing")
+    assert "Testing the package" not in c.out
+    assert "TEST" not in c.out
+    c.run("create . -tf=test_package2 -tm")
+    assert "Testing the package" in c.out
+    assert "TEST2!!!" in c.out
+    assert "TEST1!!!" not in c.out
+    c.run("create . -tf=test_package2 --build=missing --test-missing")
+    assert "Testing the package" not in c.out
+    assert "TEST2!!!" not in c.out
+    assert "TEST1!!!" not in c.out
+
+    # error
+    c.run("create . -tm -tf=", assert_error=True)
+    assert '--test-folder="" is incompatible with --test-missing' in c.out
+
+
+def test_create_test_package_only_build_python_require():
+    c = TestClient()
+    test = textwrap.dedent("""
+        from conan import ConanFile
+
+        class Tool(ConanFile):
+            python_requires = "tested_reference_str"
+            def test(self):
+                self.output.info("TEST!!!!")
+        """)
+    c.save({"conanfile.py": GenConanfile("pkg", "0.1").with_package_type("python-require"),
+            "test_package/conanfile.py": test})
+    c.run("create .")
+    assert "Testing the package" in c.out
+    assert "pkg/0.1 (test package): TEST!!!" in c.out
+    c.run("create . -tm")
+    assert "Testing the package" in c.out
+    assert "pkg/0.1 (test package): TEST!!!" in c.out
+    c.run("create . -tm --build=missing")
+    assert "Testing the package" in c.out
+    assert "pkg/0.1 (test package): TEST!!!" in c.out
diff --git a/conans/test/integration/command/remote_test.py b/conans/test/integration/command/remote_test.py
index 12cbdda74..b687fb592 100644
--- a/conans/test/integration/command/remote_test.py
+++ b/conans/test/integration/command/remote_test.py
@@ -247,7 +247,7 @@ class RemoteModificationTest(unittest.TestCase):
         for remote in data["remotes"]:
             self.assertEqual(remote["disabled"], True)
 
-        data = json.loads(client.out)
+        data = json.loads(client.stdout)
         for remote in data:
             assert remote["enabled"] is False
 
diff --git a/conans/test/integration/command/test_profile.py b/conans/test/integration/command/test_profile.py
index d066f45f7..db8e6d7b7 100644
--- a/conans/test/integration/command/test_profile.py
+++ b/conans/test/integration/command/test_profile.py
@@ -74,7 +74,7 @@ def test_shorthand_syntax():
             """)})
 
     tc.run("profile show -pr:a=pre -pr:a=mid -pr:a=post -f=json")
-    out = json.loads(tc.out)
+    out = json.loads(tc.stdout)
     assert out == {'build': {'build_env': '',
                              'conf': {},
                              'options': {},
diff --git a/conans/test/integration/conanfile/test_deploy_method.py b/conans/test/integration/conanfile/test_deploy_method.py
index 7579ec5c0..1c8a3f1ba 100644
--- a/conans/test/integration/conanfile/test_deploy_method.py
+++ b/conans/test/integration/conanfile/test_deploy_method.py
@@ -76,3 +76,53 @@ def test_deploy_local():
     # I can exclude the current consumer, it won't fail
     c.run("install . --deployer-package=!& --deployer-package=*")
     assert "Install finished successfully" in c.out
+
+
+def test_deploy_method_tool_requires():
+    # Test to validate https://github.com/conan-io/docs/issues/3649
+    c = TestClient()
+    tool = textwrap.dedent(r"""
+        import os
+        from conan import ConanFile
+        from conan.tools.files import save, chdir
+        class Pkg(ConanFile):
+            name = "tool"
+            version = "0.1"
+            type = "application"
+
+            def package(self):
+               with chdir(self, self.package_folder):
+                   echo = "@echo off\necho MYTOOL RUNNING!!"
+                   save(self, "mytool.bat", echo)
+                   save(self, "mytool.sh", echo)
+                   os.chmod("mytool.sh", 0o777)
+
+            def package_info(self):
+               self.buildenv_info.prepend_path("PATH", self.package_folder)
+        """)
+    conanfile = textwrap.dedent("""
+        import os
+        from conan import ConanFile
+        from conan.tools.env import VirtualBuildEnv
+        class Pkg(ConanFile):
+            name = "pkg"
+            version = "0.1"
+            tool_requires = "tool/0.1"
+            settings = "os"
+
+            def deploy(self):
+                self.output.info(f"DEPLOY {os.getcwd()}")
+                venv = VirtualBuildEnv(self)
+                with venv.vars().apply():
+                    ext = "bat" if self.settings.os == "Windows" else "sh"
+                    self.run(f"mytool.{ext}")
+        """)
+    c.save({"tool/conanfile.py": tool,
+            "pkg/conanfile.py": conanfile})
+
+    c.run("create tool")
+    c.run("create pkg")
+
+    # install can deploy all
+    c.run("install --requires=pkg/0.1 -c:b tools.graph:skip_binaries=False --deployer-package=*")
+    assert "MYTOOL RUNNING!!" in c.out
diff --git a/conans/test/integration/layout/test_layout_generate.py b/conans/test/integration/layout/test_layout_generate.py
index 7548f0788..0fdbbe8ef 100644
--- a/conans/test/integration/layout/test_layout_generate.py
+++ b/conans/test/integration/layout/test_layout_generate.py
@@ -125,3 +125,33 @@ def test_generate_build_folder_test_package():
             "test_package/conanfile.py": conanfile})
     c.run("create .")
     assert f"build_folder in test_package: True" in c.out
+
+
+class TestCustomTestPackage:
+    def test_custom_test_package(self):
+        c = TestClient(light=True)
+        conanfile = GenConanfile("pkg", "0.1").with_class_attribute('test_package_folder="mytest"')
+        c.save({"conanfile.py": conanfile,
+                "mytest/conanfile.py": GenConanfile().with_test("self.output.info('MYTEST!')"),
+                "mytest2/conanfile.py": GenConanfile().with_test("self.output.info('MYTEST2!')")})
+        c.run("create .")
+        assert "MYTEST!" in c.out
+        c.run("create . -tf=mytest2")
+        assert "MYTEST2!" in c.out
+
+    def test_custom_test_package_subfolder(self):
+        c = TestClient(light=True)
+        conanfile = GenConanfile("pkg", "0.1").with_class_attribute('test_package_folder="my/test"')
+        c.save({"pkg/conanfile.py": conanfile,
+                "pkg/my/test/conanfile.py": GenConanfile().with_test("self.output.info('MYTEST!')")})
+        c.run("create pkg")
+        assert "MYTEST!" in c.out
+
+    def test_custom_test_package_sibling(self):
+        c = TestClient(light=True)
+        conanfile = GenConanfile("pkg", "0.1").with_class_attribute(
+            'test_package_folder="../my/test"')
+        c.save({"pkg/conan/conanfile.py": conanfile,
+                "pkg/my/test/conanfile.py": GenConanfile().with_test("self.output.info('MYTEST!')")})
+        c.run("create pkg/conan")
+        assert "MYTEST!" in c.out
diff --git a/conans/test/integration/package_id/package_id_test.py b/conans/test/integration/package_id/package_id_test.py
index 9400203c1..4acd4516c 100644
--- a/conans/test/integration/package_id/package_id_test.py
+++ b/conans/test/integration/package_id/package_id_test.py
@@ -219,3 +219,33 @@ class TestBuildRequiresHeaderOnly:
         c.run("create tool --version=1.2")
         c.run("install --requires=pkg/0.1")
         c.assert_listed_binary({"pkg/0.1": (pkgid, "Cache")})
+
+
+def test_explicit_implements():
+    c = TestClient()
+    pkg = textwrap.dedent("""\
+        from conan import ConanFile
+        class Pkg(ConanFile):
+            name = "pkg"
+            version = "0.1"
+            settings = "os"
+            options = {"header_only": [True, False]}
+
+            def package_id(self):
+                if self.package_type == "header-library":
+                    self.info.clear()
+             """)
+    c.save({"conanfile.py": pkg})
+    c.run("create . -s os=Windows -o *:header_only=True")
+    pkgid = c.created_package_id("pkg/0.1")
+    c.run("create . -s os=Linux -o *:header_only=True")
+    pkgid2 = c.created_package_id("pkg/0.1")
+    assert pkgid == pkgid2
+
+    c.run("create . -s os=Windows -o *:header_only=False")
+    pkgid3 = c.created_package_id("pkg/0.1")
+    assert pkgid3 != pkgid
+    c.run("create . -s os=Linux -o *:header_only=False")
+    pkgid4 = c.created_package_id("pkg/0.1")
+    assert pkgid4 != pkgid
+    assert pkgid3 != pkgid4