Repository: conan-io/conan

Issue:
Useless warning when calling conan package
Conan version: `1.2.3`
With the command:
```
conan package --source-folder /path/to/myProject --build-folder /path/to/myProject/build --package-folder /path/to/myPorject/../../Staging /path/to/myProject
```
And the `package()` in `/path/to/myProject/conanfile.py`:
```
    def package(self):
        self.copy("myProject", src="bin", dst="linux64/bin")
        self.copy("myProject.exe", src="bin", dst="win32/bin")
```
The following output is produced:
```
PROJECT: Generating the package
PROJECT: Package folder /path/to/myPorject/../../Staging
PROJECT: Calling package()
PROJECT package(): WARN: No files copied!
PROJECT package(): Copied 1 '' files: myProject
PROJECT: Package 'Pre-release' created
```

The warning: `PROJECT package(): WARN: No files copied!` in this scenario is useless, and even misleading.


Discussion:
I think it is accurate and matching the specification. You are telling conan to copy a file/pattern. One of the files/patterns specified is not there, and it is telling that it didn't copy anything. I would say it is a total valid warning. If you want to get rid of your warning, you should code it accordingly:
```python
def package(self):
    if self.settings.os == "Windows":
        self.copy("myProject.exe", ...)
    else: ...
```
With the `package()`:
```
    def package(self):
        if self.settings.os == "Linux":
            self.copy("myProject", src="bin", dst="linux64/bin")
        if self.settings.os == "Windows":
            self.copy("myProject.exe", src="bin", dst="win32/bin")
```

I get the exact same output.

Also please note the line: `PROJECT package(): Copied 1 '' files: myProject`. It contains `''`, which I don't think is right.
I think this waring is there because when in local flow, the package method is actually called twice:

https://github.com/conan-io/conan/blob/ac25c8994878706bac23efd009ae74bc294c1add/conans/client/packager.py#L52

In the case of the `''` this is indicating the extensions of the file. As you are building an executable in linux, it has no extension.

I think both messages could be improved for the local flow

Resolving patch:
diff --git a/conans/client/file_copier.py b/conans/client/file_copier.py
--- a/conans/client/file_copier.py
+++ b/conans/client/file_copier.py
@@ -6,18 +6,23 @@
 from conans import tools
 
 
-def report_copied_files(copied, output, warn=False):
+def report_copied_files(copied, output):
     ext_files = defaultdict(list)
     for f in copied:
         _, ext = os.path.splitext(f)
         ext_files[ext].append(os.path.basename(f))
 
-    for ext, files in ext_files.items():
-        files_str = (": " + ", ".join(files)) if len(files) < 5 else ""
-        output.info("Copied %d '%s' files%s" % (len(files), ext, files_str))
+    if not ext_files:
+        return False
 
-    if warn and not ext_files:
-        output.warn("No files copied!")
+    for ext, files in ext_files.items():
+        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))
+        else:
+            output.info("Copied %d '%s' %s: %s" % (len(files), ext, file_or_files, files_str))
+    return True
 
 
 class FileCopier(object):
@@ -42,8 +47,8 @@ def __init__(self, root_source_folder, root_destination_folder, excluded=None):
         if excluded:
             self._excluded.append(excluded)
 
-    def report(self, output, warn=False):
-        report_copied_files(self._copied, output, warn)
+    def report(self, output):
+        return report_copied_files(self._copied, output)
 
     def __call__(self, pattern, dst="", src="", keep_path=True, links=False, symlinks=None,
                  excludes=None, ignore_case=False):
diff --git a/conans/client/packager.py b/conans/client/packager.py
--- a/conans/client/packager.py
+++ b/conans/client/packager.py
@@ -15,11 +15,14 @@ def export_pkg(conanfile, src_package_folder, package_folder, output):
     mkdir(package_folder)
 
     output.info("Exporting to cache existing package from user folder")
-    output.info("Package folder %s" % (package_folder))
+    output.info("Package folder %s" % package_folder)
 
     copier = FileCopier(src_package_folder, package_folder)
     copier("*", symlinks=True)
-    copier.report(output, warn=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)
@@ -29,14 +32,14 @@ def export_pkg(conanfile, src_package_folder, package_folder, output):
 
 def create_package(conanfile, source_folder, build_folder, package_folder, install_folder,
                    output, local=False, copy_info=False):
