Repository: conan-io/conan

Issue:
imports/copy question
Suppose I have
```
x86
  vc15
    bin
      opencv_core310.dll 
      ...
```
directory structure in my package. Is it possible to import `opencv_core310.dll` and friends to project's `bin` folder forgetting all the directory structure? I tried
```python
def imports(self):
   self.copy("*.dll", "", "bin")
```
but it creates `x86/vc15/bin/opencv_core310.dll` while I would like to get `bin/opencv_core310.dll`
Probably  import/copy should have `keep_path` parameter like package/copy does?




Discussion:
Hi @db4!

Thank you for reporting.
Yes you are right, there is no `keep_path` parameter in for the copy object inside the `imports()` method and this could be introduced without further issue in a following release.

However, I would like to note one thing here. There is no need to maintain that folder structure when you are packagin the generated binaries in the `package()` method because the settings of the package are already stored in the package ID.

Indeed, as a convention it is normally used like this:

```
def package(self):
    self.copy("*.h", dst="include", src="hello")
    self.copy("*.lib", dst="lib", keep_path=False)
    self.copy("*.dll", dst="bin", keep_path=False)
    self.copy("*.so", dst="lib", keep_path=False)
    self.copy("*.dylib", dst="lib", keep_path=False)
    self.copy("*.a", dst="lib", keep_path=False)
```

Have you found any issue packagin the binaries as above? Please try that and tell me what you think.
Thanks! 😃 
@danimtb , for OpenCV I would like to preserve directory structure to be able to use its `OpenCVConfig.cmake`. Otherwise cmake find_package() will not be possible. But you are right - I can copy `*.dll`/`*.so` to bin folder and keep paths for `*.h`/`*.lib`/`*.cmake`. Then no changes is needed, at least for my case.

BTW, does anyone know which OpenCV package considered "mainstream" for conan? What it the best way to contribute - create my own hundredth fork of some conan-opencv repo or choose an arbitrary one and make a PR?
Yes @db4 I understand. We will add keep_path parameter to fit your use case.

