Merge ~artivis/lpci:feature/ros-plugins into lpci:main
- Git
- lp:~artivis/lpci
- feature/ros-plugins
- Merge into main
Status: | Needs review |
---|---|
Proposed branch: | ~artivis/lpci:feature/ros-plugins |
Merge into: | lpci:main |
Diff against target: |
1087 lines (+1041/-2) 2 files modified
lpcraft/plugin/tests/test_plugins.py (+765/-2) lpcraft/plugins/plugins.py (+276/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jürgen Gmach | Approve | ||
Review via email: mp+435371@code.launchpad.net |
Commit message
Description of the change
This implements three new plugins for building and testing ROS & ROS 2 projects, namely,
- CatkinPlugin - based on the 'catkin' build tool
- CatkinToolsPlugin - based on the 'catkin-tools' build tool
- ColconPlugin - based on the 'colcon' build tool
These plugins all share a common base class - RosBasePlugin - factoring some common code.
The three plugins follow the same principle,
- Install the ROS project's dependencies during the 'run-before' step
- Build the ROS project during the 'run' step
- Build and run the tests during the 'run-after' step (optional)
The later is optional and depends on a plugin config parameter.
Beside some ROS-specific environment variables, the base class define the following env. vars.,
- ROS_PROJECT_BASE - the parent directory of the lpcraft build tree
- ROS_PROJECT_SRC - the directory of the project
- ROS_PROJECT_BUILD - the directory of the plugins build tree
- ROS_PROJECT_INSTALL - the directory of the plugins install tree
They somewhat mimic env. vars. in snapcraft allowing for out-of-source build/install. They also allow the end user to more easily override the run commands not having to figure out the internal paths. They could be replaced in the future by lpcraft-wide env. vars.
jeremie (artivis) wrote : | # |
Hello,
I've already tackled the missed lines in coverage and the CI is happy as far as I can tell.
We are indeed planning on using those plugins in Launchpad CI.
This MP is stand-alone and totally independent of https:/
Looking forward to your review :+1:
Jürgen Gmach (jugmac00) wrote : | # |
Looks good with some minor comments!
As we have no merge bot activated for lpcraft, I think I need to merge the MP.
Just tell me when you think it is ready to be merged.
Note for myself:
- In a follow-up MP we should split the plugins and the tests into submodules.
- In a follow-up MP we should revisit the currently used exceptions.
Preview Diff
1 | diff --git a/lpcraft/plugin/tests/test_plugins.py b/lpcraft/plugin/tests/test_plugins.py | |||
2 | index 1def73b..f7ba1b8 100644 | |||
3 | --- a/lpcraft/plugin/tests/test_plugins.py | |||
4 | +++ b/lpcraft/plugin/tests/test_plugins.py | |||
5 | @@ -5,7 +5,7 @@ import os | |||
6 | 5 | import subprocess | 5 | import subprocess |
7 | 6 | from pathlib import Path, PosixPath | 6 | from pathlib import Path, PosixPath |
8 | 7 | from textwrap import dedent | 7 | from textwrap import dedent |
10 | 8 | from typing import List, Optional, Union, cast | 8 | from typing import Any, Dict, List, Optional, Union, cast |
11 | 9 | from unittest.mock import ANY, Mock, call, patch | 9 | from unittest.mock import ANY, Mock, call, patch |
12 | 10 | 10 | ||
13 | 11 | from craft_providers.lxd import launch | 11 | from craft_providers.lxd import launch |
14 | @@ -17,7 +17,7 @@ from lpcraft.commands.tests import CommandBaseTestCase | |||
15 | 17 | from lpcraft.errors import ConfigurationError | 17 | from lpcraft.errors import ConfigurationError |
16 | 18 | from lpcraft.plugin.manager import get_plugin_manager | 18 | from lpcraft.plugin.manager import get_plugin_manager |
17 | 19 | from lpcraft.plugins import register | 19 | from lpcraft.plugins import register |
19 | 20 | from lpcraft.plugins.plugins import BaseConfig, BasePlugin | 20 | from lpcraft.plugins.plugins import BaseConfig, BasePlugin, RosBasePlugin |
20 | 21 | from lpcraft.providers.tests import makeLXDProvider | 21 | from lpcraft.providers.tests import makeLXDProvider |
21 | 22 | 22 | ||
22 | 23 | 23 | ||
23 | @@ -1080,3 +1080,766 @@ class TestPlugins(CommandBaseTestCase): | |||
24 | 1080 | lpcraft.config.Config.load, | 1080 | lpcraft.config.Config.load, |
25 | 1081 | config_path, | 1081 | config_path, |
26 | 1082 | ) | 1082 | ) |
27 | 1083 | |||
28 | 1084 | def _get_ros_before_run_call(self, expected_env: Dict[str, str]) -> Any: | ||
29 | 1085 | return call( | ||
30 | 1086 | [ | ||
31 | 1087 | "bash", | ||
32 | 1088 | "--noprofile", | ||
33 | 1089 | "--norc", | ||
34 | 1090 | "-ec", | ||
35 | 1091 | "if [ -f /opt/ros/*/setup.sh ]; then source /opt/ros/*/setup.sh; fi" # noqa:E501 | ||
36 | 1092 | "\nif [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then\nrosdep init\nfi" # noqa:E501 | ||
37 | 1093 | "\nrosdep update --rosdistro ${ROS_DISTRO}" | ||
38 | 1094 | "\nrosdep install -i -y --rosdistro ${ROS_DISTRO} --from-paths .", # noqa:E501 | ||
39 | 1095 | ], | ||
40 | 1096 | cwd=PosixPath("/build/lpcraft/project"), | ||
41 | 1097 | env=expected_env, | ||
42 | 1098 | stdout=ANY, | ||
43 | 1099 | stderr=ANY, | ||
44 | 1100 | ) | ||
45 | 1101 | |||
46 | 1102 | def _get_ros_packages_call( | ||
47 | 1103 | self, expected_env: Dict[str, str], packages: List[str] | ||
48 | 1104 | ) -> Any: | ||
49 | 1105 | return call( | ||
50 | 1106 | [ | ||
51 | 1107 | "apt", | ||
52 | 1108 | "install", | ||
53 | 1109 | "-y", | ||
54 | 1110 | "build-essential", | ||
55 | 1111 | "cmake", | ||
56 | 1112 | "g++", | ||
57 | 1113 | "python3-rosdep", | ||
58 | 1114 | ] | ||
59 | 1115 | + packages, | ||
60 | 1116 | cwd=PosixPath("/build/lpcraft/project"), | ||
61 | 1117 | env=expected_env, | ||
62 | 1118 | stdout=ANY, | ||
63 | 1119 | stderr=ANY, | ||
64 | 1120 | ) | ||
65 | 1121 | |||
66 | 1122 | def _get_ros_expected_env( | ||
67 | 1123 | self, ros_distro: str, ros_python_version: str, ros_version: str | ||
68 | 1124 | ) -> Dict[str, str]: | ||
69 | 1125 | return { | ||
70 | 1126 | "ROS_PROJECT_BASE": "/build/lpcraft", | ||
71 | 1127 | "ROS_PROJECT_SRC": "/build/lpcraft/project", | ||
72 | 1128 | "ROS_PROJECT_BUILD": "/build/lpcraft/build", | ||
73 | 1129 | "ROS_PROJECT_INSTALL": "/build/lpcraft/install", | ||
74 | 1130 | "ROS_DISTRO": ros_distro, | ||
75 | 1131 | "ROS_PYTHON_VERSION": ros_python_version, | ||
76 | 1132 | "ROS_VERSION": ros_version, | ||
77 | 1133 | } | ||
78 | 1134 | |||
79 | 1135 | def test_fake_ros_plugin_no_version(self): | ||
80 | 1136 | config = dedent( | ||
81 | 1137 | """ | ||
82 | 1138 | pipeline: | ||
83 | 1139 | - build | ||
84 | 1140 | |||
85 | 1141 | jobs: | ||
86 | 1142 | build: | ||
87 | 1143 | plugin: fake-ros-plugin | ||
88 | 1144 | series: bionic | ||
89 | 1145 | architectures: amd64 | ||
90 | 1146 | """ | ||
91 | 1147 | ) | ||
92 | 1148 | config_path = Path(".launchpad.yaml") | ||
93 | 1149 | config_path.write_text(config) | ||
94 | 1150 | |||
95 | 1151 | @register(name="fake-ros-plugin") | ||
96 | 1152 | class FakeRosPlugin(RosBasePlugin): | ||
97 | 1153 | pass | ||
98 | 1154 | |||
99 | 1155 | config_obj = lpcraft.config.Config.load(config_path) | ||
100 | 1156 | job = config_obj.jobs["build"][0] | ||
101 | 1157 | pm = get_plugin_manager(job) | ||
102 | 1158 | plugins = pm.get_plugins() | ||
103 | 1159 | fake_plugin = [ | ||
104 | 1160 | _ for _ in plugins if _.__class__.__name__ == "FakeRosPlugin" | ||
105 | 1161 | ] | ||
106 | 1162 | self.assertEqual(job.plugin, "fake-ros-plugin") | ||
107 | 1163 | self.assertRaises(NotImplementedError, fake_plugin[0]._get_ros_version) | ||
108 | 1164 | |||
109 | 1165 | def test_fake_ros_plugin_bad_version(self): | ||
110 | 1166 | config = dedent( | ||
111 | 1167 | """ | ||
112 | 1168 | pipeline: | ||
113 | 1169 | - build | ||
114 | 1170 | |||
115 | 1171 | jobs: | ||
116 | 1172 | build: | ||
117 | 1173 | plugin: fake-ros-plugin | ||
118 | 1174 | series: bionic | ||
119 | 1175 | architectures: amd64 | ||
120 | 1176 | """ | ||
121 | 1177 | ) | ||
122 | 1178 | config_path = Path(".launchpad.yaml") | ||
123 | 1179 | config_path.write_text(config) | ||
124 | 1180 | |||
125 | 1181 | @register(name="fake-ros-plugin") | ||
126 | 1182 | class FakeRosPlugin(RosBasePlugin): | ||
127 | 1183 | def _get_ros_version(self) -> int: | ||
128 | 1184 | return 3 | ||
129 | 1185 | |||
130 | 1186 | config_obj = lpcraft.config.Config.load(config_path) | ||
131 | 1187 | job = config_obj.jobs["build"][0] | ||
132 | 1188 | pm = get_plugin_manager(job) | ||
133 | 1189 | plugins = pm.get_plugins() | ||
134 | 1190 | fake_plugin = [ | ||
135 | 1191 | _ for _ in plugins if _.__class__.__name__ == "FakeRosPlugin" | ||
136 | 1192 | ] | ||
137 | 1193 | self.assertEqual(job.plugin, "fake-ros-plugin") | ||
138 | 1194 | self.assertEqual(3, fake_plugin[0]._get_ros_version()) | ||
139 | 1195 | self.assertRaises(Exception, fake_plugin[0]._get_ros_distro) | ||
140 | 1196 | |||
141 | 1197 | @patch("lpcraft.commands.run.get_provider") | ||
142 | 1198 | @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64") | ||
143 | 1199 | def test_catkin_plugin_bionic( | ||
144 | 1200 | self, mock_get_host_architecture, mock_get_provider | ||
145 | 1201 | ): | ||
146 | 1202 | launcher = Mock(spec=launch) | ||
147 | 1203 | provider = makeLXDProvider(lxd_launcher=launcher) | ||
148 | 1204 | mock_get_provider.return_value = provider | ||
149 | 1205 | execute_run = launcher.return_value.execute_run | ||
150 | 1206 | execute_run.return_value = subprocess.CompletedProcess([], 0) | ||
151 | 1207 | config = dedent( | ||
152 | 1208 | """ | ||
153 | 1209 | pipeline: | ||
154 | 1210 | - build | ||
155 | 1211 | |||
156 | 1212 | jobs: | ||
157 | 1213 | build: | ||
158 | 1214 | plugin: catkin | ||
159 | 1215 | series: bionic | ||
160 | 1216 | architectures: amd64 | ||
161 | 1217 | """ | ||
162 | 1218 | ) | ||
163 | 1219 | Path(".launchpad.yaml").write_text(config) | ||
164 | 1220 | |||
165 | 1221 | expected_env = self._get_ros_expected_env("melodic", "2", "1") | ||
166 | 1222 | |||
167 | 1223 | self.run_command("run") | ||
168 | 1224 | self.assertEqual( | ||
169 | 1225 | [ | ||
170 | 1226 | call( | ||
171 | 1227 | ["apt", "update"], | ||
172 | 1228 | cwd=PosixPath("/build/lpcraft/project"), | ||
173 | 1229 | env=expected_env, | ||
174 | 1230 | stdout=ANY, | ||
175 | 1231 | stderr=ANY, | ||
176 | 1232 | ), | ||
177 | 1233 | self._get_ros_packages_call( | ||
178 | 1234 | expected_env, ["ros-melodic-catkin"] | ||
179 | 1235 | ), | ||
180 | 1236 | self._get_ros_before_run_call(expected_env), | ||
181 | 1237 | call( | ||
182 | 1238 | [ | ||
183 | 1239 | "bash", | ||
184 | 1240 | "--noprofile", | ||
185 | 1241 | "--norc", | ||
186 | 1242 | "-ec", | ||
187 | 1243 | "if [ -f /opt/ros/*/setup.sh ]; then source /opt/ros/*/setup.sh; fi" # noqa:E501 | ||
188 | 1244 | "\ncatkin_make_isolated --source-space ${ROS_PROJECT_SRC} --directory ${ROS_PROJECT_BASE}", # noqa:E501 | ||
189 | 1245 | ], | ||
190 | 1246 | cwd=PosixPath("/build/lpcraft/project"), | ||
191 | 1247 | env=expected_env, | ||
192 | 1248 | stdout=ANY, | ||
193 | 1249 | stderr=ANY, | ||
194 | 1250 | ), | ||
195 | 1251 | ], | ||
196 | 1252 | execute_run.call_args_list, | ||
197 | 1253 | ) | ||
198 | 1254 | |||
199 | 1255 | @patch("lpcraft.commands.run.get_provider") | ||
200 | 1256 | @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64") | ||
201 | 1257 | def test_catkin_plugin( | ||
202 | 1258 | self, mock_get_host_architecture, mock_get_provider | ||
203 | 1259 | ): | ||
204 | 1260 | launcher = Mock(spec=launch) | ||
205 | 1261 | provider = makeLXDProvider(lxd_launcher=launcher) | ||
206 | 1262 | mock_get_provider.return_value = provider | ||
207 | 1263 | execute_run = launcher.return_value.execute_run | ||
208 | 1264 | execute_run.return_value = subprocess.CompletedProcess([], 0) | ||
209 | 1265 | config = dedent( | ||
210 | 1266 | """ | ||
211 | 1267 | pipeline: | ||
212 | 1268 | - build | ||
213 | 1269 | |||
214 | 1270 | jobs: | ||
215 | 1271 | build: | ||
216 | 1272 | plugin: catkin | ||
217 | 1273 | series: focal | ||
218 | 1274 | architectures: amd64 | ||
219 | 1275 | """ | ||
220 | 1276 | ) | ||
221 | 1277 | Path(".launchpad.yaml").write_text(config) | ||
222 | 1278 | |||
223 | 1279 | expected_env = self._get_ros_expected_env("noetic", "3", "1") | ||
224 | 1280 | |||
225 | 1281 | self.run_command("run") | ||
226 | 1282 | self.assertEqual( | ||
227 | 1283 | [ | ||
228 | 1284 | call( | ||
229 | 1285 | ["apt", "update"], | ||
230 | 1286 | cwd=PosixPath("/build/lpcraft/project"), | ||
231 | 1287 | env=expected_env, | ||
232 | 1288 | stdout=ANY, | ||
233 | 1289 | stderr=ANY, | ||
234 | 1290 | ), | ||
235 | 1291 | self._get_ros_packages_call( | ||
236 | 1292 | expected_env, ["ros-noetic-catkin"] | ||
237 | 1293 | ), | ||
238 | 1294 | self._get_ros_before_run_call(expected_env), | ||
239 | 1295 | call( | ||
240 | 1296 | [ | ||
241 | 1297 | "bash", | ||
242 | 1298 | "--noprofile", | ||
243 | 1299 | "--norc", | ||
244 | 1300 | "-ec", | ||
245 | 1301 | "if [ -f /opt/ros/*/setup.sh ]; then source /opt/ros/*/setup.sh; fi" # noqa:E501 | ||
246 | 1302 | "\ncatkin_make_isolated --source-space ${ROS_PROJECT_SRC} --directory ${ROS_PROJECT_BASE}", # noqa:E501 | ||
247 | 1303 | ], | ||
248 | 1304 | cwd=PosixPath("/build/lpcraft/project"), | ||
249 | 1305 | env=expected_env, | ||
250 | 1306 | stdout=ANY, | ||
251 | 1307 | stderr=ANY, | ||
252 | 1308 | ), | ||
253 | 1309 | ], | ||
254 | 1310 | execute_run.call_args_list, | ||
255 | 1311 | ) | ||
256 | 1312 | |||
257 | 1313 | @patch("lpcraft.commands.run.get_provider") | ||
258 | 1314 | @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64") | ||
259 | 1315 | def test_catkin_plugin_with_tests( | ||
260 | 1316 | self, mock_get_host_architecture, mock_get_provider | ||
261 | 1317 | ): | ||
262 | 1318 | launcher = Mock(spec=launch) | ||
263 | 1319 | provider = makeLXDProvider(lxd_launcher=launcher) | ||
264 | 1320 | mock_get_provider.return_value = provider | ||
265 | 1321 | execute_run = launcher.return_value.execute_run | ||
266 | 1322 | execute_run.return_value = subprocess.CompletedProcess([], 0) | ||
267 | 1323 | config = dedent( | ||
268 | 1324 | """ | ||
269 | 1325 | pipeline: | ||
270 | 1326 | - build | ||
271 | 1327 | |||
272 | 1328 | jobs: | ||
273 | 1329 | build: | ||
274 | 1330 | plugin: catkin | ||
275 | 1331 | run-tests: True | ||
276 | 1332 | series: focal | ||
277 | 1333 | architectures: amd64 | ||
278 | 1334 | packages: [file, git] | ||
279 | 1335 | """ | ||
280 | 1336 | ) | ||
281 | 1337 | Path(".launchpad.yaml").write_text(config) | ||
282 | 1338 | |||
283 | 1339 | expected_env = self._get_ros_expected_env("noetic", "3", "1") | ||
284 | 1340 | |||
285 | 1341 | self.run_command("run") | ||
286 | 1342 | self.assertEqual( | ||
287 | 1343 | [ | ||
288 | 1344 | call( | ||
289 | 1345 | ["apt", "update"], | ||
290 | 1346 | cwd=PosixPath("/build/lpcraft/project"), | ||
291 | 1347 | env=expected_env, | ||
292 | 1348 | stdout=ANY, | ||
293 | 1349 | stderr=ANY, | ||
294 | 1350 | ), | ||
295 | 1351 | self._get_ros_packages_call( | ||
296 | 1352 | expected_env, ["ros-noetic-catkin", "file", "git"] | ||
297 | 1353 | ), | ||
298 | 1354 | self._get_ros_before_run_call(expected_env), | ||
299 | 1355 | call( | ||
300 | 1356 | [ | ||
301 | 1357 | "bash", | ||
302 | 1358 | "--noprofile", | ||
303 | 1359 | "--norc", | ||
304 | 1360 | "-ec", | ||
305 | 1361 | "if [ -f /opt/ros/*/setup.sh ]; then source /opt/ros/*/setup.sh; fi" # noqa:E501 | ||
306 | 1362 | "\ncatkin_make_isolated --source-space ${ROS_PROJECT_SRC} --directory ${ROS_PROJECT_BASE}", # noqa:E501 | ||
307 | 1363 | ], | ||
308 | 1364 | cwd=PosixPath("/build/lpcraft/project"), | ||
309 | 1365 | env=expected_env, | ||
310 | 1366 | stdout=ANY, | ||
311 | 1367 | stderr=ANY, | ||
312 | 1368 | ), | ||
313 | 1369 | call( | ||
314 | 1370 | [ | ||
315 | 1371 | "bash", | ||
316 | 1372 | "--noprofile", | ||
317 | 1373 | "--norc", | ||
318 | 1374 | "-ec", | ||
319 | 1375 | "\nsource ${ROS_PROJECT_BASE}/devel_isolated/setup.sh" | ||
320 | 1376 | "\ncatkin_make_isolated --source-space ${ROS_PROJECT_SRC} --directory ${ROS_PROJECT_BASE} --force-cmake --catkin-make-args run_tests" # noqa:E501 | ||
321 | 1377 | "\ncatkin_test_results --verbose ${ROS_PROJECT_BASE}/build_isolated/", # noqa:E501 | ||
322 | 1378 | ], | ||
323 | 1379 | cwd=PosixPath("/build/lpcraft/project"), | ||
324 | 1380 | env=expected_env, | ||
325 | 1381 | stdout=ANY, | ||
326 | 1382 | stderr=ANY, | ||
327 | 1383 | ), | ||
328 | 1384 | ], | ||
329 | 1385 | execute_run.call_args_list, | ||
330 | 1386 | ) | ||
331 | 1387 | |||
332 | 1388 | @patch("lpcraft.commands.run.get_provider") | ||
333 | 1389 | @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64") | ||
334 | 1390 | def test_catkin_plugin_user_command( | ||
335 | 1391 | self, mock_get_host_architecture, mock_get_provider | ||
336 | 1392 | ): | ||
337 | 1393 | launcher = Mock(spec=launch) | ||
338 | 1394 | provider = makeLXDProvider(lxd_launcher=launcher) | ||
339 | 1395 | mock_get_provider.return_value = provider | ||
340 | 1396 | execute_run = launcher.return_value.execute_run | ||
341 | 1397 | execute_run.return_value = subprocess.CompletedProcess([], 0) | ||
342 | 1398 | config = dedent( | ||
343 | 1399 | """ | ||
344 | 1400 | pipeline: | ||
345 | 1401 | - build | ||
346 | 1402 | |||
347 | 1403 | jobs: | ||
348 | 1404 | build: | ||
349 | 1405 | plugin: catkin | ||
350 | 1406 | run-tests: True | ||
351 | 1407 | series: focal | ||
352 | 1408 | architectures: amd64 | ||
353 | 1409 | packages: [file, git] | ||
354 | 1410 | run-before: echo 'hello' | ||
355 | 1411 | run: echo 'robot' | ||
356 | 1412 | # run-after: "we shouldn't get anything unless uncommented" | ||
357 | 1413 | """ | ||
358 | 1414 | ) | ||
359 | 1415 | Path(".launchpad.yaml").write_text(config) | ||
360 | 1416 | |||
361 | 1417 | expected_env = self._get_ros_expected_env("noetic", "3", "1") | ||
362 | 1418 | |||
363 | 1419 | self.run_command("run") | ||
364 | 1420 | self.assertEqual( | ||
365 | 1421 | [ | ||
366 | 1422 | call( | ||
367 | 1423 | ["apt", "update"], | ||
368 | 1424 | cwd=PosixPath("/build/lpcraft/project"), | ||
369 | 1425 | env=expected_env, | ||
370 | 1426 | stdout=ANY, | ||
371 | 1427 | stderr=ANY, | ||
372 | 1428 | ), | ||
373 | 1429 | self._get_ros_packages_call( | ||
374 | 1430 | expected_env, ["ros-noetic-catkin", "file", "git"] | ||
375 | 1431 | ), | ||
376 | 1432 | call( | ||
377 | 1433 | ["bash", "--noprofile", "--norc", "-ec", "echo 'hello'"], | ||
378 | 1434 | cwd=PosixPath("/build/lpcraft/project"), | ||
379 | 1435 | env=expected_env, | ||
380 | 1436 | stdout=ANY, | ||
381 | 1437 | stderr=ANY, | ||
382 | 1438 | ), | ||
383 | 1439 | call( | ||
384 | 1440 | ["bash", "--noprofile", "--norc", "-ec", "echo 'robot'"], | ||
385 | 1441 | cwd=PosixPath("/build/lpcraft/project"), | ||
386 | 1442 | env=expected_env, | ||
387 | 1443 | stdout=ANY, | ||
388 | 1444 | stderr=ANY, | ||
389 | 1445 | ), | ||
390 | 1446 | ], | ||
391 | 1447 | execute_run.call_args_list, | ||
392 | 1448 | ) | ||
393 | 1449 | |||
394 | 1450 | @patch("lpcraft.commands.run.get_provider") | ||
395 | 1451 | @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64") | ||
396 | 1452 | def test_catkin_tools_plugin( | ||
397 | 1453 | self, mock_get_host_architecture, mock_get_provider | ||
398 | 1454 | ): | ||
399 | 1455 | launcher = Mock(spec=launch) | ||
400 | 1456 | provider = makeLXDProvider(lxd_launcher=launcher) | ||
401 | 1457 | mock_get_provider.return_value = provider | ||
402 | 1458 | execute_run = launcher.return_value.execute_run | ||
403 | 1459 | execute_run.return_value = subprocess.CompletedProcess([], 0) | ||
404 | 1460 | config = dedent( | ||
405 | 1461 | """ | ||
406 | 1462 | pipeline: | ||
407 | 1463 | - build | ||
408 | 1464 | |||
409 | 1465 | jobs: | ||
410 | 1466 | build: | ||
411 | 1467 | plugin: catkin-tools | ||
412 | 1468 | series: focal | ||
413 | 1469 | architectures: amd64 | ||
414 | 1470 | """ | ||
415 | 1471 | ) | ||
416 | 1472 | Path(".launchpad.yaml").write_text(config) | ||
417 | 1473 | |||
418 | 1474 | expected_env = self._get_ros_expected_env("noetic", "3", "1") | ||
419 | 1475 | |||
420 | 1476 | self.run_command("run") | ||
421 | 1477 | self.assertEqual( | ||
422 | 1478 | [ | ||
423 | 1479 | call( | ||
424 | 1480 | ["apt", "update"], | ||
425 | 1481 | cwd=PosixPath("/build/lpcraft/project"), | ||
426 | 1482 | env=expected_env, | ||
427 | 1483 | stdout=ANY, | ||
428 | 1484 | stderr=ANY, | ||
429 | 1485 | ), | ||
430 | 1486 | self._get_ros_packages_call( | ||
431 | 1487 | expected_env, ["python3-catkin-tools"] | ||
432 | 1488 | ), | ||
433 | 1489 | self._get_ros_before_run_call(expected_env), | ||
434 | 1490 | call( | ||
435 | 1491 | [ | ||
436 | 1492 | "bash", | ||
437 | 1493 | "--noprofile", | ||
438 | 1494 | "--norc", | ||
439 | 1495 | "-ec", | ||
440 | 1496 | "if [ -f /opt/ros/*/setup.sh ]; then source /opt/ros/*/setup.sh; fi\n" # noqa:E501 | ||
441 | 1497 | "catkin init --workspace ${ROS_PROJECT_BASE}\n" | ||
442 | 1498 | "catkin profile add --force lpcraft\n" | ||
443 | 1499 | "catkin config --profile lpcraft --install --source-space ${ROS_PROJECT_SRC} --build-space ${ROS_PROJECT_BUILD} --install-space ${ROS_PROJECT_INSTALL}\n" # noqa:E501 | ||
444 | 1500 | "catkin build --no-notify --profile lpcraft", # noqa:E501 | ||
445 | 1501 | ], | ||
446 | 1502 | cwd=PosixPath("/build/lpcraft/project"), | ||
447 | 1503 | env=expected_env, | ||
448 | 1504 | stdout=ANY, | ||
449 | 1505 | stderr=ANY, | ||
450 | 1506 | ), | ||
451 | 1507 | ], | ||
452 | 1508 | execute_run.call_args_list, | ||
453 | 1509 | ) | ||
454 | 1510 | |||
455 | 1511 | @patch("lpcraft.commands.run.get_provider") | ||
456 | 1512 | @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64") | ||
457 | 1513 | def test_catkin_tools_plugin_with_tests( | ||
458 | 1514 | self, mock_get_host_architecture, mock_get_provider | ||
459 | 1515 | ): | ||
460 | 1516 | launcher = Mock(spec=launch) | ||
461 | 1517 | provider = makeLXDProvider(lxd_launcher=launcher) | ||
462 | 1518 | mock_get_provider.return_value = provider | ||
463 | 1519 | execute_run = launcher.return_value.execute_run | ||
464 | 1520 | execute_run.return_value = subprocess.CompletedProcess([], 0) | ||
465 | 1521 | config = dedent( | ||
466 | 1522 | """ | ||
467 | 1523 | pipeline: | ||
468 | 1524 | - build | ||
469 | 1525 | |||
470 | 1526 | jobs: | ||
471 | 1527 | build: | ||
472 | 1528 | plugin: catkin-tools | ||
473 | 1529 | run-tests: True | ||
474 | 1530 | series: focal | ||
475 | 1531 | architectures: amd64 | ||
476 | 1532 | packages: [file, git] | ||
477 | 1533 | """ | ||
478 | 1534 | ) | ||
479 | 1535 | Path(".launchpad.yaml").write_text(config) | ||
480 | 1536 | |||
481 | 1537 | expected_env = self._get_ros_expected_env("noetic", "3", "1") | ||
482 | 1538 | |||
483 | 1539 | self.run_command("run") | ||
484 | 1540 | self.assertEqual( | ||
485 | 1541 | [ | ||
486 | 1542 | call( | ||
487 | 1543 | ["apt", "update"], | ||
488 | 1544 | cwd=PosixPath("/build/lpcraft/project"), | ||
489 | 1545 | env=expected_env, | ||
490 | 1546 | stdout=ANY, | ||
491 | 1547 | stderr=ANY, | ||
492 | 1548 | ), | ||
493 | 1549 | self._get_ros_packages_call( | ||
494 | 1550 | expected_env, ["python3-catkin-tools", "file", "git"] | ||
495 | 1551 | ), | ||
496 | 1552 | self._get_ros_before_run_call(expected_env), | ||
497 | 1553 | call( | ||
498 | 1554 | [ | ||
499 | 1555 | "bash", | ||
500 | 1556 | "--noprofile", | ||
501 | 1557 | "--norc", | ||
502 | 1558 | "-ec", | ||
503 | 1559 | "if [ -f /opt/ros/*/setup.sh ]; then source /opt/ros/*/setup.sh; fi\n" # noqa:E501 | ||
504 | 1560 | "catkin init --workspace ${ROS_PROJECT_BASE}\n" | ||
505 | 1561 | "catkin profile add --force lpcraft\n" | ||
506 | 1562 | "catkin config --profile lpcraft --install --source-space ${ROS_PROJECT_SRC} --build-space ${ROS_PROJECT_BUILD} --install-space ${ROS_PROJECT_INSTALL}\n" # noqa:E501 | ||
507 | 1563 | "catkin build --no-notify --profile lpcraft", # noqa:E501 | ||
508 | 1564 | ], | ||
509 | 1565 | cwd=PosixPath("/build/lpcraft/project"), | ||
510 | 1566 | env=expected_env, | ||
511 | 1567 | stdout=ANY, | ||
512 | 1568 | stderr=ANY, | ||
513 | 1569 | ), | ||
514 | 1570 | call( | ||
515 | 1571 | [ | ||
516 | 1572 | "bash", | ||
517 | 1573 | "--noprofile", | ||
518 | 1574 | "--norc", | ||
519 | 1575 | "-ec", | ||
520 | 1576 | "\nsource ${ROS_PROJECT_BASE}/devel/setup.sh\n" | ||
521 | 1577 | "catkin test --profile lpcraft --summary", | ||
522 | 1578 | ], | ||
523 | 1579 | cwd=PosixPath("/build/lpcraft/project"), | ||
524 | 1580 | env=expected_env, | ||
525 | 1581 | stdout=ANY, | ||
526 | 1582 | stderr=ANY, | ||
527 | 1583 | ), | ||
528 | 1584 | ], | ||
529 | 1585 | execute_run.call_args_list, | ||
530 | 1586 | ) | ||
531 | 1587 | |||
532 | 1588 | @patch("lpcraft.commands.run.get_provider") | ||
533 | 1589 | @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64") | ||
534 | 1590 | def test_catkin_tools_plugin_user_command( | ||
535 | 1591 | self, mock_get_host_architecture, mock_get_provider | ||
536 | 1592 | ): | ||
537 | 1593 | launcher = Mock(spec=launch) | ||
538 | 1594 | provider = makeLXDProvider(lxd_launcher=launcher) | ||
539 | 1595 | mock_get_provider.return_value = provider | ||
540 | 1596 | execute_run = launcher.return_value.execute_run | ||
541 | 1597 | execute_run.return_value = subprocess.CompletedProcess([], 0) | ||
542 | 1598 | config = dedent( | ||
543 | 1599 | """ | ||
544 | 1600 | pipeline: | ||
545 | 1601 | - build | ||
546 | 1602 | |||
547 | 1603 | jobs: | ||
548 | 1604 | build: | ||
549 | 1605 | plugin: catkin-tools | ||
550 | 1606 | run-tests: True | ||
551 | 1607 | series: focal | ||
552 | 1608 | architectures: amd64 | ||
553 | 1609 | packages: [file, git] | ||
554 | 1610 | run-before: echo 'hello' | ||
555 | 1611 | run: echo 'robot' | ||
556 | 1612 | # run-after: "we shouldn't get anything unless uncommented" | ||
557 | 1613 | """ | ||
558 | 1614 | ) | ||
559 | 1615 | Path(".launchpad.yaml").write_text(config) | ||
560 | 1616 | |||
561 | 1617 | expected_env = self._get_ros_expected_env("noetic", "3", "1") | ||
562 | 1618 | |||
563 | 1619 | self.run_command("run") | ||
564 | 1620 | self.assertEqual( | ||
565 | 1621 | [ | ||
566 | 1622 | call( | ||
567 | 1623 | ["apt", "update"], | ||
568 | 1624 | cwd=PosixPath("/build/lpcraft/project"), | ||
569 | 1625 | env=expected_env, | ||
570 | 1626 | stdout=ANY, | ||
571 | 1627 | stderr=ANY, | ||
572 | 1628 | ), | ||
573 | 1629 | self._get_ros_packages_call( | ||
574 | 1630 | expected_env, ["python3-catkin-tools", "file", "git"] | ||
575 | 1631 | ), | ||
576 | 1632 | call( | ||
577 | 1633 | ["bash", "--noprofile", "--norc", "-ec", "echo 'hello'"], | ||
578 | 1634 | cwd=PosixPath("/build/lpcraft/project"), | ||
579 | 1635 | env=expected_env, | ||
580 | 1636 | stdout=ANY, | ||
581 | 1637 | stderr=ANY, | ||
582 | 1638 | ), | ||
583 | 1639 | call( | ||
584 | 1640 | ["bash", "--noprofile", "--norc", "-ec", "echo 'robot'"], | ||
585 | 1641 | cwd=PosixPath("/build/lpcraft/project"), | ||
586 | 1642 | env=expected_env, | ||
587 | 1643 | stdout=ANY, | ||
588 | 1644 | stderr=ANY, | ||
589 | 1645 | ), | ||
590 | 1646 | ], | ||
591 | 1647 | execute_run.call_args_list, | ||
592 | 1648 | ) | ||
593 | 1649 | |||
594 | 1650 | @patch("lpcraft.commands.run.get_provider") | ||
595 | 1651 | @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64") | ||
596 | 1652 | def test_colcon_plugin( | ||
597 | 1653 | self, mock_get_host_architecture, mock_get_provider | ||
598 | 1654 | ): | ||
599 | 1655 | launcher = Mock(spec=launch) | ||
600 | 1656 | provider = makeLXDProvider(lxd_launcher=launcher) | ||
601 | 1657 | mock_get_provider.return_value = provider | ||
602 | 1658 | execute_run = launcher.return_value.execute_run | ||
603 | 1659 | execute_run.return_value = subprocess.CompletedProcess([], 0) | ||
604 | 1660 | config = dedent( | ||
605 | 1661 | """ | ||
606 | 1662 | pipeline: | ||
607 | 1663 | - build | ||
608 | 1664 | |||
609 | 1665 | jobs: | ||
610 | 1666 | build: | ||
611 | 1667 | plugin: colcon | ||
612 | 1668 | series: focal | ||
613 | 1669 | architectures: amd64 | ||
614 | 1670 | """ | ||
615 | 1671 | ) | ||
616 | 1672 | Path(".launchpad.yaml").write_text(config) | ||
617 | 1673 | |||
618 | 1674 | expected_env = self._get_ros_expected_env("foxy", "3", "2") | ||
619 | 1675 | |||
620 | 1676 | self.run_command("run") | ||
621 | 1677 | self.assertEqual( | ||
622 | 1678 | [ | ||
623 | 1679 | call( | ||
624 | 1680 | ["apt", "update"], | ||
625 | 1681 | cwd=PosixPath("/build/lpcraft/project"), | ||
626 | 1682 | env=expected_env, | ||
627 | 1683 | stdout=ANY, | ||
628 | 1684 | stderr=ANY, | ||
629 | 1685 | ), | ||
630 | 1686 | self._get_ros_packages_call( | ||
631 | 1687 | expected_env, ["python3-colcon-common-extensions"] | ||
632 | 1688 | ), | ||
633 | 1689 | self._get_ros_before_run_call(expected_env), | ||
634 | 1690 | call( | ||
635 | 1691 | [ | ||
636 | 1692 | "bash", | ||
637 | 1693 | "--noprofile", | ||
638 | 1694 | "--norc", | ||
639 | 1695 | "-ec", | ||
640 | 1696 | "if [ -f /opt/ros/*/setup.sh ]; then source /opt/ros/*/setup.sh; fi\n" # noqa:E501 | ||
641 | 1697 | "colcon build --base-paths ${ROS_PROJECT_SRC} --build-base ${ROS_PROJECT_BUILD} --install-base ${ROS_PROJECT_INSTALL} --event-handlers console_direct+", # noqa:E501 | ||
642 | 1698 | ], | ||
643 | 1699 | cwd=PosixPath("/build/lpcraft/project"), | ||
644 | 1700 | env=expected_env, | ||
645 | 1701 | stdout=ANY, | ||
646 | 1702 | stderr=ANY, | ||
647 | 1703 | ), | ||
648 | 1704 | ], | ||
649 | 1705 | execute_run.call_args_list, | ||
650 | 1706 | ) | ||
651 | 1707 | |||
652 | 1708 | @patch("lpcraft.commands.run.get_provider") | ||
653 | 1709 | @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64") | ||
654 | 1710 | def test_colcon_plugin_with_tests( | ||
655 | 1711 | self, mock_get_host_architecture, mock_get_provider | ||
656 | 1712 | ): | ||
657 | 1713 | launcher = Mock(spec=launch) | ||
658 | 1714 | provider = makeLXDProvider(lxd_launcher=launcher) | ||
659 | 1715 | mock_get_provider.return_value = provider | ||
660 | 1716 | execute_run = launcher.return_value.execute_run | ||
661 | 1717 | execute_run.return_value = subprocess.CompletedProcess([], 0) | ||
662 | 1718 | config = dedent( | ||
663 | 1719 | """ | ||
664 | 1720 | pipeline: | ||
665 | 1721 | - build | ||
666 | 1722 | |||
667 | 1723 | jobs: | ||
668 | 1724 | build: | ||
669 | 1725 | plugin: colcon | ||
670 | 1726 | run-tests: True | ||
671 | 1727 | series: focal | ||
672 | 1728 | architectures: amd64 | ||
673 | 1729 | packages: [file, git] | ||
674 | 1730 | """ | ||
675 | 1731 | ) | ||
676 | 1732 | Path(".launchpad.yaml").write_text(config) | ||
677 | 1733 | |||
678 | 1734 | expected_env = self._get_ros_expected_env("foxy", "3", "2") | ||
679 | 1735 | |||
680 | 1736 | self.run_command("run") | ||
681 | 1737 | self.assertEqual( | ||
682 | 1738 | [ | ||
683 | 1739 | call( | ||
684 | 1740 | ["apt", "update"], | ||
685 | 1741 | cwd=PosixPath("/build/lpcraft/project"), | ||
686 | 1742 | env=expected_env, | ||
687 | 1743 | stdout=ANY, | ||
688 | 1744 | stderr=ANY, | ||
689 | 1745 | ), | ||
690 | 1746 | self._get_ros_packages_call( | ||
691 | 1747 | expected_env, | ||
692 | 1748 | ["python3-colcon-common-extensions", "file", "git"], | ||
693 | 1749 | ), | ||
694 | 1750 | self._get_ros_before_run_call(expected_env), | ||
695 | 1751 | call( | ||
696 | 1752 | [ | ||
697 | 1753 | "bash", | ||
698 | 1754 | "--noprofile", | ||
699 | 1755 | "--norc", | ||
700 | 1756 | "-ec", | ||
701 | 1757 | "if [ -f /opt/ros/*/setup.sh ]; then source /opt/ros/*/setup.sh; fi\n" # noqa:E501 | ||
702 | 1758 | "colcon build --base-paths ${ROS_PROJECT_SRC} --build-base ${ROS_PROJECT_BUILD} --install-base ${ROS_PROJECT_INSTALL} --event-handlers console_direct+", # noqa:E501 | ||
703 | 1759 | ], | ||
704 | 1760 | cwd=PosixPath("/build/lpcraft/project"), | ||
705 | 1761 | env=expected_env, | ||
706 | 1762 | stdout=ANY, | ||
707 | 1763 | stderr=ANY, | ||
708 | 1764 | ), | ||
709 | 1765 | call( | ||
710 | 1766 | [ | ||
711 | 1767 | "bash", | ||
712 | 1768 | "--noprofile", | ||
713 | 1769 | "--norc", | ||
714 | 1770 | "-ec", | ||
715 | 1771 | "\nsource ${ROS_PROJECT_INSTALL}/setup.sh\n" | ||
716 | 1772 | "colcon test --base-paths ${ROS_PROJECT_SRC} --build-base ${ROS_PROJECT_BUILD} --install-base ${ROS_PROJECT_INSTALL} --event-handlers console_direct+\n" # noqa:E501 | ||
717 | 1773 | "colcon test-result --all --verbose --test-result-base ${ROS_PROJECT_BUILD}", # noqa:E501 | ||
718 | 1774 | ], | ||
719 | 1775 | cwd=PosixPath("/build/lpcraft/project"), | ||
720 | 1776 | env=expected_env, | ||
721 | 1777 | stdout=ANY, | ||
722 | 1778 | stderr=ANY, | ||
723 | 1779 | ), | ||
724 | 1780 | ], | ||
725 | 1781 | execute_run.call_args_list, | ||
726 | 1782 | ) | ||
727 | 1783 | |||
728 | 1784 | @patch("lpcraft.commands.run.get_provider") | ||
729 | 1785 | @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64") | ||
730 | 1786 | def test_colcon_plugin_user_command( | ||
731 | 1787 | self, mock_get_host_architecture, mock_get_provider | ||
732 | 1788 | ): | ||
733 | 1789 | launcher = Mock(spec=launch) | ||
734 | 1790 | provider = makeLXDProvider(lxd_launcher=launcher) | ||
735 | 1791 | mock_get_provider.return_value = provider | ||
736 | 1792 | execute_run = launcher.return_value.execute_run | ||
737 | 1793 | execute_run.return_value = subprocess.CompletedProcess([], 0) | ||
738 | 1794 | config = dedent( | ||
739 | 1795 | """ | ||
740 | 1796 | pipeline: | ||
741 | 1797 | - build | ||
742 | 1798 | |||
743 | 1799 | jobs: | ||
744 | 1800 | build: | ||
745 | 1801 | plugin: colcon | ||
746 | 1802 | run-tests: True | ||
747 | 1803 | series: focal | ||
748 | 1804 | architectures: amd64 | ||
749 | 1805 | packages: [file, git] | ||
750 | 1806 | run-before: echo 'hello' | ||
751 | 1807 | run: echo 'robot' | ||
752 | 1808 | # run-after: "we shouldn't get anything unless uncommented" | ||
753 | 1809 | """ | ||
754 | 1810 | ) | ||
755 | 1811 | Path(".launchpad.yaml").write_text(config) | ||
756 | 1812 | |||
757 | 1813 | expected_env = self._get_ros_expected_env("foxy", "3", "2") | ||
758 | 1814 | |||
759 | 1815 | self.run_command("run") | ||
760 | 1816 | self.assertEqual( | ||
761 | 1817 | [ | ||
762 | 1818 | call( | ||
763 | 1819 | ["apt", "update"], | ||
764 | 1820 | cwd=PosixPath("/build/lpcraft/project"), | ||
765 | 1821 | env=expected_env, | ||
766 | 1822 | stdout=ANY, | ||
767 | 1823 | stderr=ANY, | ||
768 | 1824 | ), | ||
769 | 1825 | self._get_ros_packages_call( | ||
770 | 1826 | expected_env, | ||
771 | 1827 | ["python3-colcon-common-extensions", "file", "git"], | ||
772 | 1828 | ), | ||
773 | 1829 | call( | ||
774 | 1830 | ["bash", "--noprofile", "--norc", "-ec", "echo 'hello'"], | ||
775 | 1831 | cwd=PosixPath("/build/lpcraft/project"), | ||
776 | 1832 | env=expected_env, | ||
777 | 1833 | stdout=ANY, | ||
778 | 1834 | stderr=ANY, | ||
779 | 1835 | ), | ||
780 | 1836 | call( | ||
781 | 1837 | ["bash", "--noprofile", "--norc", "-ec", "echo 'robot'"], | ||
782 | 1838 | cwd=PosixPath("/build/lpcraft/project"), | ||
783 | 1839 | env=expected_env, | ||
784 | 1840 | stdout=ANY, | ||
785 | 1841 | stderr=ANY, | ||
786 | 1842 | ), | ||
787 | 1843 | ], | ||
788 | 1844 | execute_run.call_args_list, | ||
789 | 1845 | ) | ||
790 | diff --git a/lpcraft/plugins/plugins.py b/lpcraft/plugins/plugins.py | |||
791 | index d4464ee..2a6e41e 100644 | |||
792 | --- a/lpcraft/plugins/plugins.py | |||
793 | +++ b/lpcraft/plugins/plugins.py | |||
794 | @@ -9,6 +9,9 @@ __all__ = [ | |||
795 | 9 | "MiniCondaPlugin", | 9 | "MiniCondaPlugin", |
796 | 10 | "CondaBuildPlugin", | 10 | "CondaBuildPlugin", |
797 | 11 | "GolangPlugin", | 11 | "GolangPlugin", |
798 | 12 | "CatkinPlugin", | ||
799 | 13 | "CatkinToolsPlugin", | ||
800 | 14 | "ColconPlugin", | ||
801 | 12 | ] | 15 | ] |
802 | 13 | 16 | ||
803 | 14 | import textwrap | 17 | import textwrap |
804 | @@ -18,6 +21,7 @@ from typing import TYPE_CHECKING, ClassVar, Dict, List, Optional, cast | |||
805 | 18 | import pydantic | 21 | import pydantic |
806 | 19 | from pydantic import StrictStr | 22 | from pydantic import StrictStr |
807 | 20 | 23 | ||
808 | 24 | from lpcraft.env import get_managed_environment_project_path | ||
809 | 21 | from lpcraft.plugin import hookimpl | 25 | from lpcraft.plugin import hookimpl |
810 | 22 | from lpcraft.plugins import register | 26 | from lpcraft.plugins import register |
811 | 23 | 27 | ||
812 | @@ -451,3 +455,275 @@ class GolangPlugin(BasePlugin): | |||
813 | 451 | export PATH=/usr/lib/go-{version}/bin/:$PATH | 455 | export PATH=/usr/lib/go-{version}/bin/:$PATH |
814 | 452 | {run_command}""" | 456 | {run_command}""" |
815 | 453 | ) | 457 | ) |
816 | 458 | |||
817 | 459 | |||
818 | 460 | class RosBasePlugin(BasePlugin): | ||
819 | 461 | """A base class for ROS-related plugins.""" | ||
820 | 462 | |||
821 | 463 | _MAP_SERIES_TO_ROSDISTRO = { | ||
822 | 464 | "xenial": "kinetic", | ||
823 | 465 | "bionic": "melodic", | ||
824 | 466 | "focal": "noetic", | ||
825 | 467 | } | ||
826 | 468 | |||
827 | 469 | _MAP_SERIES_TO_ROS2DISTRO = { | ||
828 | 470 | "bionic": "dashing", | ||
829 | 471 | "focal": "foxy", | ||
830 | 472 | "jammy": "humble", | ||
831 | 473 | } | ||
832 | 474 | |||
833 | 475 | class Config(BaseConfig): | ||
834 | 476 | run_tests: Optional[bool] | ||
835 | 477 | |||
836 | 478 | def get_plugin_config(self) -> "RosBasePlugin.Config": | ||
837 | 479 | return cast(RosBasePlugin.Config, self.config.plugin_config) | ||
838 | 480 | |||
839 | 481 | def _get_ros_version(self) -> int: | ||
840 | 482 | raise NotImplementedError | ||
841 | 483 | |||
842 | 484 | def _get_ros_distro(self) -> str: | ||
843 | 485 | """Get the ROS distro associated to the Ubuntu series in use.""" | ||
844 | 486 | if self._get_ros_version() == 1: | ||
845 | 487 | return self._MAP_SERIES_TO_ROSDISTRO[self.config.series] | ||
846 | 488 | elif self._get_ros_version() == 2: | ||
847 | 489 | return self._MAP_SERIES_TO_ROS2DISTRO[self.config.series] | ||
848 | 490 | else: | ||
849 | 491 | raise Exception("Unknown ROS version.") | ||
850 | 492 | |||
851 | 493 | def _get_project_path(self) -> Path: | ||
852 | 494 | """Get the project path.""" | ||
853 | 495 | return get_managed_environment_project_path() | ||
854 | 496 | |||
855 | 497 | def _get_build_path(self) -> Path: | ||
856 | 498 | """Get the out-of-source build path.""" | ||
857 | 499 | return get_managed_environment_project_path().parent / "build" | ||
858 | 500 | |||
859 | 501 | def _get_install_path(self) -> Path: | ||
860 | 502 | """Get the out-of-source install path.""" | ||
861 | 503 | return get_managed_environment_project_path().parent / "install" | ||
862 | 504 | |||
863 | 505 | def _get_ros_workspace_activation(self) -> str: | ||
864 | 506 | """Get the ROS system workspace activation command.""" | ||
865 | 507 | # There should be only one ROS distro installed at any time | ||
866 | 508 | # therefore let's make use of the wildcard | ||
867 | 509 | return "if [ -f /opt/ros/*/setup.sh ]; then source /opt/ros/*/setup.sh; fi" # noqa:E501 | ||
868 | 510 | |||
869 | 511 | @hookimpl # type: ignore | ||
870 | 512 | def lpcraft_set_environment(self) -> dict[str, str | None]: | ||
871 | 513 | ros_distro = self._get_ros_distro() | ||
872 | 514 | python_version = ( | ||
873 | 515 | "3" if ros_distro not in ["kinetic", "melodic"] else "2" | ||
874 | 516 | ) | ||
875 | 517 | return { | ||
876 | 518 | "ROS_DISTRO": ros_distro, | ||
877 | 519 | "ROS_PROJECT_BASE": str(self._get_project_path().parent), | ||
878 | 520 | "ROS_PROJECT_SRC": str(self._get_project_path()), | ||
879 | 521 | "ROS_PROJECT_BUILD": str(self._get_build_path()), | ||
880 | 522 | "ROS_PROJECT_INSTALL": str(self._get_install_path()), | ||
881 | 523 | "ROS_PYTHON_VERSION": python_version, | ||
882 | 524 | "ROS_VERSION": f"{self._get_ros_version()}", | ||
883 | 525 | } | ||
884 | 526 | |||
885 | 527 | @hookimpl # type: ignore | ||
886 | 528 | def lpcraft_install_packages(self) -> list[str]: | ||
887 | 529 | return ["build-essential", "cmake", "g++", "python3-rosdep"] | ||
888 | 530 | |||
889 | 531 | @hookimpl # type: ignore | ||
890 | 532 | def lpcraft_execute_before_run(self) -> str: | ||
891 | 533 | return self._get_ros_workspace_activation() + textwrap.dedent( | ||
892 | 534 | """ | ||
893 | 535 | if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then | ||
894 | 536 | rosdep init | ||
895 | 537 | fi | ||
896 | 538 | rosdep update --rosdistro ${ROS_DISTRO} | ||
897 | 539 | rosdep install -i -y --rosdistro ${ROS_DISTRO} --from-paths .""" | ||
898 | 540 | ) | ||
899 | 541 | |||
900 | 542 | |||
901 | 543 | @register(name="catkin") | ||
902 | 544 | class CatkinPlugin(RosBasePlugin): | ||
903 | 545 | """Installs ROS dependencies, builds and (optionally) tests with catkin. | ||
904 | 546 | |||
905 | 547 | Usage: | ||
906 | 548 | In `.launchpad.yaml`, create the following structure. | ||
907 | 549 | |||
908 | 550 | .. code-block:: yaml | ||
909 | 551 | |||
910 | 552 | pipeline: | ||
911 | 553 | - build | ||
912 | 554 | |||
913 | 555 | jobs: | ||
914 | 556 | build: | ||
915 | 557 | plugin: catkin | ||
916 | 558 | run-tests: True # optional | ||
917 | 559 | series: focal | ||
918 | 560 | architectures: amd64 | ||
919 | 561 | packages: [file, git] | ||
920 | 562 | package-repositories: | ||
921 | 563 | - components: [main] | ||
922 | 564 | formats: [deb] | ||
923 | 565 | suites: [focal] | ||
924 | 566 | type: apt | ||
925 | 567 | url: http://packages.ros.org/ros/ubuntu | ||
926 | 568 | trusted: True | ||
927 | 569 | |||
928 | 570 | Please note that the ROS repository must be | ||
929 | 571 | set up in `package-repositories`. | ||
930 | 572 | """ | ||
931 | 573 | |||
932 | 574 | def _get_ros_version(self) -> int: | ||
933 | 575 | return 1 | ||
934 | 576 | |||
935 | 577 | @hookimpl # type: ignore | ||
936 | 578 | def lpcraft_install_packages(self) -> list[str]: | ||
937 | 579 | # XXX artivis 2022-12-9: mypy is struggling with the super() call | ||
938 | 580 | packages: list[str] = super().lpcraft_install_packages() + [ | ||
939 | 581 | # "ros-${ROS_DISTRO}-catkin" | ||
940 | 582 | f"ros-{self._get_ros_distro()}-catkin" | ||
941 | 583 | ] | ||
942 | 584 | return packages | ||
943 | 585 | |||
944 | 586 | @hookimpl # type: ignore | ||
945 | 587 | def lpcraft_execute_run(self) -> str: | ||
946 | 588 | return self._get_ros_workspace_activation() + textwrap.dedent( | ||
947 | 589 | """ | ||
948 | 590 | catkin_make_isolated --source-space ${ROS_PROJECT_SRC} --directory ${ROS_PROJECT_BASE}""" # noqa:E501 | ||
949 | 591 | ) | ||
950 | 592 | |||
951 | 593 | @hookimpl # type: ignore | ||
952 | 594 | def lpcraft_execute_after_run(self) -> str: | ||
953 | 595 | if not self.get_plugin_config().run_tests or self.config.run: | ||
954 | 596 | return "" | ||
955 | 597 | |||
956 | 598 | return textwrap.dedent( | ||
957 | 599 | """ | ||
958 | 600 | source ${ROS_PROJECT_BASE}/devel_isolated/setup.sh | ||
959 | 601 | catkin_make_isolated --source-space ${ROS_PROJECT_SRC} --directory ${ROS_PROJECT_BASE} --force-cmake --catkin-make-args run_tests | ||
960 | 602 | catkin_test_results --verbose ${ROS_PROJECT_BASE}/build_isolated/""" # noqa:E501 | ||
961 | 603 | ) | ||
962 | 604 | |||
963 | 605 | |||
964 | 606 | @register(name="catkin-tools") | ||
965 | 607 | class CatkinToolsPlugin(RosBasePlugin): | ||
966 | 608 | """Installs ROS dependencies, builds and (optionally) tests with catkin-tools. | ||
967 | 609 | |||
968 | 610 | Usage: | ||
969 | 611 | In `.launchpad.yaml`, create the following structure. | ||
970 | 612 | |||
971 | 613 | .. code-block:: yaml | ||
972 | 614 | |||
973 | 615 | pipeline: | ||
974 | 616 | - build | ||
975 | 617 | |||
976 | 618 | jobs: | ||
977 | 619 | build: | ||
978 | 620 | plugin: catkin-tools | ||
979 | 621 | run-tests: True # optional | ||
980 | 622 | series: focal | ||
981 | 623 | architectures: amd64 | ||
982 | 624 | packages: [file, git] | ||
983 | 625 | package-repositories: | ||
984 | 626 | - components: [main] | ||
985 | 627 | formats: [deb] | ||
986 | 628 | suites: [focal] | ||
987 | 629 | type: apt | ||
988 | 630 | url: http://packages.ros.org/ros/ubuntu | ||
989 | 631 | trusted: True | ||
990 | 632 | |||
991 | 633 | Please note that the ROS repository must be | ||
992 | 634 | set up in `package-repositories`. | ||
993 | 635 | """ | ||
994 | 636 | |||
995 | 637 | def _get_ros_version(self) -> int: | ||
996 | 638 | return 1 | ||
997 | 639 | |||
998 | 640 | @hookimpl # type: ignore | ||
999 | 641 | def lpcraft_install_packages(self) -> list[str]: | ||
1000 | 642 | # XXX artivis 2022-12-9: mypy is struggling with the super() call | ||
1001 | 643 | packages: list[str] = super().lpcraft_install_packages() + [ | ||
1002 | 644 | "python3-catkin-tools" | ||
1003 | 645 | ] | ||
1004 | 646 | return packages | ||
1005 | 647 | |||
1006 | 648 | @hookimpl # type: ignore | ||
1007 | 649 | def lpcraft_execute_run(self) -> str: | ||
1008 | 650 | return self._get_ros_workspace_activation() + textwrap.dedent( | ||
1009 | 651 | """ | ||
1010 | 652 | catkin init --workspace ${ROS_PROJECT_BASE} | ||
1011 | 653 | catkin profile add --force lpcraft | ||
1012 | 654 | catkin config --profile lpcraft --install --source-space ${ROS_PROJECT_SRC} --build-space ${ROS_PROJECT_BUILD} --install-space ${ROS_PROJECT_INSTALL} | ||
1013 | 655 | catkin build --no-notify --profile lpcraft""" # noqa:E501 | ||
1014 | 656 | ) | ||
1015 | 657 | |||
1016 | 658 | @hookimpl # type: ignore | ||
1017 | 659 | def lpcraft_execute_after_run(self) -> str: | ||
1018 | 660 | if not self.get_plugin_config().run_tests or self.config.run: | ||
1019 | 661 | return "" | ||
1020 | 662 | |||
1021 | 663 | return textwrap.dedent( | ||
1022 | 664 | """ | ||
1023 | 665 | source ${ROS_PROJECT_BASE}/devel/setup.sh | ||
1024 | 666 | catkin test --profile lpcraft --summary""" | ||
1025 | 667 | ) | ||
1026 | 668 | |||
1027 | 669 | |||
1028 | 670 | @register(name="colcon") | ||
1029 | 671 | class ColconPlugin(RosBasePlugin): | ||
1030 | 672 | """Installs ROS dependencies, builds and (optionally) tests with colcon. | ||
1031 | 673 | |||
1032 | 674 | Usage: | ||
1033 | 675 | In `.launchpad.yaml`, create the following structure. | ||
1034 | 676 | |||
1035 | 677 | .. code-block:: yaml | ||
1036 | 678 | |||
1037 | 679 | pipeline: | ||
1038 | 680 | - build | ||
1039 | 681 | |||
1040 | 682 | jobs: | ||
1041 | 683 | build: | ||
1042 | 684 | plugin: colcon | ||
1043 | 685 | run-tests: True # optional | ||
1044 | 686 | series: focal | ||
1045 | 687 | architectures: amd64 | ||
1046 | 688 | packages: [file, git] | ||
1047 | 689 | package-repositories: | ||
1048 | 690 | - components: [main] | ||
1049 | 691 | formats: [deb] | ||
1050 | 692 | suites: [focal] | ||
1051 | 693 | type: apt | ||
1052 | 694 | url: http://repo.ros2.org/ubuntu/main/ | ||
1053 | 695 | trusted: True | ||
1054 | 696 | |||
1055 | 697 | Please note that the ROS 2 repository must be | ||
1056 | 698 | set up in `package-repositories`. | ||
1057 | 699 | """ | ||
1058 | 700 | |||
1059 | 701 | def _get_ros_version(self) -> int: | ||
1060 | 702 | return 2 | ||
1061 | 703 | |||
1062 | 704 | @hookimpl # type: ignore | ||
1063 | 705 | def lpcraft_install_packages(self) -> list[str]: | ||
1064 | 706 | # XXX artivis 2022-12-9: mypy is struggling with the super() call | ||
1065 | 707 | packages: list[str] = super().lpcraft_install_packages() + [ | ||
1066 | 708 | "python3-colcon-common-extensions" | ||
1067 | 709 | ] | ||
1068 | 710 | return packages | ||
1069 | 711 | |||
1070 | 712 | @hookimpl # type: ignore | ||
1071 | 713 | def lpcraft_execute_run(self) -> str: | ||
1072 | 714 | return self._get_ros_workspace_activation() + textwrap.dedent( | ||
1073 | 715 | """ | ||
1074 | 716 | colcon build --base-paths ${ROS_PROJECT_SRC} --build-base ${ROS_PROJECT_BUILD} --install-base ${ROS_PROJECT_INSTALL} --event-handlers console_direct+""" # noqa:E501 | ||
1075 | 717 | ) | ||
1076 | 718 | |||
1077 | 719 | @hookimpl # type: ignore | ||
1078 | 720 | def lpcraft_execute_after_run(self) -> str: | ||
1079 | 721 | if not self.get_plugin_config().run_tests or self.config.run: | ||
1080 | 722 | return "" | ||
1081 | 723 | |||
1082 | 724 | return textwrap.dedent( | ||
1083 | 725 | """ | ||
1084 | 726 | source ${ROS_PROJECT_INSTALL}/setup.sh | ||
1085 | 727 | colcon test --base-paths ${ROS_PROJECT_SRC} --build-base ${ROS_PROJECT_BUILD} --install-base ${ROS_PROJECT_INSTALL} --event-handlers console_direct+ | ||
1086 | 728 | colcon test-result --all --verbose --test-result-base ${ROS_PROJECT_BUILD}""" # noqa:E501 | ||
1087 | 729 | ) |
Jeremie, thanks for the merge proposal.
CI currently fails as there are two uncovered lines, see buildlog below (below `unmerged commits`):
``` plugins/ plugins. py 284 2 64 1 99% 482, 491
:: lpcraft/
```
Could you please fix these?
As lpcraft is both the CI runner for Launchpad and a standalone tool, I'd like to know how you are planning to use it. Do you use or plan to use it in Launchpad CI?
I am asking as this would influence the plugin story. For a standalone lpcraft we could easily add setuptools based plugins ( https:/ /pluggy. readthedocs. io/en/stable/ #loading- setuptools- entry-points ), so we could have the plugin in a standalone plugin package. For Launchpad CI this is more elaborate, and currently out of scope.
I am discussing these options, as at least for me ROS is a business domain I have never touched before, and including these plugins means I (we) have to maintain them.
Fortunately, the code looks pretty straightforward. I will discuss this MP at today's standup and will get back to you.
Afterwards, I will also give you a detailed review. At very least you would need to add some descriptive docstrings which explain the used business terminology.
Also, does this MP depend on https:/ /code.launchpad .net/~artivis/ lpcraft/ +git/lpcraft/ +merge/ 435370 or would merging this MP on its own already be helpful?