Repository: ros-tooling/cross_compile

Issue:
Allow user to skip rosdep collection
## Description

As a time optimization in the build process, allow the user to skip collecting rosdeps. It can take about 10s to run `rosdep update` and when working on code without changing dependencies, it is not necessary every time.

This would be a "power-user feature"

## Related Issues

Depends on #139 

## Completion Criteria

* [x] User can explicitly skip collecting rosdeps via a CLI flag
* [x] If the `cc_internals/install_rosdeps.sh` script doesn't exist already, raise an exception
* [x] Appropriate documentation exists on the flag to warn about the problems it can cause when used incorrectly. Something like "NOTE: This is an optimization feature for incremental builds to save time running `rosdep update` - you must have run without this flag at least once to collect the initial dependencies. It has undefined behavior if you change the dependencies of your workspace."

## Implementation Notes / Suggestions

Just need a bool CLI flag to pass to the cross_compile pipeline and put the rosdep step in an if-block

## Testing Notes / Suggestions

Add a unit test!


Comments:

Patch:
diff --git a/ros_cross_compile/dependencies.py b/ros_cross_compile/dependencies.py
index 2c738ba..8e57edc 100644
--- a/ros_cross_compile/dependencies.py
+++ b/ros_cross_compile/dependencies.py
@@ -80,3 +80,15 @@ def gather_rosdeps(
         },
         volumes=volumes,
     )
+
+
+def assert_install_rosdep_script_exists(
+    ros_workspace_dir: Path,
+    platform: Platform
+) -> bool:
+    install_rosdep_script_path = ros_workspace_dir / rosdep_install_script(platform)
+    if not install_rosdep_script_path.is_file():
+        raise RuntimeError('Rosdep installation script has never been created,'
+                           'you need to run this without skipping rosdep collection'
+                           'at least once.')
+    return True
diff --git a/ros_cross_compile/ros_cross_compile.py b/ros_cross_compile/ros_cross_compile.py
index 60bc710..1da143d 100644
--- a/ros_cross_compile/ros_cross_compile.py
+++ b/ros_cross_compile/ros_cross_compile.py
@@ -24,6 +24,7 @@ from typing import List
 from typing import Optional
 
 from ros_cross_compile.builders import run_emulated_docker_build
+from ros_cross_compile.dependencies import assert_install_rosdep_script_exists
 from ros_cross_compile.dependencies import gather_rosdeps
 from ros_cross_compile.docker_client import DEFAULT_COLCON_DEFAULTS_FILE
 from ros_cross_compile.docker_client import DockerClient
@@ -116,6 +117,15 @@ def parse_args(args: List[str]) -> argparse.Namespace:
         help='Relative path within the workspace to a file that provides colcon arguments. '
              'See "Package Selection and Build Customization" in README.md for more details.')
 
+    parser.add_argument(
+        '--skip-rosdep-collection',
+        action='store_true',
+        required=False,
+        help='Skip querying rosdep for dependencies. This is intended to save time'
+             'when running repeatedly during development, but has undefined behavior if'
+             "the workspace's dependencies have changed since the last time"
+             'collection was run.')
+
     return parser.parse_args(args)
 
 
@@ -139,12 +149,14 @@ def cross_compile_pipeline(
         default_docker_dir=sysroot_build_context,
         colcon_defaults_file=args.colcon_defaults)
 
-    gather_rosdeps(
-        docker_client=docker_client,
-        platform=platform,
-        workspace=ros_workspace_dir,
-        custom_script=custom_rosdep_script,
-        custom_data_dir=custom_data_dir)
+    if not args.skip_rosdep_collection:
+        gather_rosdeps(
+            docker_client=docker_client,
+            platform=platform,
+            workspace=ros_workspace_dir,
+            custom_script=custom_rosdep_script,
+            custom_data_dir=custom_data_dir)
+    assert_install_rosdep_script_exists(ros_workspace_dir, platform)
     create_workspace_sysroot_image(docker_client, platform)
     run_emulated_docker_build(docker_client, platform, ros_workspace_dir)
 


Test patch:
diff --git a/test/test_entrypoint.py b/test/test_entrypoint.py
index 0412da0..9058cb2 100644
--- a/test/test_entrypoint.py
+++ b/test/test_entrypoint.py
@@ -12,10 +12,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from pathlib import Path
 from unittest.mock import Mock
 from unittest.mock import patch
 
+import pytest
+
 # from ros_cross_compile.docker_client import DockerClient
+from ros_cross_compile.dependencies import assert_install_rosdep_script_exists
+from ros_cross_compile.dependencies import rosdep_install_script
+from ros_cross_compile.platform import Platform
 from ros_cross_compile.ros_cross_compile import cross_compile_pipeline
 from ros_cross_compile.ros_cross_compile import parse_args
 
@@ -27,8 +33,32 @@ def test_trivial_argparse():
 
 def test_mocked_cc_pipeline(tmpdir):
     args = parse_args([str(tmpdir), '-a', 'aarch64', '-o', 'ubuntu'])
-    with patch('ros_cross_compile.ros_cross_compile.DockerClient', Mock()) as docker_mock:
+    with patch(
+        'ros_cross_compile.ros_cross_compile.DockerClient', Mock()
+    ) as docker_mock, patch(
+        'ros_cross_compile.ros_cross_compile.assert_install_rosdep_script_exists'
+    ) as script_mock:
         cross_compile_pipeline(args)
+        assert script_mock.called
         assert docker_mock.called
         assert docker_mock().build_image.call_count == 2
         assert docker_mock().run_container.call_count == 2
+
+
+def test_install_rosdep_script_exist(tmpdir):
+    ws = Path(str(tmpdir))
+    platform = Platform('aarch64', 'ubuntu', 'dashing')
+    data_file = ws / rosdep_install_script(platform)
+    data_file.parent.mkdir(parents=True)
+    data_file.touch()
+    check_script = assert_install_rosdep_script_exists(ws, platform)
+    assert check_script
+
+
+def test_install_rosdep_script_doesnot_exist(tmpdir):
+    ws = Path(str(tmpdir))
+    platform = Platform('aarch64', 'ubuntu', 'dashing')
+    data_file = ws / rosdep_install_script(platform)
+    data_file.parent.mkdir(parents=True)
+    with pytest.raises(RuntimeError):
+        assert_install_rosdep_script_exists(ws, platform)