codecov-io: # [Codecov](https://codecov.io/gh/ros-infrastructure/rosdep/pull/686?src=pr&el=h1) Report
> Merging [#686](https://codecov.io/gh/ros-infrastructure/rosdep/pull/686?src=pr&el=desc) into [master](https://codecov.io/gh/ros-infrastructure/rosdep/commit/6a7112a2c896bfd5c3e738e1d1fdca98847d8205?src=pr&el=desc) will **increase** coverage by `<.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/ros-infrastructure/rosdep/pull/686/graphs/tree.svg?width=650&token=YdnUPit6vO&height=150&src=pr)](https://codecov.io/gh/ros-infrastructure/rosdep/pull/686?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #686      +/-   ##
==========================================
+ Coverage   75.46%   75.47%   +<.01%     
==========================================
  Files          32       32              
  Lines        2967     2968       +1     
==========================================
+ Hits         2239     2240       +1     
  Misses        728      728
```


| [Impacted Files](https://codecov.io/gh/ros-infrastructure/rosdep/pull/686?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/rosdep2/main.py](https://codecov.io/gh/ros-infrastructure/rosdep/pull/686/diff?src=pr&el=tree#diff-c3JjL3Jvc2RlcDIvbWFpbi5weQ==) | `48.58% <100%> (+0.09%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/ros-infrastructure/rosdep/pull/686?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/ros-infrastructure/rosdep/pull/686?src=pr&el=footer). Last update [6a7112a...e9b3a56](https://codecov.io/gh/ros-infrastructure/rosdep/pull/686?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

kyrofa: @nuclearsandwich curious to get your thoughts on this.
diff --git a/src/rosdep2/main.py b/src/rosdep2/main.py
index 585ad8b..50a0bb4 100644
--- a/src/rosdep2/main.py
+++ b/src/rosdep2/main.py
@@ -61,7 +61,7 @@ from . import __version__
 from .core import RosdepInternalError, InstallFailed, UnsupportedOs, InvalidData, CachePermissionError, DownloadFailure
 from .installers import normalize_uninstalled_to_list
 from .installers import RosdepInstaller
-from .lookup import RosdepLookup, ResolutionError
+from .lookup import RosdepLookup, ResolutionError, prune_catkin_packages
 from .rospkg_loader import DEFAULT_VIEW_KEY
 from .sources_list import update_sources_list, get_sources_cache_dir,\
     download_default_sources_list, SourcesListLoader, CACHE_INDEX,\
@@ -292,10 +292,10 @@ def _rosdep_main(args):
                       action='store_false', help="Do not consider implicit/recursive dependencies.  Only valid with 'keys', 'check', and 'install' commands.")
     parser.add_option('--ignore-packages-from-source', '--ignore-src', '-i',
                       dest='ignore_src', default=False, action='store_true',
-                      help="Affects the 'check' and 'install' verbs. If "
-                           'specified then rosdep will not install keys '
-                           'that are found to be catkin packages anywhere in '
-                           'the ROS_PACKAGE_PATH or in any of the directories '
+                      help="Affects the 'check', 'install', and 'keys' verbs. "
+                           'If specified then rosdep will ignore keys that '
+                           'are found to be catkin packages anywhere in the '
+                           'ROS_PACKAGE_PATH or in any of the directories '
                            'given by the --from-paths option.')
     parser.add_option('--skip-keys',
                       dest='skip_keys', action='append', default=[],
@@ -462,7 +462,7 @@ def _package_args_handler(command, parser, options, args):
         raise rospkg.ResourceNotFound(not_found[0], rospack.get_ros_paths())
 
     # Handle the --ignore-src option
-    if command in ['install', 'check'] and options.ignore_src:
+    if command in ['install', 'check', 'keys'] and options.ignore_src:
         if options.verbose:
             print('Searching ROS_PACKAGE_PATH for '
                   'sources: ' + str(os.environ['ROS_PACKAGE_PATH'].split(':')))
@@ -617,16 +617,17 @@ def command_update(options):
 def command_keys(lookup, packages, options):
     lookup = _get_default_RosdepLookup(options)
     rosdep_keys = get_keys(lookup, packages, options.recursive)
+    prune_catkin_packages(rosdep_keys, options.verbose)
     _print_lookup_errors(lookup)
     print('\n'.join(rosdep_keys))
 
 
 def get_keys(lookup, packages, recursive):
-    rosdep_keys = []
+    rosdep_keys = set()  # using a set to ensure uniqueness
     for package_name in packages:
         deps = lookup.get_rosdeps(package_name, implicit=recursive)
-        rosdep_keys.extend(deps)
-    return set(rosdep_keys)
+        rosdep_keys.update(deps)
+    return list(rosdep_keys)
 
 
 def command_check(lookup, packages, options):

Apply --ignore-src to keys as well
I was recently surprised to learn that `-i`/`--ignore-packages-from-source`/`--ignore-src` only affects `check` and `install`, not `keys` (no error though, of course). That requires one to do their own filtering using e.g. `rospack list-names`, when the functionality obviously already exists in rosdep. How do the maintainers feel about supporting `-i` for `keys` as well, to filter out dependencies already satisfied by packages in the workspace? Happy to do the work.
diff --git a/test/test_rosdep_main.py b/test/test_rosdep_main.py
index 8c6a595..cc33f1d 100644
--- a/test/test_rosdep_main.py
+++ b/test/test_rosdep_main.py
@@ -256,6 +256,10 @@ class TestRosdepMain(unittest.TestCase):
                 rosdep_main(['keys', 'rospack_fake', '--os', 'ubuntu:lucid', '--verbose'] + cmd_extras)
                 stdout, stderr = b
                 assert stdout.getvalue().strip() == 'testtinyxml', stdout.getvalue()
+            with fakeout() as b:
+                rosdep_main(['keys', 'another_catkin_package'] + cmd_extras + ['-i'])
+                stdout, stderr = b
+                assert stdout.getvalue().strip() == 'catkin', stdout.getvalue()
         except SystemExit:
             assert False, 'system exit occurred'
         try:

pip install -e .[dev]
parse_log_pytest
pytest
nose coverage flake8 mock codecov pytest
apt-get update apt-get install -y gcc
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///opt/conda/conda-bld/attrs_1642510447205/work
catkin-pkg==1.0.0
certifi==2021.5.30
charset-normalizer==2.0.12
codecov==2.1.13
coverage==6.2
docutils==0.18.1
flake8==5.0.4
idna==3.10
importlib-metadata==4.2.0
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
mccabe==0.7.0
mock==5.2.0
more-itertools @ file:///tmp/build/80754af9/more-itertools_1637733554872/work
nose==1.3.7
packaging @ file:///tmp/build/80754af9/packaging_1637314298585/work
pluggy @ file:///tmp/build/80754af9/pluggy_1615976315926/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pycodestyle==2.9.1
pyflakes==2.5.0
pyparsing @ file:///tmp/build/80754af9/pyparsing_1635766073266/work
pytest==6.2.4
python-dateutil==2.9.0.post0
PyYAML==6.0.1
requests==2.27.1
-e git+https://github.com/ros-infrastructure/rosdep.git@6a7112a2c896bfd5c3e738e1d1fdca98847d8205#egg=rosdep
rosdistro==1.0.1
rospkg==1.6.0
six==1.17.0
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
urllib3==1.26.20
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work

name: rosdep
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=21.4.0=pyhd3eb1b0_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - importlib_metadata=4.8.1=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - more-itertools=8.12.0=pyhd3eb1b0_0
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=21.3=pyhd3eb1b0_0
  - pip=21.2.2=py36h06a4308_0
  - pluggy=0.13.1=py36h06a4308_0
  - py=1.11.0=pyhd3eb1b0_0
  - pyparsing=3.0.4=pyhd3eb1b0_0
  - pytest=6.2.4=py36h06a4308_2
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - typing_extensions=4.1.1=pyh06a4308_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.6.0=pyhd3eb1b0_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - catkin-pkg==1.0.0
      - charset-normalizer==2.0.12
      - codecov==2.1.13
      - coverage==6.2
      - docutils==0.18.1
      - flake8==5.0.4
      - idna==3.10
      - importlib-metadata==4.2.0
      - mccabe==0.7.0
      - mock==5.2.0
      - nose==1.3.7
      - pycodestyle==2.9.1
      - pyflakes==2.5.0
      - python-dateutil==2.9.0.post0
      - pyyaml==6.0.1
      - requests==2.27.1
      - rosdistro==1.0.1
      - rospkg==1.6.0
      - six==1.17.0
      - urllib3==1.26.20
prefix: /opt/conda/envs/rosdep

test/test_rosdep_main.py::TestRosdepMain::test_keys
test/test_rosdep_main.py::TestRosdepMain::test_check test/test_rosdep_main.py::TestRosdepMain::test_install
test/test_rosdep_main.py::TestRosdepMain::test_bad_commands test/test_rosdep_main.py::TestRosdepMain::test_invalid_package_message test/test_rosdep_main.py::TestRosdepMain::test_proxy_detection test/test_rosdep_main.py::TestRosdepMain::test_what_needs test/test_rosdep_main.py::TestRosdepMain::test_where_defined
src/rosdep2/main.py
src/rosdep2/main.py