Skip to content

Commit f6b0f71

Browse files
committed
Add support for ROS-O (ROS One) distribution
ROS-O is a community-supported ROS 1 distribution that works on Ubuntu 20.04 and newer versions. This change adds support for installing ROS-O alongside ROS 2 distributions. Changes: - Add ROS-O repository configuration and GPG key handling - Support mixed ROS 1 and ROS 2 repository installation - Add ROS-O test cases to CI workflow - Fix SSL certificate handling for ROS-O repository - Ensure correct repository selection based on Ubuntu version - Pin meson version to avoid colcon-meson compatibility issues The implementation allows installing multiple ROS distributions simultaneously, such as 'noetic rolling' on Ubuntu 20.04 or 'one humble' on Ubuntu 22.04. Technical notes: - Meson is constrained to >=0.60.0,<0.64.0 for colcon-meson 0.4.2 compatibility, avoiding IntrospectionInterpreter API breakage Signed-off-by: Iori Yanokura <ab.ioryz@gmail.com>
1 parent ce1e1de commit f6b0f71

6 files changed

Lines changed: 209 additions & 13 deletions

File tree

.github/workflows/test.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ on:
88
schedule:
99
# Run the CI automatically twice per day to look for flakyness.
1010
- cron: "0 */12 * * *"
11+
workflow_dispatch:
1112

1213
defaults:
1314
run:
@@ -115,6 +116,15 @@ jobs:
115116
ros_distribution: noetic
116117
ros_version: 1
117118

119+
# ROS-O (ROS One) - ROS 1 for Ubuntu 20.04+
120+
- docker_image: ubuntu:jammy
121+
ros_distribution: one
122+
ros_version: 1
123+
124+
- docker_image: ubuntu:noble
125+
ros_distribution: one
126+
ros_version: 1
127+
118128
# Humble Hawksbill (May 2022 - May 2027)
119129
- docker_image: ubuntu:jammy
120130
ros_distribution: humble

