Repository: conda/conda

Issue:
--dependencies-only flag for development
It'd be nice to have a `conda install -dependencies-only` flag. This would install all of the dependencies of the required package, but not the package itself. This would be useful when developing a package - it would allow you to install all of the required packages in one fell swoop.



Discussion:
I agree. It would be basically the opposite of `--no-deps`.

Even so, it's pretty easy to achieve the same thing with `conda install package; conda remove package`. 

easy but feels like hacky....

+1 - this is a common scenario when wanting to automate unit tests prior to creating a package. Don't want to go through the whole conda build unnecessarily (and could fail, but I want my unit tests to fail).

@benbovy Is there progress on this? It seems that #3500 works already but no decision has been made whether this feature will become part of conda. 

I would like to use it to setup the development environment easily and for testing as well.

I'm still waiting for some feedback or review on this.

It does work, although currently a (major) limitation is that we can't set specific versions for the dependencies, which is I actually need to easily create multiple dev/test envs for a given package.

> It does work, although currently a (major) limitation is that we can't set specific versions for the dependencies, which is I actually need to easily create multiple dev/test envs for a given package.

maybe then you need to change the option to be 

```
--add-dependencies-from-package [packagename]
```

which will circumvent the whole installation process and only add the requirements to the install string. This way you can just add more constraints and be more flexible in building envs. Say your package `foo` requires `xyz>=1.0` and you want to test with later versions you run

```
conda install --add-dependencies-from-package foo  xyz=1.1
```

which should act something like

```
conda install xyz>=1.0  xyz=1.1
```

(not that this syntax works but that is what I think would be nice)

This would also be useful if you want to update all dependencies when developing. 

In my case -- just setting up an environment for testing -- it would be nice to have some option in conda-build instead that creates a suitable environment to execute, test or develop in. Conda-build used to not clean up its testing enviroment `_test` which I used to run nose tests a while ago. Having only this step as a single option with a name of the environment would be useful.

I imagine the case where the test branch needs additional packages then installing from an existing build is not enough. Probably the origin why it _feels_ hacky.

```
conda build --only-create-test-environment mytest path-to-recipe 
```

I think the use case might fit better with conda-build's already-built capabilities too.  @benbovy if we implemented something like what @jhprinz suggests in conda-build, would that cover your use case?

CC @msarahan 

@kalefranz although I rarely use conda-build and I've not used it so far for setting up development environments, to me it feels a bit hacky to use a "tool for _building_ conda packages" to actually just _install_ packages in a new development environment. In my case -- create and submit PRs -- I don't need a recipe to build a conda package. Using `conda install package; conda remove package` would be easier than downloading or writing a recipe.

I actually like @jhprinz's suggestion of having a `--add-dependencies-from-package PACKAGE` option (maybe with a more succinct name like `--deps-from PACKAGE` ?). I can update #3500 to implement that option if needed.

If meta.yaml is involved, then conda build is the right way. If only run
time deps are involved, I agree with Benoit.

All this said, I do wonder if the conda develop command (part of conda
build) is what is needed here.

On Oct 24, 2016 6:32 AM, "Benoit Bovy" notifications@github.com wrote:

> @kalefranz https://github.com/kalefranz although I rarely use
> conda-build and I've not used it so far for setting up development
> environments, to me it feels a bit hacky to use a "tool for _building_
> conda packages" to actually just _install_ packages in a new development
> environment. In my case -- create and submit PRs -- I don't need a recipe
> to build a conda package. Using conda install package; conda remove
> package would be easier than downloading or writing a recipe.
> 
> I actually like @jhprinz https://github.com/jhprinz's suggestion of
> having a --add-dependencies-from-package PACKAGE option (maybe with a
> more succinct name like --deps-from PACKAGE ?). I can update #3500
> https://github.com/conda/conda/pull/3500 to implement that option if
> needed.
> 
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> https://github.com/conda/conda/issues/1543#issuecomment-255716085, or mute
> the thread
> https://github.com/notifications/unsubscribe-auth/AACV-axaiBKR0sXX_kWMYA8fqn1I7F1jks5q3JdkgaJpZM4Fvz9C
> .

@msarahan Yes, the following would be nice in my case:

```
$ conda create -n devenv --deps-from package
$ conda develop -n devenv ./package_repo  
```

I guess what I meant was it would be neat if conda develop could (optionally) create new envs for you - and that the way to get development environments would be to use conda-build's conda develop command, and cut the conda create step entirely.  I don't think that this is the way it is right now, but it seems like the right way to do things eventually.