Note sure what you are asking about regarding OpenCV, I would say the official repo is[ this one](https://github.com/opencv/opencv). Best way would be to fork and create a PR there with the minimum files for conan package support. Or you can create your standalone repo with the conan files...
I think I understand better now. I think you mean what OpenCV package would be the best to use with Conan.

Currently there are no good quality packages for OpenCV. I guess there might be outdated and not fully-featured packages in the old conan-transit bit I'd suggest not to use them as they would probably be depreprecated.

We are more than willing to help anyone develop a good package for OpenCV and include it in conan-center repository. It would be awesome for the Conan users!
> We are more than willing to help anyone develop a good package for OpenCV and include it in conan-center repository. It would be awesome for the Conan users!

I'm quite new to conan.io, so some things are not very obvious to me. How do you know that something is  good enough for conan-center? How other users expected to find new packages (not yet included into conan repos), test them and contribute to their development? There is at least one "stable" conan packages repository (bincrafters). How do you coordinate your efforts with them? These questions are probably very common, but I have not found answers in your FAQ or other conan.io docs.
No problem, in fact conan-center is just starting to be populated, and we are all learning in the process.
If you haven't, you might read this guide: http://docs.conan.io/en/latest/uploading_packages/bintray/conan_center_guide.html

The bincrafters are doing a great job, but I don't think all the packages they have can be considered stable. They will be doing inclusions requests to conan-center, and we will collaborate to review and help improving them, so conan-center will be the main reference for stable packages.

Some community has concentrated in the CppLang slack team (https://cpplang.now.sh/) there is "conan" channel with lots of conan users, and also a "bincrafters" channel. They are both very very active and you can find in them people willing to help, discuss, learn, and collaborate.

For packages as important as OpenCV I'd expect to be managed by more than just 1 individual. Ideally, and due to its importance, it would be great that the OpenCV maintainers would collaborate and help with the task. I had myself some recipes for OpenCV that could be a good start, I might try to prioritize it, and I think I know some other users that would be interested in helping.

@memsharded, thanks for the clarification. As for OpenCV, [I've  done my own attempt](https://github.com/db4/conan-opencv), I believe resulting conanfile.py is more flexible than others - it auto-detects OpenCV link options ans is sutable for building at least versions 3.1-3.4 editing a single line. As it forked from your/xmar repo, I would like to know you opinion on that.
Thanks @db4 this is very interesting. We have started a package for OpenCV in conan-community (packages mantained by us): https://github.com/conan-community. We will keep yours as a reference too, there are some neat ideas there. Other things could be improved I think. Lets collaborate in that repo if you feel so: https://github.com/conan-community/conan-opencv

Resolving patch:
diff --git a/conans/client/importer.py b/conans/client/importer.py
--- a/conans/client/importer.py
+++ b/conans/client/importer.py
@@ -116,7 +116,7 @@ def __init__(self, conanfile, dst_folder):
         self.copied_files = set()
 
     def __call__(self, pattern, dst="", src="", root_package=None, folder=False,
-                 ignore_case=False, excludes=None):
+                 ignore_case=False, excludes=None, keep_path=True):
         """
         param pattern: an fnmatch file pattern of the files that should be copied. Eg. *.dll
         param dst: the destination local folder, wrt to current conanfile dir, to which
@@ -136,7 +136,7 @@ def __call__(self, pattern, dst="", src="", root_package=None, folder=False,
             final_dst_path = os.path.join(real_dst_folder, name) if folder else real_dst_folder
             file_copier = FileCopier(matching_path, final_dst_path)
             files = file_copier(pattern, src=src, links=True, ignore_case=ignore_case,
-                                excludes=excludes)
+                                excludes=excludes, keep_path=keep_path)
             self.copied_files.update(files)
 
     def _get_folders(self, pattern):
diff --git a/conans/client/loader_parse.py b/conans/client/loader_parse.py
--- a/conans/client/loader_parse.py
+++ b/conans/client/loader_parse.py
@@ -111,7 +111,7 @@ def options(self):
     @property
     def _import_parameters(self):
         def _parse_args(param_string):
-            root_package, ignore_case, folder, excludes = None, False, False, None
+            root_package, ignore_case, folder, excludes, keep_path = None, False, False, None, True
             params = param_string.split(",")
             params = [p.split("=") for p in params if p]
             for (var, value) in params:
@@ -125,9 +125,11 @@ def _parse_args(param_string):
                     folder = (value.lower() == "true")
                 elif var == "excludes":
                     excludes = value.split()
+                elif var == "keep_path":
+                    keep_path = (value.lower() == "true")
                 else:
                     raise Exception("Invalid imports. Unknown argument %s" % var)
-            return root_package, ignore_case, folder, excludes
+            return root_package, ignore_case, folder, excludes, keep_path
 
         def _parse_import(line):
             pair = line.split("->")
@@ -156,9 +158,9 @@ def _parse_import(line):
                     params = tokens[1]
                 else:
                     params = ""
-                root_package, ignore_case, folder, excludes = _parse_args(params)
+                root_package, ignore_case, folder, excludes, keep_path = _parse_args(params)
                 pattern, dest, src = _parse_import(line)
-                ret.append((pattern, dest, src, root_package, folder, ignore_case, excludes))
+                ret.append((pattern, dest, src, root_package, folder, ignore_case, excludes, keep_path))
             except Exception as e:
                 raise ConanException("%s\n%s" % (invalid_line_msg, str(e)))
         return ret


Test patch:
diff --git a/conans/test/functional/conanfile_loader_test.py b/conans/test/functional/conanfile_loader_test.py
--- a/conans/test/functional/conanfile_loader_test.py
+++ b/conans/test/functional/conanfile_loader_test.py
@@ -162,6 +162,7 @@ def load_imports_arguments_test(self):
 OpenCV/lib, * -> ./lib @ root_package=Pkg
 OpenCV/data, * -> ./data @ root_package=Pkg, folder=True # Irrelevant
 docs, * -> ./docs @ root_package=Pkg, folder=True, ignore_case=True, excludes="a b c" # Other
+licenses, * -> ./licenses @ root_package=Pkg, folder=True, ignore_case=True, excludes="a b c", keep_path=False # Other
 '''
         tmp_dir = temp_folder()
         file_path = os.path.join(tmp_dir, "file.txt")
@@ -171,10 +172,12 @@ def load_imports_arguments_test(self):
 
         ret.copy = Mock()
         ret.imports()
-        expected = [call(u'*', u'./bin', u'OpenCV/bin', None, False, False, None),
-                    call(u'*', u'./lib', u'OpenCV/lib', u'Pkg', False, False, None),
-                    call(u'*', u'./data', u'OpenCV/data', u'Pkg', True, False, None),
-                    call(u'*', u'./docs', u'docs', u'Pkg', True, True, [u'"a', u'b', u'c"'])]
+        expected = [call(u'*', u'./bin', u'OpenCV/bin', None, False, False, None, True),
+                    call(u'*', u'./lib', u'OpenCV/lib', u'Pkg', False, False, None, True),
+                    call(u'*', u'./data', u'OpenCV/data', u'Pkg', True, False, None, True),
+                    call(u'*', u'./docs', u'docs', u'Pkg', True, True, [u'"a', u'b', u'c"'], True),
+                    call(u'*', u'./licenses', u'licenses', u'Pkg', True, True, [u'"a', u'b', u'c"'],
+                         False)]
         self.assertEqual(ret.copy.call_args_list, expected)
 
     def test_package_settings(self):
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
@@ -15,6 +15,11 @@ def package(self):
         self.copy("*")
 """
 
+conanfile_txt = """
+[requires]
+%s
+"""
+
 
 class ImportTest(unittest.TestCase):
     def _set_up(self):
@@ -117,3 +122,45 @@ def package(self):
         self.assertTrue(os.path.exists(os.path.join(client.current_folder, "a.dll")))
         self.assertFalse(os.path.exists(os.path.join(client.current_folder, "Foo/b.dll")))
         self.assertFalse(os.path.exists(os.path.join(client.current_folder, "Baz/b.dll")))
+
+    def imports_keep_path_test(self):
+        client = TestClient()
+        client.save({"conanfile.py": conanfile % "LibC",
+                     "path/to/license.txt": "LicenseC"}, clean_first=True)
+        client.run("export . lasote/testing")
+
+        # keep_path = True AND conanfile.py
+        testconanfile = conanfile % "LibD" + "    requires='LibC/0.1@lasote/testing'"
+        testconanfile += """
+    def imports(self):
+        self.copy("*license*", dst="licenses", keep_path=True)
+"""
+        client.save({"conanfile.py": testconanfile}, clean_first=True)
+        client.run("install . --build=missing")
+        self.assertTrue(os.path.exists(os.path.join(client.current_folder,
+                                                     "licenses/path/to/license.txt")))
+
+        # keep_path = False AND conanfile.py
+        testconanfile = testconanfile.replace("keep_path=True", "keep_path=False")
+        client.save({"conanfile.py": testconanfile}, clean_first=True)
+        client.run("install . --build=missing")
+        self.assertTrue(os.path.exists(os.path.join(client.current_folder,
+                                                     "licenses/license.txt")))
+
+        # keep_path = True AND conanfile.txt
+        testconanfile = conanfile_txt % "LibC/0.1@lasote/testing"
+        testconanfile += """
+[imports]:
+., *license* -> ./licenses @ keep_path=True
+"""
+        client.save({"conanfile.txt": testconanfile}, clean_first=True)
+        client.run("install . --build=missing")
+        self.assertTrue(os.path.exists(os.path.join(client.current_folder,
+                                                     "licenses/path/to/license.txt")))
+
+        # keep_path = False AND conanfile.txt
+        testconanfile = testconanfile.replace("keep_path=True", "keep_path=False")
+        client.save({"conanfile.txt": testconanfile}, clean_first=True)
+        client.run("install . --build=missing")
+        self.assertTrue(os.path.exists(os.path.join(client.current_folder, "licenses")))
+        self.assertTrue(os.path.exists(os.path.join(client.current_folder, "licenses/license.txt")))