Skip to content

Commit 9d17e66

Browse files
committed
Pull request #209: Feat/PRCUN-5837 integration launch on jenkins humble
Merge in MOCTRL/franka_ros2 from feat/PRCUN-5837-integration-launch-on-jenkins-humble to humble * commit 'd0dbb5df30e3266ec510125092d8a064182194a5': fix: disable humble hardware tests due to BFFTRAC-2632 fix: define workspace as home for ros logs fix: restored cleanWs fix: removed groovy api, added pure bash script from libfranka feat: reworked franka_ros2 jenkins pipeline (incremental build) feat: added FERobotAPI in jenkins pipelines for reuse feat: removed integration tests packages, moved tests where they belong
2 parents 1163335 + d0dbb5d commit 9d17e66

23 files changed

Lines changed: 373 additions & 284 deletions

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Requires libfranka >= 0.20.4 and franka_description >= 2.4.0 requires ROS 2 Jazz
2222
* fix: added arm_prefix functionality to all franka_example_controllers and franka_robot_state_broadcaster
2323
* feat: add sensor support for gazebo. Remove multi robot example for gazebo.
2424
* feat: support tmr simulation with sensors
25+
* chore: removed integration_launch_testing package, moved integration tests in their subpackage
2526

2627
v2.2.0 (2026-01-14)
2728
----------

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ RUN apt-get update && \
2020
gdb \
2121
git \
2222
nano \
23+
iputils-ping \
2324
openssh-client \
2425
python3-colcon-argcomplete \
2526
python3-colcon-common-extensions \

Jenkinsfile