```
conda develop -n devenv ./package_repo
```

would create the environment if it didn't already exist - otherwise, it would ensure that the dependencies were up to date, potentially adding new ones or even removing ones removed from the package.  The removal is of course contentious, but might be the best way to ensure that you have a good, known-working recipe, since previously installed packages would allow things to run, even if they were no longer listed in the recipe.

Opinions?

Indeed, it would be nice to run conda develop only. However, I don't kown how we can get from `./package_repo` all the information needed to setup the environment (i.e., dependencies, channels) when no meta.yml is involved. A possible solution is to set it explicitly (as well as additional constraints on the versions of the packages to install) as new options of conda develop, but then it would probably end up adding a lot of conda create/install features to conda develop. Maybe I miss easier solutions (as my knowledge of conda and conda-build logic is quite limited)? 

>  @msarahan  it would be neat if conda develop could (optionally) create new envs for you 

I think I expected `conda develop` to work in the way you want it to: Install and/or update packages so that you can _develop_ on your package. If that is not the case it would definitely be a good option.

I will not cover my two usecases though: 
1. I usually have a github repo I am working on that is also build into a conda package. What is (kind of) annoying is that once in a while a test on travis fails because some packages were updated (without me noticing) and I need to update my conda environment again. 
   
   So for this I would like some `conda build` option to update the current environment  to the latest packages matching the specifications in `meta.yml`. I think it would be useful if `conda build` would check this when building or even disallow building when your current packages do not represent what would be installed from just using `meta.yml`. All of this should work in the current environment and I assume `conda build` would be the right place to put this functionality
2. I actually need a clean environment to run tests (on travis) that has all the dependencies and at best also all the dependencies in the `test` section of `meta.yml`. I realize that the cleaner way would be to run all the tests when building and put these into the `test` section but for travis CI we decided to run these separately after the build is done. Then use the freshly build package, install it and run the tests. For these we need to recreate the test environment a second time. I assume this would be very easy to implement since conda build can already do that.

The earlier proposed `--deps-from PACKAGE` would still be nice and make sense for `conda install` or `conda update` to provide similar functionality, but it is more a convenience for cases where you know a packages dependencies are complete in providing a certain range of packages. When developing this can be helpful but it is not really what you want. It still requires a package to already exists that needs the packages you want. 

If I understand correctly then these cases are a little different from what @benbovy needs ?!

I guess that the main difference from @jhprinz's usecases is that the packages I'd like to contribute are available as conda packages but they don't include any conda recipe in their github repo (the recipes are maintained either in the Continuum's anaconda-recipes repo or as separate conda-forge feedstocks).

Moreover, when I'm working on a pull request I often prefer to run unit tests locally on multiple environments (e.g., py27, py35) before committing and pushing my commits, even if these tests will be run again automatically with CI. This way I avoid pushing too many commits like "fix tests with py27"...

It would indeed be neat if we could use `conda develop` to automatically create all these environments (at least one command for one environment), though it doesn't seem straightforward as the information needed to setup these environments is usually written in `meta.yml` (which is not directly available here) and/or in files like `.travis.yml`. A solution would be to explicitly provide that information as `conda develop` options, but IMO it feels a bit like copy/pasting the features of `conda create/install` to `conda develop`.

In the case explained here, I don't mind using the two commands `conda create; conda develop` to setup one environment, instead of just `conda develop`. The `--deps-from PACKAGE` option would avoid writing `conda create; conda remove; conda develop`.

It would also be useful if the command would work when multiple packages are specified.
That is, if `packageA` requires `packageB` and `packageC` and the line 
`conda install --dependencies-only packageA packageB` is run, then only `packageC` would be installed.

This behavior would be useful in a CI environment where the tests are run on heads of `packageA` and `packageB`, and not the released package.

Resolving patch:
diff --git a/conda/base/context.py b/conda/base/context.py
--- a/conda/base/context.py
+++ b/conda/base/context.py
@@ -155,7 +155,9 @@ class Context(Configuration):
     dry_run = PrimitiveParameter(False)
     force = PrimitiveParameter(False)
     json = PrimitiveParameter(False)
+    no_dependencies = PrimitiveParameter(False, aliases=('no_deps',))
     offline = PrimitiveParameter(False)
+    only_dependencies = PrimitiveParameter(False, aliases=('only_deps',))
     quiet = PrimitiveParameter(False)
     prune = PrimitiveParameter(False)
     respect_pinned = PrimitiveParameter(True)
@@ -473,6 +475,8 @@ def list_parameters(self):
             'force_32bit',
             'max_shlvl',
             'migrated_custom_channels',