-    """ copies built artifacts, libs, headers, data, etc from build_folder to
+    """ copies built artifacts, libs, headers, data, etc. from build_folder to
     package folder
     """
     mkdir(package_folder)
 
     # Make the copy of all the patterns
     output.info("Generating the package")
-    output.info("Package folder %s" % (package_folder))
+    output.info("Package folder %s" % package_folder)
 
     try:
         package_output = ScopedOutput("%s package()" % output.scope, output)
@@ -46,7 +49,7 @@ def create_package(conanfile, source_folder, build_folder, package_folder, insta
         conanfile.install_folder = install_folder
         conanfile.build_folder = build_folder
 
-        def recipe_has(conanfile, attribute):
+        def recipe_has(attribute):
             return attribute in conanfile.__class__.__dict__
 
         if source_folder != build_folder:
@@ -54,15 +57,17 @@ def recipe_has(conanfile, attribute):
             with conanfile_exception_formatter(str(conanfile), "package"):
                 with tools.chdir(source_folder):
                     conanfile.package()
-            warn = recipe_has(conanfile, "package")
-            conanfile.copy.report(package_output, warn=warn)
+            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()
-        warn = recipe_has(conanfile, "build") and recipe_has(conanfile, "package")
-        conanfile.copy.report(package_output, warn=warn)
+        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)


Test patch:
diff --git a/conans/test/build_helpers/msbuild_test.py b/conans/test/build_helpers/msbuild_test.py
--- a/conans/test/build_helpers/msbuild_test.py
+++ b/conans/test/build_helpers/msbuild_test.py
@@ -45,7 +45,7 @@ def package(self):
         self.assertTrue(error)
         client.run('create . Hello/1.2.1@lasote/stable -s cppstd=17 '
                    '-s compiler="Visual Studio" -s compiler.version=14')
-        self.assertIn("Copied 1 '.exe' files: MyProject.exe", client.user_io.out)
+        self.assertIn("Copied 1 '.exe' file: MyProject.exe", client.user_io.out)
 
         files = get_vs_project_files()
         files[CONANFILE] = conan_build_vs
@@ -64,18 +64,18 @@ def package(self):
         client.run("export . lasote/stable")
         client.run("install Hello/1.2.1@lasote/stable --build -s arch=x86_64")
         self.assertIn("Release|x64", client.user_io.out)
-        self.assertIn("Copied 1 '.exe' files: MyProject.exe", client.user_io.out)
+        self.assertIn("Copied 1 '.exe' file: MyProject.exe", client.user_io.out)
 
         # Try with x86
         client.save(files, clean_first=True)
         client.run("export . lasote/stable")
         client.run("install Hello/1.2.1@lasote/stable --build -s arch=x86")
         self.assertIn("Release|x86", client.user_io.out)
-        self.assertIn("Copied 1 '.exe' files: MyProject.exe", client.user_io.out)
+        self.assertIn("Copied 1 '.exe' file: MyProject.exe", client.user_io.out)
 
         # Try with x86 debug
         client.save(files, clean_first=True)
         client.run("export . lasote/stable")
         client.run("install Hello/1.2.1@lasote/stable --build -s arch=x86 -s build_type=Debug")
         self.assertIn("Debug|x86", client.user_io.out)
-        self.assertIn("Copied 1 '.exe' files: MyProject.exe", client.user_io.out)
+        self.assertIn("Copied 1 '.exe' file: MyProject.exe", client.user_io.out)
diff --git a/conans/test/command/create_test.py b/conans/test/command/create_test.py
--- a/conans/test/command/create_test.py
+++ b/conans/test/command/create_test.py
@@ -66,7 +66,7 @@ def test(self):
         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' files: header.h", client.out)
+        self.assertIn("Pkg/0.1@lasote/testing package(): Copied 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")
@@ -74,14 +74,14 @@ def test(self):
         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' files: header.h", client.out)
+        self.assertIn("Pkg/0.1@lasote/testing package(): Copied 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", client.out)
         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' files: header.h", client.out)
+        self.assertIn("Pkg/0.1@lasote/testing package(): Copied 1 '.h' file: header.h", client.out)
 
         # Changes in the recipe again
         client.save({"conanfile.py": conanfile})
@@ -92,7 +92,7 @@ def test(self):
         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' files: header.h", client.out)
+        self.assertIn("Pkg/0.1@lasote/testing package(): Copied 1 '.h' file: header.h", client.out)
 
     def keep_build_error_test(self):
         client = TestClient()
diff --git a/conans/test/command/export_pkg_test.py b/conans/test/command/export_pkg_test.py
--- a/conans/test/command/export_pkg_test.py
+++ b/conans/test/command/export_pkg_test.py
@@ -33,7 +33,8 @@ 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!", client.out)
+        self.assertIn("Hello/0.1@lasote/stable: WARN: No files copied from package folder!",
+                      client.out)
 
     def test_package_folder(self):
         # https://github.com/conan-io/conan/issues/2350
@@ -51,7 +52,7 @@ def package(self):
 
         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' files: myfile.h", client.out)
+        self.assertIn("Hello/0.1@lasote/stable: Copied 1 '.h' file: myfile.h", client.out)
         ref = ConanFileReference.loads("Hello/0.1@lasote/stable")
         pkg_folder = client.client_cache.packages(ref)
         folders = os.listdir(pkg_folder)
@@ -227,10 +228,10 @@ def test_new(self):
         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 copied",
                          client.out)  # --bare include a now mandatory package() method!
 
-        self.assertIn("Copied 1 '.a' files: libmycoollib.a", client.out)
+        self.assertIn("Copied 1 '.a' file: libmycoollib.a", client.out)
         self._consume(client, settings + " . -g cmake")
 
         cmakeinfo = load(os.path.join(client.current_folder, "conanbuildinfo.cmake"))
@@ -336,7 +337,7 @@ def package(self):
         # 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' files: file.txt", client.out)
+        self.assertIn("Hello/0.1@conan/stable package(): Copied 1 '.txt' file: file.txt", client.out)
 
         # Specify different name or version is not working
         error = client.run("export-pkg . lib/1.0@conan/stable -f", ignore_error=True)
@@ -360,7 +361,7 @@ def package(self):
         # 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' files: file.txt", client.out)
+        self.assertIn("anyname/1.222@conan/stable package(): Copied 1 '.txt' file: file.txt", client.out)
 
     def test_with_deps(self):
         client = TestClient()
diff --git a/conans/test/command/package_test.py b/conans/test/command/package_test.py
--- a/conans/test/command/package_test.py
+++ b/conans/test/command/package_test.py
@@ -19,17 +19,16 @@ def prepare_for_package(the_client):
             the_client.run("new lib/1.0 -s")
 
             # don't need build method
-            tools.replace_in_file(os.path.join(client.current_folder,
-                                               "conanfile.py"),
-                                               "def build",
-                                               "def skip_build")
+            tools.replace_in_file(os.path.join(client.current_folder, "conanfile.py"),
+                                  "def build",
+                                  "def skip_build")
             the_client.run("install . --install-folder build")
             mkdir(os.path.join(client.current_folder, "build2"))
 
         # 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 copied", client.out)
         self.assertTrue(os.path.exists(os.path.join(client.current_folder, "subdir")))
 
         # In current dir subdir with conanfile path
@@ -40,7 +39,7 @@ def prepare_for_package(the_client):
         # 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 copied", client.out)
         self.assertTrue(os.path.exists(os.path.join(client.current_folder, "build", "package")))
 
         # Default path with conanfile path
@@ -52,7 +51,7 @@ def prepare_for_package(the_client):
         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 copied", client.out)
         self.assertTrue(os.path.exists(os.path.join(client.current_folder, "mypackage", "two")))
 
         # Abs path with conanfile path
@@ -162,10 +161,12 @@ class MyConan(ConanFile):
     def package(self):
         self.copy(pattern="*.h", dst="include", src="include")
         self.copy(pattern="*.lib")
+        self.copy(pattern="myapp", src="bin", dst="bin")
 """
 
         client.save({"src/include/file.h": "foo",
                      "build/lib/mypkg.lib": "mylib",
+                     "build/bin/myapp": "",
                      CONANFILE: conanfile_template})
         conanfile_folder = client.current_folder
         path = conanfile_folder
