Repository: conan-io/conan

Issue:
Warning when using cmake.install() for packaging
To help us debug your issue please explain:

- [x] I've read the [CONTRIBUTING guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).
- [x] I've specified the Conan version, operating system version and any tool that can be relevant.
- [x] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.

I'm using conan version 1.4.4, running on Windows 10
I have a simple conanfile.py, which contains mostly:

    def configure_cmake(self):
        cmake = CMake(self)
        cmake.configure()
        return cmake

    def build(self):
        cmake = self.configure_cmake()
        cmake.build()

    def package(self):
        cmake = self.configure_cmake()
        cmake.install()

Note that cmake.install is used to copy files into the package folder. This method is documented at https://docs.conan.io/en/latest/reference/build_helpers/cmake.html.

I invoke conan like this:

conan export conanfile.py MyUser/MyChannel
conan install MyPackage/MyVersion@MyUser/MyChannel --build

I get a warning 
MyPackage/MyVersion@MyUser/MyChannel: WARN: No files copied from build folder!

However, the package folder does actually contain my files. The warning is incorrect. Is there some way for me to suppress it? I generally don't allow any warnings or errors in my build process.


Discussion:

Patch:
diff --git a/.ci/jenkins/Jenkinsfile b/.ci/jenkins/Jenkinsfile
index 1398085b3..95fc8b5aa 100644
--- a/.ci/jenkins/Jenkinsfile
+++ b/.ci/jenkins/Jenkinsfile
@@ -96,6 +96,61 @@ echo "STAGES: ${flavors}"
 echo "PYVERS: ${all_pyvers}"
 echo "EXCLUDED TAGS: ${excluded_tags}"
 
