Skip to content

Commit

Permalink
adds support for colcon args when building colcon workspace (#27)
Browse files Browse the repository at this point in the history
* adds support for colcon args when building colcon workspace
* adds help message for colcon-args
* adds documentation for colcon-args option
* Added test for option helpers

---------

Co-authored-by: Felix Exner <[email protected]>
  • Loading branch information
taDachs and fmauch authored Jun 10, 2024
1 parent 756d264 commit dc6208d
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 8 deletions.
4 changes: 3 additions & 1 deletion docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ Build options
Set to catkin_make by default but can be changed to catkin build.

``colcon_build_options``
Options passed to each ``colcon build`` invocation that is piped through ``fzirob make``.
Options passed to each ``colcon build`` invocation that is piped through ``fzirob make``. These
options can be overriden by using the ``--colcon-args`` option when running
``fzirob make colcon``


Directory options
Expand Down
4 changes: 4 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ You can also manually specify which workspace to build by using ``fzirob make
ros`` or ``fzirob make colcon``. When using ``fzirob make`` you don't have to
worry about the particular build command at all.

When building a colcon workspace, you can pass arguments to colcon using the ``--colcon-args``
option. For example to selectivly build a package called ``foobar`` and install it using symlinks
you can call ``fzirob make --colcon-args --packages-select foobar --symlink-install``.

Default options for building environments such as the builder for catkin
workspaces or cmake arguments for a colcon workspace can be set in the
:ref:`configuration:Configuration`.
Expand Down
2 changes: 1 addition & 1 deletion src/robot_folders/commands/make.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def get_command(self, ctx, name):
if name == "ros":
return build.CatkinBuilder(name=name, add_help_option=False)
elif name == "colcon":
return build.ColconBuilder(name=name, add_help_option=False)
return build.ColconBuilder(name=name, add_help_option=True)
else:
click.echo("Did not find a workspace with the key < {} >.".format(name))
return None
Expand Down
42 changes: 36 additions & 6 deletions src/robot_folders/helpers/build_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from robot_folders.helpers import compilation_db_helpers
from robot_folders.helpers import config_helpers
from robot_folders.helpers.exceptions import ModuleException
from robot_folders.helpers.option_helpers import SwallowAllOption


def get_cmake_flags():
Expand Down Expand Up @@ -154,12 +155,31 @@ def get_install_default(cls):
class ColconBuilder(Builder):
"""Builder class for colcon workspace"""

def get_build_command(self, ros_distro):
build_cmd = "colcon build"
def __init__(self, *args, **kwargs):
params = [
SwallowAllOption(
["--colcon-args"],
nargs=-1,
help="Arguments passed to colcon. Everything after this flag will be interpreted as"
" colcon arguments. If this option is set, colcon arguments in the config are "
"discarded",
type=click.UNPROCESSED,
)
]

colcon_options = config_helpers.get_value_safe_default(
section="build", value="colcon_build_options", default=""
)
if "params" in kwargs and kwargs["params"]:
kwargs["params"].extend(params)
else:
kwargs["params"] = params
super().__init__(*args, **kwargs)

def get_build_command(self, ros_distro, colcon_options):
if colcon_options is None:
colcon_options = config_helpers.get_value_safe_default(
section="build", value="colcon_build_options", default=""
)

build_cmd = "colcon build"

generator_flag = ""
generator = config_helpers.get_value_safe_default(
Expand Down Expand Up @@ -193,6 +213,16 @@ def invoke(self, ctx):
colcon_dir = get_colcon_dir()
click.echo("Building colcon_ws in {}".format(colcon_dir))

if (
ctx is not None
and "colcon_args" in ctx.params
and ctx.params["colcon_args"] is not None
):
colcon_args = ctx.params["colcon_args"]
colcon_args = " ".join(colcon_args)
else:
colcon_args = None

# Colcon needs to build in an env that does not have the current workspace sourced
# See https://docs.ros.org/en/galactic/Tutorials/Workspace/Creating-A-Workspace.html#source-the-overlay
my_env = os.environ.copy()
Expand All @@ -203,7 +233,7 @@ def invoke(self, ctx):
# We abuse the name to code the ros distribution if we're building for the first time.
try:
process = subprocess.check_call(
["bash", "-c", self.get_build_command(self.name)],
["bash", "-c", self.get_build_command(self.name, colcon_args)],
cwd=colcon_dir,
env=my_env,
)
Expand Down
31 changes: 31 additions & 0 deletions src/robot_folders/helpers/option_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import click


class SwallowAllOption(click.Option):
"""Option that swallows all values after it"""

def __init__(self, *args, **kwargs):
nargs = kwargs.pop("nargs", -1)

if nargs != -1:
raise ValueError("nargs has to be -1")

super(SwallowAllOption, self).__init__(*args, **kwargs)

def add_to_parser(self, parser, ctx):
def parser_process(value, state):
value = [value] + state.rargs
value = tuple(value)
state.rargs[:] = []

self._previous_parser_process(value, state)

retval = super(SwallowAllOption, self).add_to_parser(parser, ctx)
for name in self.opts:
our_parser = parser._long_opt.get(name) or parser._short_opt.get(name)
if our_parser:
self._eat_all_parser = our_parser
self._previous_parser_process = our_parser.process
our_parser.process = parser_process
break
return retval
50 changes: 50 additions & 0 deletions tests/test_option_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#
# Copyright (c) 2024 FZI Forschungszentrum Informatik
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import click

import pytest

from robot_folders.helpers.option_helpers import SwallowAllOption


def test_swallow_all_option():
runner = click.testing.CliRunner()

colcon_args = "--symlink-install --packages-select foobar"

@click.command()
@click.option("--colcon_args", cls=SwallowAllOption)
def test_command(colcon_args):
click.echo(f"{colcon_args}!")

result = runner.invoke(test_command, [f"--colcon_args={colcon_args}"])
assert result.exit_code == 0
assert result.output == f"('{colcon_args}',)!\n"

with pytest.raises(ValueError):

@click.command()
@click.option("--colcon_args", cls=SwallowAllOption, nargs=2)
def illegal_test_command():
click.echo("hello")

runner.invoke(illegal_test_command)

0 comments on commit dc6208d

Please sign in to comment.