@@ -185,9 +186,62 @@ def package(self):
             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)
         content = load(os.path.join(package_folder, "include/file.h"))
         self.assertEqual(content, "foo")
         self.assertEqual(sorted(os.listdir(package_folder)),
-                         sorted(["include", "lib", "conaninfo.txt", "conanmanifest.txt"]))
+                         sorted(["include", "lib", "bin", "conaninfo.txt", "conanmanifest.txt"]))
         self.assertEqual(os.listdir(os.path.join(package_folder, "include")), ["file.h"])
         self.assertEqual(os.listdir(os.path.join(package_folder, "lib")), ["mypkg.lib"])
+        self.assertEqual(os.listdir(os.path.join(package_folder, "bin")), ["myapp"])
+
+    def no_files_copied_local_package_test(self):
+        # https://github.com/conan-io/conan/issues/2753
+        client = TestClient()
+        conanfile = """
+from conans import ConanFile
+
+class MyConan(ConanFile):
+    def build(self):
+        pass
+    def package(self):
+        self.copy(pattern="*.lib")
+"""
+        client.save({"source/include/file.h": "foo",
+                     "build/bin/library.lib": "",
+                     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)
+
+        conanfile = """
+from conans import ConanFile
+
+class MyConan(ConanFile):
+    def build(self):
+        pass
+    def package(self):
+        self.copy(pattern="*.h")
+"""
+        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)
+
+        conanfile = """
+from conans import ConanFile
+
+class MyConan(ConanFile):
+    def build(self):
+        pass
+    def package(self):
+        self.copy(pattern="*.fake")
+"""
+        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)
diff --git a/conans/test/functional/conan_trace_file_test.py b/conans/test/functional/conan_trace_file_test.py
--- a/conans/test/functional/conan_trace_file_test.py
+++ b/conans/test/functional/conan_trace_file_test.py
@@ -55,21 +55,21 @@ def _install_a_package(print_commands_to_output, generate_run_log_file):
             return log_file_packaged, client.user_io.out
 
         log_file_packaged, output = _install_a_package(False, True)
