Repository: conan-io/conan

Issue:
Download command switch to just download the recipe.
Hi guys, congrats on getting 1.0 out!

I think it might be useful to add a command switch for the download command to only download the conanfile.py. 
I have a use case where I need to re-host some packages locally on our own conan_server, when I do a search and download using conan it downloads all of the package folders for a package. To save on time and bandwidth it would be useful to just download the recipe and build the packages our devs will use ourselves.




Discussion:
Thank you! :)

Have you tried the ``conan info`` command? That downloads the recipes only, not the binaries.

The main difference with ``conan download`` is that it retrieves also the transitive dependencies, but that shouldn't be an issue in practice.
You can also try with `$ conan get` and redirect command output to file:

```
$ conan get zlib/1.2.11@conan/stable -r conan-center > conanfile.py
```

Then you can export (or create with the desired settings) and upload to your remote:

```
$ conan export . conan/stable
$ conan upload zlib/1.2.11@conan/stable -r my_remote
Thankyou both for the suggestions! @memsharded I'm going to go with your method. It didn't seem clear to me that the info command would actually download into my local_cache. Maybe that's me misunderstanding Conan a little. But maybe it would be worth pointing it out in the command line description?
It is not in the docs, because actually it is mostly a workaround.
It is possible that the most intuitive approach would be the one that you are suggesting, using a switch to specify it in the ``download`` command. Lets propose it.

Resolving patch:
diff --git a/conans/client/command.py b/conans/client/command.py
--- a/conans/client/command.py
+++ b/conans/client/command.py
@@ -232,10 +232,13 @@ def download(self, *args):
                             help='Force install specified package ID (ignore settings/options)')
         parser.add_argument("-r", "--remote", help='look in the specified remote server',
                             action=OnceArgument)
+        parser.add_argument("-re", "--recipe", help='Downloads only the recipe', default=False,
+                            action="store_true")
 
         args = parser.parse_args(*args)
 
-        return self._conan.download(reference=args.reference, package=args.package, remote=args.remote)
+        return self._conan.download(reference=args.reference, package=args.package,
+                                    remote=args.remote, recipe=args.recipe)
 
     def install(self, *args):
         """Installs the requirements specified in a conanfile (.py or .txt).
diff --git a/conans/client/conan_api.py b/conans/client/conan_api.py
--- a/conans/client/conan_api.py
+++ b/conans/client/conan_api.py
@@ -371,10 +371,12 @@ def export_pkg(self, conanfile_path, name, channel, source_folder=None, build_fo
                                  profile=profile, force=force)
 
     @api_method
-    def download(self, reference, remote=None, package=None):
+    def download(self, reference, remote=None, package=None, recipe=False):
+        if package and recipe:
+            raise ConanException("recipe parameter cannot be used together with package")
         # Install packages without settings (fixed ids or all)
         conan_ref = ConanFileReference.loads(reference)
-        self._manager.download(conan_ref, package, remote=remote)
+        self._manager.download(conan_ref, package, remote=remote, recipe=recipe)
 
     @api_method
     def install_reference(self, reference, settings=None, options=None, env=None,
diff --git a/conans/client/manager.py b/conans/client/manager.py
--- a/conans/client/manager.py
+++ b/conans/client/manager.py
@@ -206,11 +206,12 @@ def export_pkg(self, reference, source_folder, build_folder, package_folder, ins
             packager.create_package(conanfile, source_folder, build_folder, dest_package_folder,
                                     install_folder, package_output, local=True)
 
-    def download(self, reference, package_ids, remote):
+    def download(self, reference, package_ids, remote, recipe):
         """ Download conanfile and specified packages to local repository
         @param reference: ConanFileReference
         @param package_ids: Package ids or empty for download all
         @param remote: install only from that remote
+        @param only_recipe: download only the recipe
         """
         assert(isinstance(reference, ConanFileReference))
         remote_proxy = ConanProxy(self._client_cache, self._user_io, self._remote_manager, remote)
@@ -221,6 +222,10 @@ def download(self, reference, package_ids, remote):
 
         # First of all download package recipe
         remote_proxy.get_recipe(reference)
+
+        if recipe:
+            return
+
         # Download the sources too, don't be lazy
         conan_file_path = self._client_cache.conanfile(reference)
         conanfile = load_conanfile_class(conan_file_path)


Test patch:
diff --git a/conans/test/command/download_test.py b/conans/test/command/download_test.py
--- a/conans/test/command/download_test.py
+++ b/conans/test/command/download_test.py
@@ -7,6 +7,47 @@
 
 class DownloadTest(unittest.TestCase):
 
+    def download_recipe_test(self):
+        server = TestServer()
+        servers = {"default": server}
+        client = TestClient(servers=servers, users={"default": [("lasote", "mypass")]})
+
+        # Test argument --package and --recipe cannot be together
+        error = client.run("download eigen/3.3.4@conan/stable --recipe --package fake_id",
+                           ignore_error=True)
+        self.assertTrue(error)
+        self.assertIn("ERROR: recipe parameter cannot be used together with package", client.out)
+
+        # Test download of the recipe only
+        conanfile = """from conans import ConanFile
+class Pkg(ConanFile):
+    name = "pkg"
+    version = "0.1"
+    exports_sources = "*"
+"""
+        client.save({"conanfile.py": conanfile,
+                     "file.h": "myfile.h"})
+        client.run("create . lasote/stable")
+        ref = ConanFileReference.loads("pkg/0.1@lasote/stable")
+        self.assertTrue(os.path.exists(client.paths.conanfile(ref)))
+        conan = client.client_cache.conan(ref)
+        self.assertTrue(os.path.exists(os.path.join(conan, "package")))
+        client.run("upload pkg/0.1@lasote/stable --all")
+        client.run("remove pkg/0.1@lasote/stable -f")
+        self.assertFalse(os.path.exists(client.paths.export(ref)))
+        client.run("download pkg/0.1@lasote/stable --recipe")
+
+        self.assertIn("Downloading conanfile.py", client.out)
+        self.assertNotIn("Downloading conan_sources.tgz", client.out)
+        self.assertNotIn("Downloading conan_package.tgz", client.out)
+        export = client.client_cache.export(ref)
+        self.assertTrue(os.path.exists(os.path.join(export, "conanfile.py")))
+        self.assertEqual(conanfile, load(os.path.join(export, "conanfile.py")))
+        source = client.client_cache.export_sources(ref)
+        self.assertFalse(os.path.exists(os.path.join(source, "file.h")))
+        conan = client.client_cache.conan(ref)
+        self.assertFalse(os.path.exists(os.path.join(conan, "package")))
+
     def download_with_sources_test(self):
         server = TestServer()
         servers = {"default": server,