__test__/main.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ describe("required-ros-distributions/noetic workflow tests", () => {
6464
describe("validate distribution test", () => {
6565
it("test valid", async () => {
6666
await expect(utils.validateDistro(["noetic"])).toBe(true);
67+
await expect(utils.validateDistro(["one"])).toBe(true);
6768
await expect(utils.validateDistro(["humble"])).toBe(true);
6869
await expect(utils.validateDistro(["iron"])).toBe(true);
6970
await expect(utils.validateDistro(["jazzy"])).toBe(true);

dist/index.js

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6682,6 +6682,7 @@ const pip3Packages = [
66826682
"colcon-recursive-crawl==0.2.1",
66836683
"colcon-ros==0.3.23",
66846684
"colcon-test-result==0.3.8",
6685+
"meson>=0.60.0,<0.64.0",
66856686
"coverage",
66866687
"cryptography",
66876688
"empy<4",
@@ -7144,7 +7145,13 @@ function configOs() {
71447145
yield utils.exec("sudo", ["bash", "-c", "echo 'Etc/UTC' > /etc/timezone"]);
71457146
yield utils.exec("sudo", ["apt-get", "update"]);
71467147
// Install tools required to configure the worker system.
7147-
yield apt.runAptGetInstall(["curl", "gnupg2", "locales", "lsb-release"]);
7148+
yield apt.runAptGetInstall([
7149+
"ca-certificates",
7150+
"curl",
7151+
"gnupg2",
7152+
"locales",
7153+
"lsb-release",
7154+
]);
71487155
// Select a locale supporting Unicode.
71497156
yield utils.exec("sudo", ["locale-gen", "en_US", "en_US.UTF-8"]);
71507157
core.exportVariable("LANG", "en_US.UTF-8");
@@ -7172,24 +7179,44 @@ function addAptRepoKey() {
71727179
yield utils.exec("sudo", ["apt-key", "add", keyFilePath]);
71737180
});
71747181
}
7182+
/**
7183+
* Add ROS-O (ROS One) APT repository key.
7184+
*
7185+
* Downloads and installs the GPG key for the ROS-O repository.
7186+
*/
7187+
function addRosOneAptRepoKey() {
7188+
return __awaiter(this, void 0, void 0, function* () {
7189+
// Ensure the keyrings directory exists and ca-certificates is up to date
7190+
yield utils.exec("sudo", ["mkdir", "-p", "/etc/apt/keyrings"]);
7191+
yield utils.exec("sudo", ["update-ca-certificates"]);
7192+
yield utils.exec("sudo", [
7193+
"bash",
7194+
"-c",
7195+
"curl -sSL https://ros.packages.techfak.net/gpg.key -o /etc/apt/keyrings/ros-one-keyring.gpg",
7196+
]);
7197+
});
7198+
}
71757199
// Ubuntu distribution for ROS 1
71767200
const ros1UbuntuVersion = "focal";
71777201
/**
71787202
* Add OSRF APT repository.
71797203
*
71807204
* @param ubuntuCodename the Ubuntu version codename
7205+
* @param needsRos1 whether ROS 1 packages are needed
7206+
* @param needsRos2 whether ROS 2 packages are needed
71817207
*/
7182-
function addAptRepo(ubuntuCodename, use_ros2_testing) {
7208+
function addAptRepo(ubuntuCodename, use_ros2_testing, needsRos1, needsRos2) {
71837209
return __awaiter(this, void 0, void 0, function* () {
7184-
// There is now no Ubuntu version overlap between ROS 1 and ROS 2
7185-
if (ros1UbuntuVersion === ubuntuCodename) {
7210+
// Add ROS 1 repository if needed
7211+
if (needsRos1) {
71867212
yield utils.exec("sudo", [
71877213
"bash",
71887214
"-c",
71897215
`echo "deb http://packages.ros.org/ros/ubuntu ${ubuntuCodename} main" > /etc/apt/sources.list.d/ros-latest.list`,
71907216
]);
71917217
}
7192-
else {
7218+
// Add ROS 2 repository if needed
7219+
if (needsRos2) {
71937220
yield utils.exec("sudo", [
71947221
"bash",
71957222
"-c",
@@ -7199,6 +7226,24 @@ function addAptRepo(ubuntuCodename, use_ros2_testing) {
71997226
yield utils.exec("sudo", ["apt-get", "update"]);
72007227
});
72017228
}
7229+
/**
7230+
* Add ROS-O (ROS One) APT repository.
7231+
*
7232+
* @param ubuntuCodename the Ubuntu version codename
7233+
* @param use_testing whether to use the testing repository
7234+
*/
7235+
function addRosOneAptRepo(ubuntuCodename, use_testing) {
7236+
return __awaiter(this, void 0, void 0, function* () {
7237+
const arch = yield utils.getArch();
7238+
const repo = use_testing ? `${ubuntuCodename}-testing` : ubuntuCodename;
7239+
yield utils.exec("sudo", [
7240+
"bash",
7241+
"-c",
7242+
`echo "deb [arch=${arch} signed-by=/etc/apt/keyrings/ros-one-keyring.gpg] https://ros.packages.techfak.net ${repo} main" > /etc/apt/sources.list.d/ros-one.list`,
7243+
]);
7244+
yield utils.exec("sudo", ["apt-get", "update"]);
7245+
});
7246+
}
72027247
/**
72037248
* Initialize rosdep.
72047249
*/
@@ -7216,6 +7261,20 @@ function rosdepInit() {
72167261
yield utils.exec("sudo", ["rosdep", "init"]);
72177262
});
72187263
}
7264+
/**
7265+
* Configure rosdep for ROS-O (ROS One).
7266+
*
7267+
* Adds custom rosdep source for ROS-O packages.
7268+
*/
7269+
function configureRosOneRosdep() {
7270+
return __awaiter(this, void 0, void 0, function* () {
7271+
yield utils.exec("sudo", [
7272+
"bash",
7273+
"-c",
7274+
'echo "yaml https://ros.packages.techfak.net/ros-one.yaml one" > /etc/ros/rosdep/sources.list.d/1-ros-one.list',
7275+
]);
7276+
});
7277+
}
72197278
/**
72207279
* Install ROS 1 or 2 (development packages and/or ROS binaries) on a Linux worker.
72217280
*/
@@ -7224,10 +7283,32 @@ function runLinux() {
72247283
// Get user input & validate
72257284
const use_ros2_testing = core.getInput("use-ros2-testing") === "true";
72267285
const installConnext = core.getInput("install-connext") === "true";
7286+
const requiredDistros = utils.getRequiredRosDistributions();
7287+
const needsRosOne = requiredDistros.includes("one");
7288+
// Determine which ROS versions are needed
7289+
// ROS 1 distributions: noetic (from packages.ros.org/ros)
7290+
// ROS 2 distributions: rolling, humble, jazzy, iron, kilted, etc. (from packages.ros.org/ros2)
7291+
// ROS-O "one": separate repository (ros.packages.techfak.net)
7292+
const ros1Distros = ["noetic"];
7293+
const needsRos1 = requiredDistros.some((distro) => ros1Distros.includes(distro));
7294+
const needsRos2 = requiredDistros.some((distro) => !ros1Distros.includes(distro) && distro !== "one");
72277295
yield configOs();
72287296
yield addAptRepoKey();
72297297
const ubuntuCodename = yield utils.determineDistribCodename();
7230-
yield addAptRepo(ubuntuCodename, use_ros2_testing);
7298+
// For backward compatibility when no ROS distributions are specified:
7299+
// - Focal (Ubuntu 20.04): add ROS 1 repository (for focal-specific dependencies)
7300+
// - Other versions: add ROS 2 repository (for jammy/noble-specific dependencies)
7301+
// For ROS-O (one): also add ROS 2 repository as it depends on ROS 2 packages
7302+
const addRos1Repo = needsRos1 || ubuntuCodename === ros1UbuntuVersion;
7303+
const addRos2Repo = needsRos2 ||
7304+
needsRosOne ||
7305+
(requiredDistros.length === 0 && ubuntuCodename !== ros1UbuntuVersion);
7306+
yield addAptRepo(ubuntuCodename, use_ros2_testing, addRos1Repo, addRos2Repo);
7307+
// Add ROS-O repository if needed
7308+
if (needsRosOne) {
7309+
yield addRosOneAptRepoKey();
7310+
yield addRosOneAptRepo(ubuntuCodename, use_ros2_testing);
7311+
}
72317312
if ("noble" !== ubuntuCodename) {
72327313
// Temporary fix to avoid error mount: /var/lib/grub/esp: special device (...) does not exist.
72337314
const arch = yield utils.getArch();
@@ -7244,7 +7325,11 @@ function runLinux() {
72447325
yield pip.installPython3Dependencies();
72457326
}
72467327
yield rosdepInit();
7247-
for (const rosDistro of utils.getRequiredRosDistributions()) {
7328+
// Configure rosdep for ROS-O if needed
7329+
if (needsRosOne) {
7330+
yield configureRosOneRosdep();
7331+
}
7332+
for (const rosDistro of requiredDistros) {
72487333
yield apt.runAptGetInstall([`ros-${rosDistro}-desktop`]);
72497334
}
72507335
});
@@ -7570,6 +7655,7 @@ function getRequiredRosDistributions() {
75707655
//list of valid linux distributions
75717656
const validDistro = [
75727657
"noetic",
7658+
"one",
75737659
"humble",
75747660
"iron",
75757661
"jazzy",

src/package_manager/pip.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const pip3Packages: string[] = [
2727
"colcon-recursive-crawl==0.2.1",
2828
"colcon-ros==0.3.23",
2929
"colcon-test-result==0.3.8",
30+
"meson>=0.60.0,<0.64.0",
3031
"coverage",
3132
"cryptography",
3233
"empy<4",

src/setup-ros-ubuntu.ts

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,13 @@ async function configOs(): Promise<void> {
8383
await utils.exec("sudo", ["apt-get", "update"]);
8484

8585
// Install tools required to configure the worker system.
86-
await apt.runAptGetInstall(["curl", "gnupg2", "locales", "lsb-release"]);
86+
await apt.runAptGetInstall([
87+
"ca-certificates",
88+
"curl",
89+
"gnupg2",
90+
"locales",
91+
"lsb-release",
92+
]);
8793

8894
// Select a locale supporting Unicode.
8995
await utils.exec("sudo", ["locale-gen", "en_US", "en_US.UTF-8"]);
@@ -112,26 +118,49 @@ async function addAptRepoKey(): Promise<void> {
112118
await utils.exec("sudo", ["apt-key", "add", keyFilePath]);
113119
}
114120

121+
/**
122+
* Add ROS-O (ROS One) APT repository key.
123+
*
124+
* Downloads and installs the GPG key for the ROS-O repository.
125+
*/
126+
async function addRosOneAptRepoKey(): Promise<void> {
127+
// Ensure the keyrings directory exists and ca-certificates is up to date
128+
await utils.exec("sudo", ["mkdir", "-p", "/etc/apt/keyrings"]);
129+
await utils.exec("sudo", ["update-ca-certificates"]);
130+
await utils.exec("sudo", [
131+
"bash",
132+
"-c",
133+
"curl -sSL https://ros.packages.techfak.net/gpg.key -o /etc/apt/keyrings/ros-one-keyring.gpg",
134+
]);
135+
}
136+
115137
// Ubuntu distribution for ROS 1
116138
const ros1UbuntuVersion = "focal";
117139

118140
/**
119141
* Add OSRF APT repository.
120142
*
121143
* @param ubuntuCodename the Ubuntu version codename
144+
* @param needsRos1 whether ROS 1 packages are needed
145+
* @param needsRos2 whether ROS 2 packages are needed
122146
*/
123147
async function addAptRepo(
124148
ubuntuCodename: string,
125149
use_ros2_testing: boolean,
150+
needsRos1: boolean,
151+
needsRos2: boolean,
126152
): Promise<void> {
127-
// There is now no Ubuntu version overlap between ROS 1 and ROS 2
128-
if (ros1UbuntuVersion === ubuntuCodename) {
153+
// Add ROS 1 repository if needed
154+
if (needsRos1) {
129155
await utils.exec("sudo", [
130156
"bash",
131157
"-c",
132158
`echo "deb http://packages.ros.org/ros/ubuntu ${ubuntuCodename} main" > /etc/apt/sources.list.d/ros-latest.list`,
133159
]);
134-
} else {
160+
}
161+
162+
// Add ROS 2 repository if needed
163+
if (needsRos2) {
135164
await utils.exec("sudo", [
136165
"bash",
137166
"-c",
@@ -144,6 +173,26 @@ async function addAptRepo(
144173
await utils.exec("sudo", ["apt-get", "update"]);
145174
}
146175

176+
/**
177+
* Add ROS-O (ROS One) APT repository.
178+
*
179+
* @param ubuntuCodename the Ubuntu version codename
180+
* @param use_testing whether to use the testing repository
181+
*/
182+
async function addRosOneAptRepo(
183+
ubuntuCodename: string,
184+
use_testing: boolean,
185+
): Promise<void> {
186+
const arch = await utils.getArch();
187+
const repo = use_testing ? `${ubuntuCodename}-testing` : ubuntuCodename;
188+
await utils.exec("sudo", [
189+
"bash",
190+
"-c",
191+
`echo "deb [arch=${arch} signed-by=/etc/apt/keyrings/ros-one-keyring.gpg] https://ros.packages.techfak.net ${repo} main" > /etc/apt/sources.list.d/ros-one.list`,
192+
]);
193+
await utils.exec("sudo", ["apt-get", "update"]);
194+
}
195+
147196
/**
148197
* Initialize rosdep.
149198
*/
@@ -160,6 +209,19 @@ async function rosdepInit(): Promise<void> {
160209
await utils.exec("sudo", ["rosdep", "init"]);
161210
}
162211

212+
/**
213+
* Configure rosdep for ROS-O (ROS One).
214+
*
215+
* Adds custom rosdep source for ROS-O packages.
216+
*/
217+
async function configureRosOneRosdep(): Promise<void> {
218+
await utils.exec("sudo", [
219+
"bash",
220+
"-c",
221+
'echo "yaml https://ros.packages.techfak.net/ros-one.yaml one" > /etc/ros/rosdep/sources.list.d/1-ros-one.list',
222+
]);
223+
}
224+
163225
/**
164226
* Install ROS 1 or 2 (development packages and/or ROS binaries) on a Linux worker.
165227
*/
@@ -168,12 +230,42 @@ export async function runLinux(): Promise<void> {
168230
const use_ros2_testing = core.getInput("use-ros2-testing") === "true";
169231
const installConnext = core.getInput("install-connext") === "true";
170232

233+
const requiredDistros = utils.getRequiredRosDistributions();
234+
const needsRosOne = requiredDistros.includes("one");
235+
236+
// Determine which ROS versions are needed
237+
// ROS 1 distributions: noetic (from packages.ros.org/ros)
238+
// ROS 2 distributions: rolling, humble, jazzy, iron, kilted, etc. (from packages.ros.org/ros2)
239+
// ROS-O "one": separate repository (ros.packages.techfak.net)
240+
const ros1Distros = ["noetic"];
241+
const needsRos1 = requiredDistros.some((distro) =>
242+
ros1Distros.includes(distro),
243+
);
244+
const needsRos2 = requiredDistros.some(
245+
(distro) => !ros1Distros.includes(distro) && distro !== "one",
246+
);
247+
171248
await configOs();
172249

173250
await addAptRepoKey();
174251

175252
const ubuntuCodename = await utils.determineDistribCodename();
176-
await addAptRepo(ubuntuCodename, use_ros2_testing);
253+
// For backward compatibility when no ROS distributions are specified:
254+
// - Focal (Ubuntu 20.04): add ROS 1 repository (for focal-specific dependencies)
255+
// - Other versions: add ROS 2 repository (for jammy/noble-specific dependencies)
256+
// For ROS-O (one): also add ROS 2 repository as it depends on ROS 2 packages
257+
const addRos1Repo = needsRos1 || ubuntuCodename === ros1UbuntuVersion;
258+
const addRos2Repo =
259+
needsRos2 ||
260+
needsRosOne ||
261+
(requiredDistros.length === 0 && ubuntuCodename !== ros1UbuntuVersion);
262+
await addAptRepo(ubuntuCodename, use_ros2_testing, addRos1Repo, addRos2Repo);
263+
264+
// Add ROS-O repository if needed
265+
if (needsRosOne) {
266+
await addRosOneAptRepoKey();
267+
await addRosOneAptRepo(ubuntuCodename, use_ros2_testing);
268+
}
177269

178270
if ("noble" !== ubuntuCodename) {
179271
// Temporary fix to avoid error mount: /var/lib/grub/esp: special device (...) does not exist.
@@ -195,7 +287,12 @@ export async function runLinux(): Promise<void> {
195287

196288
await rosdepInit();
197289

198-
for (const rosDistro of utils.getRequiredRosDistributions()) {
290+
// Configure rosdep for ROS-O if needed
291+
if (needsRosOne) {
292+
await configureRosOneRosdep();
293+
}
294+
295+
for (const rosDistro of requiredDistros) {
199296
await apt.runAptGetInstall([`ros-${rosDistro}-desktop`]);
200297
}
201298
}

src/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export function getRequiredRosDistributions(): string[] {
4343
//list of valid linux distributions
4444
const validDistro: string[] = [
4545
"noetic",
46+
"one",
4647
"humble",
4748
"iron",
4849
"jazzy",

0 commit comments

Comments
 (0)