-        self.assertIn("Copied 1 '.log' files: conan_run.log", output)
+        self.assertIn("Copied 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' files: conan_run.log", output)
+        self.assertIn("Copied 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' files: conan_run.log", output)
+        self.assertNotIn("Copied 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/functional/deploy_test.py b/conans/test/functional/deploy_test.py
--- a/conans/test/functional/deploy_test.py
+++ b/conans/test/functional/deploy_test.py
@@ -55,9 +55,9 @@ def test_install_in(folder):
             client.current_folder = temp_folder()
             client.run("install Pkg/0.1@user/testing --install-folder=%s" % folder)
 
-            self.assertIn("Pkg/0.1@user/testing deploy(): Copied 1 '.dll' files: mylib.dll",
+            self.assertIn("Pkg/0.1@user/testing deploy(): Copied 1 '.dll' file: mylib.dll",
                           client.out)
-            self.assertIn("Pkg/0.1@user/testing deploy(): Copied 1 '.exe' files: myapp.exe",
+            self.assertIn("Pkg/0.1@user/testing deploy(): Copied 1 '.exe' file: myapp.exe",
                           client.out)
             deploy_manifest = FileTreeManifest.loads(load(os.path.join(client.current_folder,
                                                                        folder,
diff --git a/conans/test/functional/imports_tests.py b/conans/test/functional/imports_tests.py
--- a/conans/test/functional/imports_tests.py
+++ b/conans/test/functional/imports_tests.py
@@ -51,7 +51,7 @@ def package(self):
 """
         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' files: LICENSE.md",
+        self.assertIn("Pkg/0.1@user/testing package(): Copied 1 '.md' file: LICENSE.md",
                       client.out)
         pkg_ref = PackageReference(ConanFileReference.loads("Pkg/0.1@user/testing"),
                                    "e6f2dac07251ad9958120a7f7c324366fb3b6f2a")
diff --git a/conans/test/integration/complete_test.py b/conans/test/integration/complete_test.py
--- a/conans/test/integration/complete_test.py
+++ b/conans/test/integration/complete_test.py
@@ -26,7 +26,7 @@ def reuse_test(self, complete_urls):
         self.client.run("export . lasote/stable")
         self.client.run("install %s --build missing" % str(conan_reference))
 
-        self.assertIn("Hello0/0.1@lasote/stable package(): Copied 1 '.h' files: helloHello0.h",
+        self.assertIn("Hello0/0.1@lasote/stable package(): Copied 1 '.h' file: helloHello0.h",
                       self.client.user_io.out)
         # Check compilation ok
         package_ids = self.client.paths.conan_packages(conan_reference)