+
+
+// FIRST rest api tests, fail ASAP
+rest_builders = [:]
+for (n in ["Windows", "Linux"]) {
+    def the_node = n
+    def pyver = "py36"
+    rest_builders["${the_node} Rest API Test"] = {
+        node(the_node){
+            stage("REST tests ${the_node}"){
+                def vars = checkout scm
+                def base_dir = (the_node == "Windows") ? win_tmp_base : rest_tmp_base
+                base_dir = base_dir + "rest" + vars["GIT_COMMIT"].substring(0, 4)
+                def venv_dir = base_dir + "/venv"
+                def src_dir = base_dir + "/src"
+
+                while(fileExists(src_dir)){
+                    src_dir = src_dir + "_"
+                }
+                while(fileExists(venv_dir)){
+                    venv_dir = venv_dir + "_"
+                }
+
+                // Copy SRC to a clean folder
+                dir(base_dir){ // Trick to create the parent
+                    def escaped_ws = "${WORKSPACE}".replace("\\", "/")
+                    def cmd = "python -c \"import shutil; shutil.copytree('${escaped_ws}', '${src_dir}')\""
+                    if (the_node == "Windows"){
+                        bat(script: cmd)
+                    }
+                    else{
+                        sh(script: cmd)
+                    }
+                }
+
+                if(the_node == "Windows"){
+                    try{
+                      bat(script: "python ${runner} conans.test.functional.remote.rest_api_test ${pyver} ${src_dir} \"${venv_dir}\"")
+                    }
+                    finally{
+                      bat(script: "rd /s /q \"${base_dir}\"")
+                    }
+                }
+                else{
+                    docker.image('conanio/conantests').inside("-e CONAN_USER_HOME=${src_dir} -v${src_dir}:${src_dir}") {
+                      sh(script: "python ${runner} conans.test.functional.remote.rest_api_test ${pyver} ${src_dir} /tmp")
+                    }
+                }
+            }
+        }
+    }
+}
+parallel rest_builders
+
+
 try{
     for (flavor in flavors){
         def builders = [:]
@@ -183,48 +238,6 @@ try{
         parallel builders
     }
 
-
-
-
-    // Run rest_api_test without concurrency between same node
-    pyvers = ['py36']
-    for (y in pyvers) {
-        builders = [:]
-        def pyver = y
-        builders["Windows Rest API Test"] = {
-            node("Windows"){
-                stage("REST tests Windows ${pyver}"){
-                    checkout scm
-                    try{
-                      bat(script: "python ${runner} conans.test.functional.remote.rest_api_test ${pyver} ${WORKSPACE} \"${win_tmp_base}${commit}\"")
-                    }
-                    finally{
-                      bat(script: "rd /s /q \"${win_tmp_base}${commit}\"")
-                    }
-                }
-            }
-        }
-        builders["Linux Rest API Test"] = {
-            node("Linux"){
-                stage("REST tests Linux ${pyver}"){
-                    checkout scm
-                    docker.image('conanio/conantests').inside("-e CONAN_USER_HOME=${WORKSPACE}") {
-                        sh(script: "python ${runner} conans.test.functional.remote.rest_api_test ${pyver} ${WORKSPACE} /tmp/${commit}")
-                    }
-                }
-            }
-        }
-        /*builders["Mac Rest API Test"] = {
-            node("Macos"){
-                stage("REST tests Windows ${pyver}"){
-                    withEnv(['PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin']) {
-                        sh(script: "tox --workdir /tmp/${commit} -e ${pyver} -- -x conans.test.functional.remote.rest_api_test")
-                    }
-                }
-            }
-        }*/ // EXTREMELY SLOW, INVESTIGATE
-        parallel builders
-    }
     if (env.BRANCH_NAME == "develop") {
         // Deploy snapshot to test pypi if branch develop
         node("Linux") {
diff --git a/conans/__init__.py b/conans/__init__.py
index 7f4434ff6..e129f6e9e 100644
--- a/conans/__init__.py
+++ b/conans/__init__.py
@@ -19,3 +19,4 @@ SERVER_CAPABILITIES = [COMPLEX_SEARCH_CAPABILITY, REVISIONS]  # Server is always
 DEFAULT_REVISION_V1 = "0"
 
 __version__ = '1.13.0-dev'
+
diff --git a/conans/client/file_copier.py b/conans/client/file_copier.py
index 150475dbb..463aba1c7 100644
--- a/conans/client/file_copier.py
+++ b/conans/client/file_copier.py
@@ -6,7 +6,7 @@ from collections import defaultdict
 from conans.util.files import mkdir, walk
 
 
-def report_copied_files(copied, output):
+def report_copied_files(copied, output, message_suffix="Copied"):
     ext_files = defaultdict(list)
     for f in copied:
         _, ext = os.path.splitext(f)
@@ -19,9 +19,9 @@ def report_copied_files(copied, output):
         files_str = (", ".join(files)) if len(files) < 5 else ""
         file_or_files = "file" if len(files) == 1 else "files"
         if not ext:
-            output.info("Copied %d %s: %s" % (len(files), file_or_files, files_str))
+            output.info("%s %d %s: %s" % (message_suffix, len(files), file_or_files, files_str))
         else:
-            output.info("Copied %d '%s' %s: %s" % (len(files), ext, file_or_files, files_str))
+            output.info("%s %d '%s' %s: %s" % (message_suffix, len(files), ext, file_or_files, files_str))
     return True
 
 
diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py
index 35bc312ba..66d6f1290 100644
--- a/conans/client/graph/graph_builder.py
+++ b/conans/client/graph/graph_builder.py
@@ -137,11 +137,10 @@ class DepsGraphBuilder(object):
     def _conflicting_references(previous_ref, new_ref):
         if previous_ref.copy_clear_rev() != new_ref.copy_clear_rev():
             return REFERENCE_CONFLICT
-        # Computed node, has to have a revision, at least 0
-        assert(previous_ref.revision is not None)
+        # Computed node, if is Editable, has revision=None
         # If new_ref.revision is None we cannot assume any conflict, the user hasn't specified
         # a revision, so it's ok any previous_ref
-        if new_ref.revision and previous_ref.revision != new_ref.revision:
+        if previous_ref.revision and new_ref.revision and previous_ref.revision != new_ref.revision:
             return REVISION_CONFLICT
         return False
 
diff --git a/conans/client/packager.py b/conans/client/packager.py
index 5c4725022..1c33b8328 100644
--- a/conans/client/packager.py
+++ b/conans/client/packager.py
@@ -2,7 +2,7 @@ import os
 import shutil
 
 from conans.client import tools
-from conans.client.file_copier import FileCopier
+from conans.client.file_copier import FileCopier, report_copied_files
 from conans.client.output import ScopedOutput
 from conans.errors import (ConanException, ConanExceptionInUserConanfileMethod,
                            conanfile_exception_formatter)
@@ -25,13 +25,12 @@ def export_pkg(conanfile, package_id, src_package_folder, package_folder, hook_m
     copier = FileCopier(src_package_folder, package_folder)
     copier("*", symlinks=True)
 
-    copy_done = copier.report(output)
-    if not copy_done:
-        output.warn("No files copied from package folder!")
-
     save(os.path.join(package_folder, CONANINFO), conanfile.info.dumps())
     digest = FileTreeManifest.create(package_folder)
     digest.save(package_folder)
+
+    _report_files_from_manifest(output, package_folder)
+
     output.success("Package '%s' created" % package_id)
     conanfile.package_folder = package_folder
     hook_manager.execute("post_package", conanfile=conanfile, conanfile_path=conanfile_path,
@@ -45,7 +44,6 @@ def create_package(conanfile, package_id, source_folder, build_folder, package_f
     package folder
     """
     mkdir(package_folder)
-
     output = conanfile.output
     # Make the copy of all the patterns
     output.info("Generating the package")
@@ -63,25 +61,16 @@ def create_package(conanfile, package_id, source_folder, build_folder, package_f
         package_output = ScopedOutput("%s package()" % output.scope, output)
         output.highlight("Calling package()")
 
-        def recipe_has(attribute):
-            return attribute in conanfile.__class__.__dict__
-
         if source_folder != build_folder:
             conanfile.copy = FileCopier(source_folder, package_folder, build_folder)
             with conanfile_exception_formatter(str(conanfile), "package"):
                 with tools.chdir(source_folder):
                     conanfile.package()
-            copy_done = conanfile.copy.report(package_output)
-            if not copy_done and recipe_has("package"):
-                output.warn("No files copied from source folder!")
 
         conanfile.copy = FileCopier(build_folder, package_folder)
         with tools.chdir(build_folder):
             with conanfile_exception_formatter(str(conanfile), "package"):
                 conanfile.package()
-        copy_done = conanfile.copy.report(package_output)
-        if not copy_done and recipe_has("build") and recipe_has("package"):
-            output.warn("No files copied from build folder!")
     except Exception as e:
         if not local:
             os.chdir(build_folder)
@@ -96,6 +85,7 @@ def create_package(conanfile, package_id, source_folder, build_folder, package_f
         raise ConanException(e)
 
     _create_aux_files(install_folder, package_folder, conanfile, copy_info)
+    _report_files_from_manifest(package_output, package_folder)
     package_id = package_id or os.path.basename(package_folder)
     output.success("Package '%s' created" % package_id)
     hook_manager.execute("post_package", conanfile=conanfile, conanfile_path=conanfile_path,
@@ -120,3 +110,15 @@ def _create_aux_files(install_folder, package_folder, conanfile, copy_info):
     # Create the digest for the package
     digest = FileTreeManifest.create(package_folder)
     digest.save(package_folder)
+
+
+def _report_files_from_manifest(output, package_folder):
+    digest = FileTreeManifest.load(package_folder)
+    copied_files = list(digest.files())
+    copied_files.remove(CONANINFO)
+
+    if not copied_files:
+        output.warn("No files in this package!")
+        return
+
+    report_copied_files(copied_files, output, message_suffix="Packaged")
diff --git a/conans/model/graph_info.py b/conans/model/graph_info.py
index 0491a6bb9..f378d4510 100644
--- a/conans/model/graph_info.py
+++ b/conans/model/graph_info.py
@@ -2,10 +2,11 @@ import json
 import os
 
 from conans.client.profile_loader import _load_profile
+from conans.errors import ConanException
 from conans.model.options import OptionsValues
+from conans.model.ref import ConanFileReference
 from conans.tools import save
 from conans.util.files import load
-from conans.model.ref import ConanFileReference
 
 
 GRAPH_INFO_FILE = "graph_info.json"
@@ -24,7 +25,11 @@ class GraphInfo(object):
         if not path:
             raise IOError("Invalid path")
         p = path if os.path.isfile(path) else os.path.join(path, GRAPH_INFO_FILE)
-        return GraphInfo.loads(load(p))
+        content = load(p)
+        try:
+            return GraphInfo.loads(content)
+        except Exception as e:
+            raise ConanException("Error parsing GraphInfo from file '{}': {}".format(p, e))
 
     @staticmethod
     def loads(text):
@@ -38,7 +43,7 @@ class GraphInfo(object):
             options = None
         else:
             options = OptionsValues(options)
-        root = graph_json["root"]
+        root = graph_json.get("root", {"name": None, "version": None, "user": None, "channel": None})
         root_ref = ConanFileReference(root["name"], root["version"], root["user"], root["channel"],
                                       validate=False)
         return GraphInfo(profile=profile, options=options, root_ref=root_ref)


Test patch:
diff --git a/conans/test/functional/command/create_test.py b/conans/test/functional/command/create_test.py
index 2e348cfbe..0d9a59cca 100644
--- a/conans/test/functional/command/create_test.py
+++ b/conans/test/functional/command/create_test.py
@@ -95,7 +95,7 @@ class MyPkg(ConanFile):
         self.assertIn("Pkg/0.1@lasote/testing: mysource!!", client.out)
         self.assertIn("Pkg/0.1@lasote/testing: mybuild!!", client.out)
         self.assertIn("Pkg/0.1@lasote/testing: mypackage!!", client.out)
-        self.assertIn("Pkg/0.1@lasote/testing package(): Copied 1 '.h' file: header.h", client.out)
+        self.assertIn("Pkg/0.1@lasote/testing package(): Packaged 1 '.h' file: header.h", client.out)
         # keep the source
         client.save({"conanfile.py": conanfile + " "})
         client.run("create . Pkg/0.1@lasote/testing --keep-source")
@@ -103,7 +103,7 @@ class MyPkg(ConanFile):
         self.assertNotIn("Pkg/0.1@lasote/testing: mysource!!", client.out)
         self.assertIn("Pkg/0.1@lasote/testing: mybuild!!", client.out)
         self.assertIn("Pkg/0.1@lasote/testing: mypackage!!", client.out)
-        self.assertIn("Pkg/0.1@lasote/testing package(): Copied 1 '.h' file: header.h", client.out)
+        self.assertIn("Pkg/0.1@lasote/testing package(): Packaged 1 '.h' file: header.h", client.out)
         # keep build
         client.run("create . Pkg/0.1@lasote/testing --keep-build")
         self.assertIn("Pkg/0.1@lasote/testing: Won't be built as specified by --keep-build",
@@ -111,7 +111,7 @@ class MyPkg(ConanFile):
         self.assertNotIn("Pkg/0.1@lasote/testing: mysource!!", client.out)
         self.assertNotIn("Pkg/0.1@lasote/testing: mybuild!!", client.out)
         self.assertIn("Pkg/0.1@lasote/testing: mypackage!!", client.out)
-        self.assertIn("Pkg/0.1@lasote/testing package(): Copied 1 '.h' file: header.h", client.out)
+        self.assertIn("Pkg/0.1@lasote/testing package(): Packaged 1 '.h' file: header.h", client.out)
 
         # Changes in the recipe again
         client.save({"conanfile.py": conanfile})
@@ -123,7 +123,7 @@ class MyPkg(ConanFile):
         self.assertNotIn("Pkg/0.1@lasote/testing: mysource!!", client.out)
         self.assertNotIn("Pkg/0.1@lasote/testing: mybuild!!", client.out)
         self.assertIn("Pkg/0.1@lasote/testing: mypackage!!", client.out)
-        self.assertIn("Pkg/0.1@lasote/testing package(): Copied 1 '.h' file: header.h", client.out)
+        self.assertIn("Pkg/0.1@lasote/testing package(): Packaged 1 '.h' file: header.h", client.out)
 
     def keep_build_error_test(self):
         client = TestClient()
diff --git a/conans/test/functional/command/export_pkg_test.py b/conans/test/functional/command/export_pkg_test.py
index 96ad56c57..d387904b2 100644
--- a/conans/test/functional/command/export_pkg_test.py
+++ b/conans/test/functional/command/export_pkg_test.py
@@ -81,7 +81,7 @@ class HelloPythonConan(ConanFile):
                       client.out)
 
         client.run("export-pkg . Hello/0.1@lasote/stable -pf=pkg")
-        self.assertIn("Hello/0.1@lasote/stable: WARN: No files copied from package folder!",
+        self.assertIn("Hello/0.1@lasote/stable: WARN: No files in this package!",
                       client.out)
 
     def test_package_folder(self):
@@ -100,7 +100,8 @@ class HelloPythonConan(ConanFile):
 
         client.run("export-pkg . Hello/0.1@lasote/stable -pf=pkg -pr=profile")
         self.assertNotIn("PACKAGE NOT CALLED", client.out)
-        self.assertIn("Hello/0.1@lasote/stable: Copied 1 '.h' file: myfile.h", client.out)
+        self.assertIn("Hello/0.1@lasote/stable: Packaged 1 '.h' file: myfile.h", client.out)
+        self.assertNotIn("No files in this package!", client.out)
         ref = ConanFileReference.loads("Hello/0.1@lasote/stable")
         pkg_folder = client.cache.packages(ref)
         folders = os.listdir(pkg_folder)
@@ -271,10 +272,10 @@ class TestConan(ConanFile):
         client.run("export-pkg . Hello/0.1@lasote/stable %s" % settings)
         self.assertIn("Hello/0.1@lasote/stable: A new conanfile.py version was exported",
                       client.out)
-        self.assertNotIn("Hello/0.1@lasote/stable package(): WARN: No files copied",
+        self.assertNotIn("Hello/0.1@lasote/stable package(): WARN: No files in this package!",
                          client.out)  # --bare include a now mandatory package() method!
 
-        self.assertIn("Copied 1 '.a' file: libmycoollib.a", client.out)
+        self.assertIn("Packaged 1 '.a' file: libmycoollib.a", client.out)
         self._consume(client, settings + " . -g cmake")
 
         cmakeinfo = load(os.path.join(client.current_folder, "conanbuildinfo.cmake"))
@@ -380,7 +381,7 @@ class TestConan(ConanFile):
         # Partial reference is ok
         client.save({CONANFILE: conanfile, "file.txt": "txt contents"})
         client.run("export-pkg . conan/stable")
-        self.assertIn("Hello/0.1@conan/stable package(): Copied 1 '.txt' file: file.txt", client.out)
+        self.assertIn("Hello/0.1@conan/stable package(): Packaged 1 '.txt' file: file.txt", client.out)
 
         # Specify different name or version is not working
         client.run("export-pkg . lib/1.0@conan/stable -f", assert_error=True)
@@ -400,7 +401,7 @@ class TestConan(ConanFile):
         # Partial reference is ok
         client.save({CONANFILE: conanfile, "file.txt": "txt contents"})
         client.run("export-pkg . anyname/1.222@conan/stable")
-        self.assertIn("anyname/1.222@conan/stable package(): Copied 1 '.txt' file: file.txt",
+        self.assertIn("anyname/1.222@conan/stable package(): Packaged 1 '.txt' file: file.txt",
                       client.out)
 
     def test_with_deps(self):
diff --git a/conans/test/functional/command/info_test.py b/conans/test/functional/command/info_test.py
index 92a93a078..33bcb2506 100644
--- a/conans/test/functional/command/info_test.py
+++ b/conans/test/functional/command/info_test.py
@@ -9,6 +9,7 @@ from conans.paths import CONANFILE
 from conans.test.utils.cpp_test_files import cpp_hello_conan_files
 from conans.test.utils.tools import TestClient
 from conans.util.files import load, save
+from conans.test.utils.conanfile import TestConanFile
 
 
 class InfoTest(unittest.TestCase):
@@ -611,3 +612,20 @@ class MyTest(ConanFile):
         html_content = load(html_path)
         self.assertIn("<h3>Pkg/0.2@lasote/testing</h3>", html_content)
         self.assertIn("<li><b>topics</b>: foo", html_content)
+
+    def wrong_graph_info_test(self):
+        # https://github.com/conan-io/conan/issues/4443
+        conanfile = TestConanFile()
+        client = TestClient()
+        client.save({"conanfile.py": str(conanfile)})
+        client.run("install .")
+        path = os.path.join(client.current_folder, "graph_info.json")
+        graph_info = load(path)
+        graph_info = json.loads(graph_info)
+        graph_info.pop("root")
+        save(path, json.dumps(graph_info))
+        client.run("info .")
+        self.assertIn("conanfile.py (Hello/0.1@None/None)", client.out)
+        save(path, "broken thing")
+        client.run("info .", assert_error=True)
+        self.assertIn("ERROR: Error parsing GraphInfo from file", client.out)
diff --git a/conans/test/functional/command/package_test.py b/conans/test/functional/command/package_test.py
index 53f80947d..4c42f12ed 100644
--- a/conans/test/functional/command/package_test.py
+++ b/conans/test/functional/command/package_test.py
@@ -47,7 +47,7 @@ class Pkg(ConanFile):
         # In current dir subdir
         prepare_for_package(client)
         client.run("package . --build-folder build2 --install-folder build --package-folder=subdir")
-        self.assertNotIn("package(): WARN: No files copied", client.out)
+        self.assertNotIn("package(): WARN: No files in this package!", client.out)
         self.assertTrue(os.path.exists(os.path.join(client.current_folder, "subdir")))
 
         # In current dir subdir with conanfile path
@@ -59,7 +59,7 @@ class Pkg(ConanFile):
         # Default path
         prepare_for_package(client)
         client.run("package . --build-folder build")
-        self.assertNotIn("package(): WARN: No files copied", client.out)
+        self.assertNotIn("package(): WARN: No files in this package!", client.out)
         self.assertTrue(os.path.exists(os.path.join(client.current_folder, "build", "package")))
 
         # Default path with conanfile path
@@ -71,7 +71,7 @@ class Pkg(ConanFile):
         prepare_for_package(client)
         pf = os.path.join(client.current_folder, "mypackage/two")
         client.run("package . --build-folder build --package-folder='%s'" % pf)
-        self.assertNotIn("package(): WARN: No files copied", client.out)
+        self.assertNotIn("package(): WARN: No files in this package!", client.out)
         self.assertTrue(os.path.exists(os.path.join(client.current_folder, "mypackage", "two")))
 
         # Abs path with conanfile path
@@ -207,8 +207,8 @@ class MyConan(ConanFile):
             client.run('package "{0}" --build-folder="{1}/build" '
                        '--package-folder="{2}" --source-folder="{1}/src"'.
                        format(path, conanfile_folder, package_folder))
-        self.assertNotIn("package(): Copied 1 \'\' file", client.out)
-        self.assertIn("package(): Copied 1 file: myapp", client.out)
+        self.assertNotIn("package(): Packaged 1 \'\' file", client.out)
+        self.assertIn("package(): Packaged 1 file: myapp", client.out)
         content = load(os.path.join(package_folder, "include/file.h"))
         self.assertEqual(content, "foo")
         self.assertEqual(sorted(os.listdir(package_folder)),
@@ -234,8 +234,8 @@ class MyConan(ConanFile):
                      CONANFILE: conanfile})
         client.run("install . --install-folder=install")
         client.run('package . --source-folder=source --install-folder=install --build-folder=build')
-        self.assertIn("No files copied from source folder!", client.out)
-        self.assertIn("Copied 1 '.lib' file: library.lib", client.out)
+        self.assertNotIn("No files in this package!", client.out)
+        self.assertIn("package(): Packaged 1 '.lib' file: library.lib", client.out)
 
         conanfile = """
 from conans import ConanFile
@@ -248,8 +248,8 @@ class MyConan(ConanFile):
 """
         client.save({CONANFILE: conanfile})
         client.run('package . --source-folder=source --install-folder=install --build-folder=build')
-        self.assertIn("No files copied from build folder!", client.out)
-        self.assertIn("Copied 1 '.h' file: file.h", client.out)
+        self.assertNotIn("No files in this package!", client.out)
+        self.assertIn("package(): Packaged 1 '.h' file: file.h", client.out)
 
         conanfile = """
 from conans import ConanFile
@@ -262,7 +262,53 @@ class MyConan(ConanFile):
 """
         client.save({CONANFILE: conanfile})
         client.run('package . --source-folder=source --install-folder=install --build-folder=build')
-        self.assertIn("No files copied from source folder!", client.out)
-        self.assertIn("No files copied from build folder!", client.out)
-        self.assertNotIn("Copied 1 '.h' file: file.h", client.out)
-        self.assertNotIn("Copied 1 '.lib' file: library.lib", client.out)
+        self.assertNotIn("No files in this package!", client.out)
+        self.assertIn("package(): Packaged 1 '.h' file: file.h", client.out)
+        self.assertIn("package(): Packaged 1 '.lib' file: library.lib", client.out)
+
+    def installer_package_test(self):
+        """ Simulates installers when packaging the file e.g. cmake install
+            The package() does not use self.copy, but files are copied to package folder
+        """
+        client = TestClient()
+        conanfile = """
+import os
+from conans import ConanFile, tools
+
+class MyConan(ConanFile):
+    def build(self):
+        pass
+    def package(self):
+        tools.save(os.path.join(self.package_folder, "LICENSE.md"), "my license")
+"""
+        client.save({CONANFILE: conanfile})
+        client.run("install .")
+        client.run("package .")
+        self.assertNotIn("No files in this package!", client.out)
+        self.assertIn("package(): Packaged 1 '.md' file: LICENSE.md", client.out)
+        self.assertIn("conanfile.py: Package 'package' created", client.out)
+
+    def empty_package_folder_test(self):
+        """ When the package folder is empty, then an warning should appear
+            and no files must be listed
+        """
+        conanfile = """from conans import ConanFile
+import os
+
+class Pkg(ConanFile):
+    name = "lib"
+    version = "1.0"
+
+    def build(self):
+        pass
+
+    def package(self):
+        self.output.info("yet another conan package")
+"""
+        client = TestClient()
+        client.save({"conanfile.py": conanfile})
+        client.run("install .")
+        client.run("package .")
+        self.assertIn("yet another conan package", client.out)
+        self.assertIn("package(): WARN: No files in this package!", client.out)
+        self.assertNotIn("package(): Packaged", client.out)
diff --git a/conans/test/functional/conanfile/imports_tests.py b/conans/test/functional/conanfile/imports_tests.py
index 85e7bb473..de057df23 100644
--- a/conans/test/functional/conanfile/imports_tests.py
+++ b/conans/test/functional/conanfile/imports_tests.py
@@ -51,7 +51,7 @@ class TestConan(ConanFile):
 """
         client.save({"conanfile.py": conanfile}, clean_first=True)
         client.run("create . Pkg/0.1@user/testing --build=missing")
-        self.assertIn("Pkg/0.1@user/testing package(): Copied 1 '.md' file: LICENSE.md",
+        self.assertIn("Pkg/0.1@user/testing package(): Packaged 1 '.md' file: LICENSE.md",
                       client.out)
         pref = PackageReference(ConanFileReference.loads("Pkg/0.1@user/testing"),
                                 "e6f2dac07251ad9958120a7f7c324366fb3b6f2a")
diff --git a/conans/test/functional/editable/transitive_editable_test.py b/conans/test/functional/editable/transitive_editable_test.py
new file mode 100644
index 000000000..c340769a6
--- /dev/null
+++ b/conans/test/functional/editable/transitive_editable_test.py
@@ -0,0 +1,32 @@
+# coding=utf-8
+
+import os
+import unittest
+
+from conans.test.utils.tools import TestClient
+from conans.test.utils.conanfile import TestConanFile
+from conans.util.files import mkdir
+
+
+class TransitiveEditableTest(unittest.TestCase):
+
+    def test_transitive_editables(self):
+        # https://github.com/conan-io/conan/issues/4445
+        client = TestClient()
+        conanfileC = TestConanFile("LibC", "0.1")
+        client.save({"conanfile.py": str(conanfileC)})
+        client.run("link . LibC/0.1@user/testing")
+
+        client2 = TestClient(client.base_folder)
+        conanfileB = TestConanFile("LibB", "0.1", requires=["LibC/0.1@user/testing"])
+
+        client2.save({"conanfile.py": str(conanfileB)})
+        client2.run("create . user/testing")
+
+        conanfileA = TestConanFile("LibA", "0.1", requires=["LibB/0.1@user/testing",
+                                                            "LibC/0.1@user/testing"])
+        client2.save({"conanfile.py": str(conanfileA)})
+        client2.run("install .")
+        client2.current_folder = os.path.join(client2.current_folder, "build")
+        mkdir(client2.current_folder)
+        client2.run("install ..")
diff --git a/conans/test/functional/old/conan_trace_file_test.py b/conans/test/functional/old/conan_trace_file_test.py
index 9f7f65fea..52a9c3e0b 100644
--- a/conans/test/functional/old/conan_trace_file_test.py
+++ b/conans/test/functional/old/conan_trace_file_test.py
@@ -57,21 +57,21 @@ class HelloConan(ConanFile):
             return log_file_packaged_, client.user_io.out
 
         log_file_packaged, output = _install_a_package(False, True)
-        self.assertIn("Copied 1 '.log' file: conan_run.log", output)
+        self.assertIn("Packaged 1 '.log' file: conan_run.log", output)
         self.assertTrue(os.path.exists(log_file_packaged))
         contents = load(log_file_packaged)
         self.assertIn("Simulating cmake...", contents)
         self.assertNotIn("----Running------%s> echo" % os.linesep, contents)
 
         log_file_packaged, output = _install_a_package(True, True)
-        self.assertIn("Copied 1 '.log' file: conan_run.log", output)
+        self.assertIn("Packaged 1 '.log' file: conan_run.log", output)
         self.assertTrue(os.path.exists(log_file_packaged))
         contents = load(log_file_packaged)
         self.assertIn("Simulating cmake...", contents)
         self.assertIn("----Running------%s> echo" % os.linesep, contents)
 
         log_file_packaged, output = _install_a_package(False, False)
-        self.assertNotIn("Copied 1 '.log' file: conan_run.log", output)
+        self.assertNotIn("Packaged 1 '.log' file: conan_run.log", output)
         self.assertFalse(os.path.exists(log_file_packaged))
 
     def test_trace_actions(self):
diff --git a/conans/test/integration/complete_test.py b/conans/test/integration/complete_test.py
index ba8a21306..ea37d0c65 100644
--- a/conans/test/integration/complete_test.py
+++ b/conans/test/integration/complete_test.py
@@ -23,7 +23,7 @@ class CompleteFlowTest(unittest.TestCase):
         files = cpp_hello_conan_files("Hello0", "0.1", build=False)
         self.client.save(files)
         self.client.run("create . lasote/stable")
-        self.assertIn("Hello0/0.1@lasote/stable package(): Copied 1 '.h' file: helloHello0.h",
+        self.assertIn("Hello0/0.1@lasote/stable package(): Packaged 1 '.h' file: helloHello0.h",
                       self.client.out)
 
         # Upload package
@@ -53,7 +53,7 @@ class CompleteFlowTest(unittest.TestCase):
         files = cpp_hello_conan_files("Hello0", "0.1", need_patch=True)
         self.client.save(files)
         self.client.run("create . lasote/stable")
-        self.assertIn("Hello0/0.1@lasote/stable package(): Copied 1 '.h' file: helloHello0.h",
+        self.assertIn("Hello0/0.1@lasote/stable package(): Packaged 1 '.h' file: helloHello0.h",
                       self.client.out)
         # Check compilation ok
         package_ids = self.client.cache.conan_packages(ref)