+            'no_dependencies',
+            'only_dependencies',
             'prune',
             'respect_pinned',
             'root_prefix',
diff --git a/conda/cli/conda_argparse.py b/conda/cli/conda_argparse.py
--- a/conda/cli/conda_argparse.py
+++ b/conda/cli/conda_argparse.py
@@ -125,6 +125,11 @@ def add_parser_create_install_update(p):
         action="store_true",
         help="Do not install dependencies.",
     )
+    p.add_argument(
+        "--only-deps",
+        action="store_true",
+        help="Only install dependencies.",
+    )
     p.add_argument(
         '-m', "--mkdir",
         action="store_true",
diff --git a/conda/core/solve.py b/conda/core/solve.py
--- a/conda/core/solve.py
+++ b/conda/core/solve.py
@@ -129,9 +129,43 @@ def solve_for_actions(prefix, r, specs_to_remove=(), specs_to_add=(), prune=Fals
     # this is not for force-removing packages, which doesn't invoke the solver
 
     solved_dists, _specs_to_add = solve_prefix(prefix, r, specs_to_remove, specs_to_add, prune)
+    # TODO: this _specs_to_add part should be refactored when we can better pin package channel origin  # NOQA
     dists_for_unlinking, dists_for_linking = sort_unlink_link_from_solve(prefix, solved_dists,
                                                                          _specs_to_add)
-    # TODO: this _specs_to_add part should be refactored when we can better pin package channel origin  # NOQA
+
+    def remove_non_matching_dists(dists_set, specs_to_match):
+        _dists_set = IndexedSet(dists_set)
+        for dist in dists_set:
+            for spec in specs_to_match:
+                if spec.match(dist):
+                    break
+            else:  # executed if the loop ended normally (no break)
+                _dists_set.remove(dist)
+        return _dists_set
+
+    if context.no_dependencies:
+        # for `conda create --no-deps python=3 flask`, do we install python? yes
+        # the only dists we touch are the ones that match a specs_to_add
+        dists_for_linking = remove_non_matching_dists(dists_for_linking, specs_to_add)
+        dists_for_unlinking = remove_non_matching_dists(dists_for_unlinking, specs_to_add)
+    elif context.only_dependencies:
+        # for `conda create --only-deps python=3 flask`, do we install python? yes
+        # remove all dists that match a specs_to_add, as long as that dist isn't a dependency
+        #   of other specs_to_add
+        _index = r.index
+        _match_any = lambda spec, dists: next((dist for dist in dists if spec.match(_index[dist])),
+                                              None)
+        _is_dependency = lambda spec, dist: any(r.depends_on(s, dist.name)
+                                                for s in specs_to_add if s != spec)
+        for spec in specs_to_add:
+            link_matching_dist = _match_any(spec, dists_for_linking)
+            if link_matching_dist:
+                if not _is_dependency(spec, link_matching_dist):
+                    # as long as that dist isn't a dependency of other specs_to_add
+                    dists_for_linking.remove(link_matching_dist)
+                    unlink_matching_dist = _match_any(spec, dists_for_unlinking)
+                    if unlink_matching_dist:
+                        dists_for_unlinking.remove(unlink_matching_dist)
 
     if context.force:
         dists_for_unlinking, dists_for_linking = forced_reinstall_specs(prefix, solved_dists,


Test patch:
diff --git a/tests/test_create.py b/tests/test_create.py
--- a/tests/test_create.py
+++ b/tests/test_create.py
@@ -531,6 +531,21 @@ def test_install_prune(self):
             assert package_is_installed(prefix, 'pytz')
             assert package_is_installed(prefix, 'python-3')
 
+    def test_no_deps_flag(self):
+        with make_temp_env("python=2 flask --no-deps") as prefix:
+            assert package_is_installed(prefix, 'flask')
+            assert package_is_installed(prefix, 'python-2')
+            assert not package_is_installed(prefix, 'openssl')
+            assert not package_is_installed(prefix, 'itsdangerous')
+
+    def test_only_deps_flag(self):
+        with make_temp_env("python=2 flask --only-deps") as prefix:
+            assert not package_is_installed(prefix, 'flask')
+            assert package_is_installed(prefix, 'python')
+            if not on_win:
+                # python on windows doesn't actually have real dependencies
+                assert package_is_installed(prefix, 'openssl')
+            assert package_is_installed(prefix, 'itsdangerous')
 
     @pytest.mark.skipif(on_win, reason="mkl package not available on Windows")
     def test_install_features(self):