Skip to content

Commit ad337f6

Browse files
committed
Initial implementation
1 parent 7b09498 commit ad337f6

File tree

5 files changed

+119
-2
lines changed

5 files changed

+119
-2
lines changed

colcon_top_level_workspace/argument_parser/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Copyright 2023 Scott K Logan
2+
# Licensed under the Apache License, Version 2.0
3+
4+
import os
5+
from pathlib import Path
6+
7+
from colcon_core.argument_parser import ArgumentParserDecoratorExtensionPoint
8+
from colcon_core.argument_parser import SuppressUsageOutput
9+
from colcon_core.argument_parser.action_collector \
10+
import ActionCollectorDecorator
11+
from colcon_core.argument_parser.action_collector \
12+
import SuppressRequiredActions
13+
from colcon_core.argument_parser.action_collector \
14+
import SuppressTypeConversions
15+
from colcon_core.plugin_system import satisfies_version
16+
17+
18+
class TopLevelWorkspaceArgumentParserDecorator(
19+
ArgumentParserDecoratorExtensionPoint
20+
):
21+
"""Locate and use a top-level workspace from a subdirectory."""
22+
23+
# Lower priority to appear as close to time-of-use as possible
24+
PRIORITY = 75
25+
26+
def __init__(self): # noqa: D107
27+
super().__init__()
28+
satisfies_version(
29+
ArgumentParserDecoratorExtensionPoint.EXTENSION_POINT_VERSION,
30+
'^1.0')
31+
32+
def decorate_argument_parser(self, *, parser): # noqa: D102
33+
return TopLevelWorkspaceArgumentDecorator(parser)
34+
35+
36+
def _enumerate_parsers(parser):
37+
yield from parser._parsers
38+
for subparser in parser._subparsers:
39+
for p in subparser._parsers:
40+
yield p
41+
yield from _enumerate_parsers(p)
42+
43+
44+
class TopLevelWorkspaceArgumentDecorator(ActionCollectorDecorator):
45+
"""Change to a top-level workspace if one is found."""
46+
47+
def __init__(self, parser): # noqa: D107
48+
# avoid setting members directly, the base class overrides __setattr__
49+
# pass them as keyword arguments instead
50+
super().__init__(
51+
parser,
52+
_parsers=[],
53+
_subparsers=[])
54+
55+
def add_parser(self, *args, **kwargs):
56+
"""Collect association of parsers to their name."""
57+
parser = super().add_parser(*args, **kwargs)
58+
self._parsers.append(parser)
59+
return parser
60+
61+
def add_subparsers(self, *args, **kwargs):
62+
"""Collect all subparsers."""
63+
subparser = super().add_subparsers(*args, **kwargs)
64+
self._subparsers.append(subparser)
65+
return subparser
66+
67+
def parse_args(self, *args, **kwargs): # noqa: D102
68+
parsers = [self._parser]
69+
parsers.extend(_enumerate_parsers(self))
70+
with SuppressUsageOutput(parsers):
71+
with SuppressTypeConversions(parsers):
72+
with SuppressRequiredActions(parsers):
73+
known_args, _ = self._parser.parse_known_args(
74+
*args, **kwargs)
75+
76+
base = 'build'
77+
for arg in ('build_base', 'test_result_base'):
78+
if hasattr(known_args, arg):
79+
base = getattr(known_args, arg)
80+
break
81+
if not os.path.isabs(base):
82+
cwd = Path.cwd()
83+
workspace = find_top_level_workspace(cwd, base)
84+
if workspace and workspace != cwd:
85+
print(f"Using top-level workspace at '{workspace}'")
86+
os.chdir(str(workspace))
87+
args = self._parser.parse_args(*args, **kwargs)
88+
return args
89+
90+
91+
def find_top_level_workspace(candidate, base, this_build_tool='colcon'):
92+
"""
93+
Search for an existing top-level colcon workspace.
94+
95+
:param candidate: Directory at which the search should begin
96+
:param str base: The base directory
97+
:param str this_build_tool: The name of this build tool
98+
99+
:returns: Path to an existing workspace root, or None
100+
"""
101+
marker = candidate / base / '.built_by'
102+
if marker.is_file():
103+
if marker.read_text().rstrip() == this_build_tool:
104+
return candidate
105+
if candidate.parent != candidate:
106+
return find_top_level_workspace(
107+
candidate.parent, base, this_build_tool)
108+
return None

setup.cfg

+3-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ keywords = colcon
2424
[options]
2525
python_requires = >=3.6
2626
install_requires =
27-
colcon-core
27+
colcon-core>=0.13.0
2828
packages = find:
2929
zip_safe = true
3030

@@ -49,6 +49,8 @@ test =
4949
junit_suite_name = colcon-top-level-workspace
5050

5151
[options.entry_points]
52+
colcon_core.argument_parser =
53+
top_level_workspace = colcon_top_level_workspace.argument_parser.top_level_workspace:TopLevelWorkspaceArgumentParserDecorator
5254

5355
[flake8]
5456
import-order-style = google

stdeb.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[colcon-top-level-workspace]
22
No-Python2:
3-
Depends3: python3-colcon-core
3+
Depends3: python3-colcon-core (>= 0.13.0)
44
Suite: bionic focal jammy stretch buster bullseye
55
X-Python3-Version: >= 3.6

test/spell_check.words

+7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
apache
2+
chdir
23
colcon
34
iterdir
5+
noqa
46
pathlib
7+
plugin
58
pytest
9+
rstrip
10+
scott
611
scspell
712
setuptools
13+
subparser
14+
subparsers
815
thomas

0 commit comments

Comments
 (0)