Lines changed: 139 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,14 @@
11
#!groovy
2+
pipeline {
23

3-
/**
4-
* Clone a repository from either a private URL (if provided) or from dependency.repos config.
5-
*
6-
* @param repoName Target directory name for the clone
7-
* @param privateUrl Private repo URL (empty string to use public)
8-
* @param privateBranch Branch to use for private repo
9-
* @param publicUrl Public URL from dependency.repos
10-
* @param publicVersion Version/branch from dependency.repos
11-
* @param credentialId Jenkins SSH credential ID
12-
* @param recursive Whether to clone recursively (default: false)
13-
* @param fetchTags Whether to fetch tags after clone (default: false)
14-
*/
15-
def cloneRepo(Map args) {
16-
def repoName = args.repoName
17-
def privateUrl = args.privateUrl ?: ''
18-
def privateBranch = args.privateBranch ?: 'main'
19-
def publicUrl = args.publicUrl
20-
def publicVersion = args.publicVersion ?: 'main'
21-
def credentialId = args.credentialId
22-
def recursive = args.recursive ?: false
23-
def fetchTags = args.fetchTags ?: false
24-
25-
def recursiveFlag = recursive ? '--recursive' : ''
26-
def fetchTagsCmd = fetchTags ? "&& cd ${repoName} && git fetch --tags" : ''
27-
28-
if (privateUrl) {
29-
echo "Cloning ${repoName} from internal: ${privateUrl} (branch: ${privateBranch})"
30-
sshagent(credentials: [credentialId]) {
31-
sh """
32-
git clone ${recursiveFlag} --branch ${privateBranch} --single-branch ${privateUrl} ${repoName} ${fetchTagsCmd}
33-
"""
34-
}
35-
} else {
36-
echo "Cloning ${repoName} from dependency.repos: ${publicUrl} (version: ${publicVersion})"
37-
sh """
38-
git clone ${recursiveFlag} --branch ${publicVersion} --single-branch ${publicUrl} ${repoName} ${fetchTagsCmd}
39-
"""
4+
libraries {
5+
lib('fe-pipeline-steps')
406
}
41-
}
427

43-
pipeline {
448
agent {
45-
label 'docker'
9+
node {
10+
label 'fepc-lx010'
11+
}
4612
}
4713

4814
triggers {
@@ -52,121 +18,161 @@ pipeline {
5218
parameters {
5319
string(name: 'libfrankaRepoUrl',
5420
defaultValue: 'ssh://[email protected]:7999/moctrl/libfranka.git',
55-
description: 'SSH URL to clone libfranka from internal repo. Leave empty to use the github remote.')
21+
description: 'SSH URL to clone libfranka')
5622

5723
string(name: 'libfrankaBranch',
5824
defaultValue: 'main',
5925
description: 'Branch or tag to checkout for libfranka.')
6026

6127
string(name: 'frankaDescriptionRepoUrl',
6228
defaultValue: 'ssh://[email protected]:7999/moctrl/franka_description.git',
63-
description: 'SSH URL to clone franka_description from internal repo. Leave empty to use the github remote.')
29+
description: 'SSH URL to clone franka_description.')
6430

6531
string(name: 'frankaDescriptionBranch',
6632
defaultValue: 'humble',
6733
description: 'Branch or tag to checkout for franka_description. Defaults to the same ROS_DISTRO branch of franka_ros2.')
6834

69-
string(name: 'sshCredentialId',
70-
defaultValue: 'git_ssh',
71-
description: 'Jenkins credential ID for SSH key to access internal Bitbucket repos.')
35+
booleanParam(name: 'executeHardwareTestsOnRobot',
36+
defaultValue: true,
37+
description: "Run the franka_ros2 tests on the real hardware")
38+
39+
string(name: "robotIp",
40+
defaultValue: "172.16.0.1",
41+
description: "The static IP of the robot to run tests onto")
42+
}
43+
44+
environment {
45+
HOME="${WORKSPACE}" // for cleaning up ros logs
7246
}
7347

48+
7449
stages {
7550
stage('Get Ready') {
51+
options { skipDefaultCheckout true }
7652
steps {
77-
cleanWs()
78-
checkout scm
53+
script{
54+
cleanWs()
55+
}
56+
checkout([
57+
$class: 'GitSCM',
58+
branches: scm.branches,
59+
userRemoteConfigs: scm.userRemoteConfigs,
60+
extensions: [
61+
[$class: 'RelativeTargetDirectory', relativeTargetDir: 'src'] // needed to put everything under src as expected by colcon/rosdep
62+
]
63+
])
7964
script {
8065
notifyBitbucket()
81-
currentBuild.displayName = "[libfranka: ${params.libfrankaRepoUrl ? params.libfrankaBranch : 'private'}, franka_description: ${params.frankaDescriptionRepoUrl ? params.frankaDescriptionBranch : 'private'}]"
66+
currentBuild.displayName = "[libfranka: ${params.libfrankaBranch}, franka_description: ${params.frankaDescriptionBranch}]"
8267
}
83-
sh 'rm -rf build log install libfranka franka_description'
8468
}
8569
}
8670

8771
stage('Fetch Dependencies') {
72+
options { skipDefaultCheckout true }
8873
steps {
89-
script {
90-
sh 'rm -rf libfranka franka_description'
91-
92-
// Read defaults from dependencies.repos so we can follow the versions defined there
93-
def repos = readYaml file: 'dependency.repos'
94-
def repoMap = repos?.repositories ?: [:]
95-
96-
// Helper to get repo config with defaults
97-
def getRepoConfig = { name, defaultUrl ->
98-
def cfg = repoMap[name] ?: [:]
99-
return [
100-
url: cfg.url ?: defaultUrl,
101-
version: cfg.version ?: 'main'
102-
]
103-
}
74+
sh 'echo "=== Workspace structure ===" && ls -la'
75+
dir('src') {
76+
script {
10477

105-
def libfrankaCfg = getRepoConfig('libfranka', 'https://github.com/frankarobotics/libfranka.git')
106-
def frankaDescCfg = getRepoConfig('franka_description', 'https://github.com/frankarobotics/franka_description.git')
107-
108-
// Clone libfranka
109-
cloneRepo(
110-
repoName: 'libfranka',
111-
privateUrl: params.libfrankaRepoUrl,
112-
privateBranch: params.libfrankaBranch,
113-
publicUrl: libfrankaCfg.url,
114-
publicVersion: libfrankaCfg.version,
115-
credentialId: params.sshCredentialId,
116-
recursive: true,
117-
fetchTags: true
118-
)
119-
120-
// Clone franka_description - default to the same branch as franka_ros2 if not specified, which is common for ROS packages
121-
cloneRepo(
122-
repoName: 'franka_description',
123-
privateUrl: params.frankaDescriptionRepoUrl,
124-
privateBranch: params.frankaDescriptionBranch,
125-
publicUrl: frankaDescCfg.url,
126-
publicVersion: frankaDescCfg.version,
127-
credentialId: params.sshCredentialId
128-
)
129-
130-
// Stash cloned external dependencies so later stages (running in another agent/container)
131-
// can restore them even if the workspace is wiped or a different node is used.
132-
// useDefaultExcludes: false is required to include .git directories for version detection
133-
stash name: 'external_deps', includes: 'libfranka/**,franka_description/**', useDefaultExcludes: false
134-
135-
sh 'echo "=== Workspace structure ===" && ls -la'
78+
def repos = readYaml file: 'dependency.repos'
79+
def repoMap = repos?.repositories ?: [:]
80+
81+
// Clone/update all 3rd party dependencies
82+
repoMap.each {repoName, cfg ->
83+
def url = cfg.url
84+
def version = cfg.version ?: 'main'
85+
if (repoName != 'libfranka' && repoName != 'franka_description'){
86+
sh """
87+
if [ -d ${repoName} ]; then
88+
cd ${repoName}
89+
git checkout ${version}
90+
git pull
91+
cd ..
92+
else
93+
git clone --depth 1 --branch ${version} ${url} ${repoName}
94+
fi
95+
"""
96+
}
97+
}
98+
99+
sshagent(['git_ssh']){
100+
sh """
101+
102+
if [ -d "libfranka" ]; then
103+
cd libfranka
104+
git fetch --all --tags
105+
git checkout ${params.libfrankaBranch}
106+
git pull
107+
cd ..
108+
else
109+
git clone --branch ${params.libfrankaBranch} ${params.libfrankaRepoUrl}
110+
cd libfranka
111+
git config submodule.common.url ssh://[email protected]:7999/moctrl/libfranka-common.git
112+
git submodule update --init --recursive --depth 1
113+
cd ..
114+
fi
115+
116+
if [ -d "franka_description" ]; then
117+
cd franka_description
118+
git fetch --all --tags
119+
git checkout ${params.frankaDescriptionBranch}
120+
git pull
121+
cd ..
122+
else
123+
git clone --depth 1 --branch ${params.frankaDescriptionBranch} ${params.frankaDescriptionRepoUrl}
124+
fi
125+
"""
126+
}
127+
}
128+
sh 'echo "=== src structure ===" && ls -la'
136129
}
137130
}
138131
}
139132

140133
stage('Build') {
134+
options { skipDefaultCheckout true }
141135
agent {
142136
dockerfile {
137+
dir 'src'
143138
reuseNode true
144139
}
145140
}
141+
environment {
142+
ROBOT_IP = "${params.robotIp}"
143+
}
146144
steps {
147-
// Clean any existing files before unstashing to avoid permission conflicts
148-
sh 'rm -rf libfranka franka_description'
149-
// Restore external deps cloned in the Fetch Dependencies stage
150-
unstash 'external_deps'
151145
sh '''
152146
. /opt/ros/$ROS_DISTRO/setup.sh
153147
echo "=== Workspace structure ===" && ls -la
154-
colcon build --cmake-args -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCHECK_TIDY=ON -DBUILD_TESTS=OFF
148+
colcon build \
149+
--base-paths src \
150+
--cmake-args -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
151+
-DCHECK_TIDY=ON \
152+
-DBUILD_TESTS=OFF \
153+
-DBUILD_TESTING=ON \
154+
-DROBOT_IP=$ROBOT_IP
155155
'''
156156
}
157157
}
158158

159+
// Run the unit tests (no hardware tests)
159160
stage('Test') {
161+
options { skipDefaultCheckout true }
160162
agent {
161163
dockerfile {
164+
dir 'src'
162165
reuseNode true
163166
}
164167
}
165168
steps {
166169
sh '''
167-
. /opt/ros/$ROS_DISTRO/setup.sh
168170
. install/setup.sh
169-
colcon test --packages-ignore hardware_interface realtime_tools libfranka controller_manager integration_launch_testing --event-handlers console_direct+
171+
colcon test \
172+
--base-paths src \
173+
--packages-select-regex '^franka_(?!bringup$|gripper$)' \
174+
--event-handlers console_direct+ \
175+
--ctest-args --exclude-regex test_hardware
170176
colcon test-result --verbose
171177
'''
172178
}
@@ -176,11 +182,41 @@ pipeline {
176182
}
177183
}
178184
}
185+
stage("Hardware Test"){
186+
// re-enable this when https://franka.atlassian.net/browse/BFFTRAC-2632 is fixed
187+
// when { expression { params.executeHardwareTestsOnRobot } }
188+
when { expression { false } }
189+
options { skipDefaultCheckout true }
190+
agent {
191+
dockerfile {
192+
dir 'src'
193+
reuseNode true
194+
args '--network host ' +
195+
'--cap-add=sys_nice ' +
196+
'--ulimit rtprio=99 ' +
197+
'--ulimit rttime=-1 ' +
198+
'--ulimit memlock=8428281856 ' +
199+
'--security-opt=seccomp=unconfined'
200+
}
201+
}
202+
203+
steps {
204+
script {
205+
echo "Checking connectivity to Master Controller..."
206+
sh "ping -c 5 ${params.robotIp}"
207+
sh """
208+
. install/setup.sh
209+
chmod +x ./src/franka_ros2/scripts/*.sh
210+
./src/franka_ros2/scripts/run_hardware_tests.sh ${params.robotIp}
211+
"""
212+
}
213+
}
214+
}
179215
}
180216
post {
181217
always {
182-
cleanWs()
183218
script {
219+
cleanWs()
184220
notifyBitbucket()
185221
}
186222
}

franka_bringup/CMakeLists.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,27 @@ install(
1818

1919
if(BUILD_TESTING)
2020
find_package(ament_lint_auto REQUIRED)
21+
find_package(launch_testing_ament_cmake REQUIRED)
22+
find_package(ament_cmake_ros REQUIRED)
2123
ament_lint_auto_find_test_dependencies()
24+
25+
if(NOT ROBOT_IP)
26+
set(ROBOT_IP "172.16.0.1")
27+
message(STATUS "ROBOT_IP not defined, defaulting to ${ROBOT_IP}...")
28+
endif()
29+
30+
function(add_ros_isolated_launch_test path)
31+
set(RUNNER "${ament_cmake_ros_DIR}/run_test_isolated.py")
32+
add_launch_test("${path}" RUNNER "${RUNNER}" ARGS "robot_ip:=${ROBOT_IP}" TIMEOUT 500)
33+
endfunction()
34+
35+
add_ros_isolated_launch_test(test/test_hardware_example_controllers.py)
36+
add_ros_isolated_launch_test(test/test_hardware_generic_controller.py)
37+
38+
install(
39+
DIRECTORY test
40+
DESTINATION share/${PROJECT_NAME}
41+
)
2242
endif()
2343

2444
ament_package()

franka_bringup/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# franka_bringup
2+
3+
Launch file package for booting up franka robots.
4+
5+
## Testing
6+
7+
**Note:** To activate the launch file smoke tests, you need to enable the `BUILD_TESTING` CMake flag when building this package:
8+
9+
```bash
10+
colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON
11+
```
12+
13+
### Run the tests on your hardware
14+
15+
```bash
16+
# If you want to use an IP other than 172.16.0.1
17+
colcon build --packages-select franka_bringup --cmake-args -DROBOT_IP=<robot-ip> -DBUILD_TESTING=ON
18+
colcon test --packages-select franka_bringup --event-handlers console_direct+
19+
# to inspect the results
20+
colcon test-result --all --verbose
21+
```

0 commit comments

Comments
 (0)