Skip to content

Commit c58060d

Browse files
authored
Merge pull request #67 from LockedThread/feat/upgrade-debian
Upgrade debian
2 parents f048ec5 + 77f41fc commit c58060d

6 files changed

Lines changed: 296 additions & 82 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Build and Validate
2+
3+
on:
4+
pull_request:
5+
branches: [main, development]
6+
7+
jobs:
8+
build-and-validate:
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
include:
13+
- platform: linux/amd64
14+
runner: ubuntu-latest
15+
- platform: linux/arm64
16+
runner: ubuntu-24.04-arm
17+
runs-on: ${{ matrix.runner }}
18+
permissions:
19+
contents: read
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v6
23+
24+
- name: Set up Docker Buildx
25+
uses: docker/setup-buildx-action@v3
26+
27+
- name: Build image (${{ matrix.platform }})
28+
run: docker build --platform ${{ matrix.platform }} -t gtsam_docker:latest .
29+
30+
- name: Validate image (PlanarSLAM example)
31+
run: |
32+
chmod +x ./scripts/validate_container.sh
33+
./scripts/validate_container.sh gtsam_docker:latest /examples/PlanarSLAMExample.py

.github/workflows/release.yaml

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ jobs:
2121
build:
2222
strategy:
2323
matrix:
24-
platform: [ "linux/amd64" ]
25-
# Use GitHub-hosted runner for amd64 and the arm64 partner runner for arm64
24+
platform: [ "linux/amd64", "linux/arm64" ]
25+
# Use GitHub-hosted runner for amd64; arm64 uses partner runner (ensure ubuntu-24.04-arm is enabled for the repo/org)
2626
runs-on: ${{ matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
2727
permissions:
2828
contents: read
@@ -62,8 +62,37 @@ jobs:
6262
tags: ${{ steps.meta.outputs.tags }}
6363
labels: ${{ steps.meta.outputs.labels }}
6464

65-
manifest:
65+
validate:
6666
needs: build
67+
strategy:
68+
fail-fast: false
69+
matrix:
70+
arch: [ amd64, arm64 ]
71+
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
72+
permissions:
73+
contents: read
74+
packages: read
75+
steps:
76+
- name: Checkout
77+
uses: actions/checkout@v6
78+
79+
- name: Log into registry ${{ env.REGISTRY }}
80+
uses: docker/login-action@v3
81+
with:
82+
registry: ${{ env.REGISTRY }}
83+
username: ${{ github.actor }}
84+
password: ${{ secrets.GITHUB_TOKEN }}
85+
86+
- name: Pull and validate image (${{ matrix.arch }})
87+
run: |
88+
LOWER_IMAGE_NAME=$(echo "${{ env.IMAGE_NAME }}" | tr '[:upper:]' '[:lower:]')
89+
docker pull ${{ env.REGISTRY }}/${LOWER_IMAGE_NAME}:${{ inputs.version }}-${{ matrix.arch }}
90+
docker tag ${{ env.REGISTRY }}/${LOWER_IMAGE_NAME}:${{ inputs.version }}-${{ matrix.arch }} gtsam_docker:latest
91+
chmod +x ./scripts/validate_container.sh
92+
./scripts/validate_container.sh gtsam_docker:latest /examples/PlanarSLAMExample.py
93+
94+
manifest:
95+
needs: validate
6796
runs-on: ubuntu-latest
6897
permissions:
6998
contents: read
@@ -78,18 +107,18 @@ jobs:
78107

79108
- name: Create multi-arch manifest for latest
80109
run: |
81-
# Convert IMAGE_NAME to lowercase
82110
LOWER_IMAGE_NAME=$(echo "${IMAGE_NAME}" | tr '[:upper:]' '[:lower:]')
83111
docker manifest create $REGISTRY/${LOWER_IMAGE_NAME}:latest \
84-
$REGISTRY/${LOWER_IMAGE_NAME}:latest-amd64
112+
$REGISTRY/${LOWER_IMAGE_NAME}:latest-amd64 \
113+
$REGISTRY/${LOWER_IMAGE_NAME}:latest-arm64
85114
docker manifest push $REGISTRY/${LOWER_IMAGE_NAME}:latest
86115
87116
- name: Create multi-arch manifest for version tag
88117
run: |
89-
# Convert IMAGE_NAME to lowercase
90118
LOWER_IMAGE_NAME=$(echo "${IMAGE_NAME}" | tr '[:upper:]' '[:lower:]')
91119
docker manifest create $REGISTRY/${LOWER_IMAGE_NAME}:${{ inputs.version }} \
92-
$REGISTRY/${LOWER_IMAGE_NAME}:${{ inputs.version }}-amd64
120+
$REGISTRY/${LOWER_IMAGE_NAME}:${{ inputs.version }}-amd64 \
121+
$REGISTRY/${LOWER_IMAGE_NAME}:${{ inputs.version }}-arm64
93122
docker manifest push $REGISTRY/${LOWER_IMAGE_NAME}:${{ inputs.version }}
94123
95124
release:

Dockerfile

Lines changed: 50 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
1-
FROM debian:bullseye as dependencies
1+
# Default build produces the "runtime" stage (slim). Use --target gtsam for a dev image with build tools and shell.
2+
FROM debian:trixie-20260112 AS dependencies
23
ARG PYTHON_VERSION=3.11.2
34

45
# Disable GUI prompts
5-
ENV DEBIAN_FRONTEND noninteractive
6+
ENV DEBIAN_FRONTEND=noninteractive
67

7-
8-
RUN rm /var/lib/dpkg/info/libc-bin.*
9-
RUN apt-get clean && apt-get update
10-
RUN apt-get -y install libc-bin
11-
12-
# Install required build dependencies
13-
RUN apt-get update && apt-get install -y \
8+
# Install required build dependencies (single update for better layer caching)
9+
RUN apt-get update && apt-get install -y --no-install-recommends \
10+
ca-certificates \
1411
build-essential \
1512
wget \
1613
libssl-dev \
@@ -33,56 +30,9 @@ RUN apt-get update && apt-get install -y \
3330
libmpc-dev \
3431
libmpfr-dev \
3532
libgmp-dev \
33+
make \
3634
&& rm -rf /var/lib/apt/lists/*
3735

38-
39-
# Download and build GCC 13.4
40-
RUN wget https://ftp.gnu.org/gnu/gcc/gcc-13.4.0/gcc-13.4.0.tar.gz && \
41-
tar -xzf gcc-13.4.0.tar.gz && \
42-
cd gcc-13.4.0 && \
43-
./contrib/download_prerequisites && \
44-
mkdir build && \
45-
cd build && \
46-
../configure --prefix=/usr/local/gcc-13.4.0 \
47-
--enable-languages=c,c++ \
48-
--disable-multilib \
49-
--disable-bootstrap \
50-
--enable-checking=release && \
51-
make -j$(nproc) && \
52-
make install && \
53-
cd ../.. && \
54-
rm -rf gcc-13.4.0 gcc-13.4.0.tar.gz
55-
56-
# Set up GCC 13.4 as the default compiler
57-
ENV PATH="/usr/local/gcc-13.4.0/bin:${PATH}"
58-
ENV LD_LIBRARY_PATH="/usr/local/gcc-13.4.0/lib64:${LD_LIBRARY_PATH}"
59-
ENV CC="/usr/local/gcc-13.4.0/bin/gcc"
60-
ENV CXX="/usr/local/gcc-13.4.0/bin/g++"
61-
62-
# Create symlinks for easier access
63-
RUN ln -sf /usr/local/gcc-13.4.0/bin/gcc /usr/local/bin/gcc && \
64-
ln -sf /usr/local/gcc-13.4.0/bin/g++ /usr/local/bin/g++
65-
66-
# Install Make 4.4.1
67-
RUN wget https://ftp.gnu.org/gnu/make/make-4.4.1.tar.gz && \
68-
tar -xzf make-4.4.1.tar.gz && \
69-
cd make-4.4.1 && \
70-
./configure --prefix=/usr/local && \
71-
make -j$(nproc) && \
72-
make install && \
73-
cd .. && \
74-
rm -rf make-4.4.1 make-4.4.1.tar.gz
75-
76-
# Install CMake 4.0.3
77-
RUN wget https://github.com/Kitware/CMake/releases/download/v4.0.3/cmake-4.0.3.tar.gz && \
78-
tar -xzf cmake-4.0.3.tar.gz && \
79-
cd cmake-4.0.3 && \
80-
./bootstrap --prefix=/usr/local && \
81-
make -j$(nproc) && \
82-
make install && \
83-
cd .. && \
84-
rm -rf cmake-4.0.3 cmake-4.0.3.tar.gz
85-
8636
# Set working directory
8737
WORKDIR /usr/src
8838

@@ -100,33 +50,33 @@ RUN wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VER
10050
# Ensure /usr/local/bin is in the PATH
10151
ENV PATH="/usr/local/bin:${PATH}"
10252

103-
RUN python3 -m pip install --upgrade pip
53+
RUN python3 -m pip install --no-cache-dir --upgrade pip
10454

10555
# Use git to clone gtsam and specific GTSAM version
106-
FROM alpine/git:2.52.0 as gtsam-clone
56+
FROM alpine/git:2.52.0 AS gtsam-clone
10757

10858
ARG GTSAM_VERSION=4.2.0
10959
WORKDIR /usr/src/
11060

111-
# Clone GTSAM and checkout to given GTSAM_VERSION tag
112-
RUN git clone --no-checkout https://github.com/borglab/gtsam.git && \
113-
cd gtsam && \
114-
git fetch origin tag ${GTSAM_VERSION} && \
115-
git checkout ${GTSAM_VERSION}
61+
# Shallow clone specific tag for smaller, faster fetch
62+
RUN git clone --depth 1 --branch ${GTSAM_VERSION} https://github.com/borglab/gtsam.git
11663

11764
# Create new stage called gtsam for GTSAM building
118-
FROM dependencies as gtsam
65+
FROM dependencies AS gtsam
66+
67+
ARG PYTHON_VERSION=3.11.2
68+
69+
# Needed to link with GTSAM (ENV works in non-interactive shells; .bashrc does not)
70+
ENV LD_LIBRARY_PATH=/usr/local/lib
11971

12072
# Move gtsam data
12173
COPY --from=gtsam-clone /usr/src/gtsam /usr/src/gtsam
12274

12375
WORKDIR /usr/src/gtsam/build
12476

125-
# Needed to link with GTSAM
126-
RUN echo "export LD_LIBRARY_PATH=/usr/local/lib:\$LD_LIBRARY_PATH" >> /root/.bashrc
127-
128-
# Install python wrapper requirements
129-
RUN python3 -m pip install -U -r /usr/src/gtsam/python/requirements.txt
77+
# Install python wrapper requirements, then pin numpy for GTSAM ABI compatibility
78+
RUN python3 -m pip install --no-cache-dir -U -r /usr/src/gtsam/python/requirements.txt && \
79+
python3 -m pip install --no-cache-dir "numpy==1.26.4"
13080

13181
# Run cmake
13282
RUN cmake \
@@ -141,14 +91,39 @@ RUN cmake \
14191
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
14292
..
14393

144-
# Make install and clean up
94+
# Build, install, strip binaries, and clean in one layer to reduce image size
14595
RUN make -j$(nproc) install && \
14696
make python-install && \
147-
make clean
97+
#find /usr/local -type f \( -name "*.so" -o -name "*.so.*" \) -exec strip --strip-unneeded {} \; 2>/dev/null || true && \
98+
#find /usr/local/bin /usr/local/lib -executable -type f -exec strip --strip-unneeded {} \; 2>/dev/null || true && \
99+
make clean && \
100+
ldconfig
101+
102+
# Final cleanup (dependencies stage already cleared apt lists)
103+
RUN rm -rf /tmp/* /var/tmp/*
104+
105+
# -----------------------------------------------------------------------------
106+
# Slim runtime stage: copy only installed artifacts, no build tools or source
107+
# -----------------------------------------------------------------------------
108+
FROM debian:trixie-slim AS runtime
109+
110+
ENV DEBIAN_FRONTEND=noninteractive
111+
ENV PATH="/usr/local/bin:${PATH}"
112+
ENV LD_LIBRARY_PATH=/usr/local/lib
113+
114+
# Runtime libs only. Python binary (ldd python3.11) needs only libc/libm/libpython; GTSAM needs Boost + TBB (see scripts/audit-runtime-deps.sh).
115+
# Add back libssl3t64 libbz2-1.0 libreadline8t64 libsqlite3-0 libffi8 zlib1g libncursesw6 if you import ssl/sqlite3/readline/etc.
116+
RUN apt-get update && apt-get install -y --no-install-recommends \
117+
ca-certificates \
118+
libtbb12 \
119+
libtbbmalloc2 \
120+
libboost-serialization1.83.0 \
121+
libboost-filesystem1.83.0 \
122+
libboost-timer1.83.0 \
123+
&& rm -rf /var/lib/apt/lists/*
148124

149-
RUN apt-get clean && \
150-
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
125+
COPY --from=gtsam /usr/local /usr/local
151126

152127
RUN ldconfig
153128

154-
CMD ["bash"]
129+
CMD ["python3"]

examples/PlanarSLAMExample.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""
2+
GTSAM Copyright 2010-2018, Georgia Tech Research Corporation,
3+
Atlanta, Georgia 30332-0415
4+
All Rights Reserved
5+
Authors: Frank Dellaert, et al. (see THANKS for the full author list)
6+
See LICENSE for the license information
7+
8+
Simple robotics example using odometry measurements and bearing-range (laser) measurements.
9+
From borglab/gtsam python/gtsam/examples/PlanarSLAMExample.py (GTSAM 4.2.0).
10+
11+
Run inside container: python3 /examples/PlanarSLAMExample.py
12+
"""
13+
# pylint: disable=invalid-name, E1101
14+
15+
from __future__ import print_function
16+
17+
import sys
18+
import gtsam
19+
import numpy as np
20+
from gtsam.symbol_shorthand import L, X
21+
22+
# Create noise models
23+
PRIOR_NOISE = gtsam.noiseModel.Diagonal.Sigmas(np.array([0.3, 0.3, 0.1]))
24+
ODOMETRY_NOISE = gtsam.noiseModel.Diagonal.Sigmas(np.array([0.2, 0.2, 0.1]))
25+
MEASUREMENT_NOISE = gtsam.noiseModel.Diagonal.Sigmas(np.array([0.1, 0.2]))
26+
27+
28+
def main():
29+
"""Main runner."""
30+
# Create an empty nonlinear factor graph
31+
graph = gtsam.NonlinearFactorGraph()
32+
33+
# Create the keys corresponding to unknown variables in the factor graph
34+
x1, x2, x3 = X(1), X(2), X(3)
35+
l1, l2 = L(4), L(5)
36+
37+
# Add a prior on pose X1 at the origin
38+
graph.add(
39+
gtsam.PriorFactorPose2(x1, gtsam.Pose2(0.0, 0.0, 0.0), PRIOR_NOISE)
40+
)
41+
42+
# Add odometry factors between X1,X2 and X2,X3
43+
graph.add(
44+
gtsam.BetweenFactorPose2(x1, x2, gtsam.Pose2(2.0, 0.0, 0.0), ODOMETRY_NOISE)
45+
)
46+
graph.add(
47+
gtsam.BetweenFactorPose2(x2, x3, gtsam.Pose2(2.0, 0.0, 0.0), ODOMETRY_NOISE)
48+
)
49+
50+
# Add Range-Bearing measurements to two different landmarks L1 and L2
51+
graph.add(
52+
gtsam.BearingRangeFactor2D(
53+
x1, l1, gtsam.Rot2.fromDegrees(45), np.sqrt(4.0 + 4.0), MEASUREMENT_NOISE
54+
)
55+
)
56+
graph.add(
57+
gtsam.BearingRangeFactor2D(x2, l1, gtsam.Rot2.fromDegrees(90), 2.0, MEASUREMENT_NOISE)
58+
)
59+
graph.add(
60+
gtsam.BearingRangeFactor2D(x3, l2, gtsam.Rot2.fromDegrees(90), 2.0, MEASUREMENT_NOISE)
61+
)
62+
63+
print("Factor Graph:\n{}".format(graph))
64+
65+
# Create (deliberately inaccurate) initial estimate
66+
initial_estimate = gtsam.Values()
67+
initial_estimate.insert(x1, gtsam.Pose2(-0.25, 0.20, 0.15))
68+
initial_estimate.insert(x2, gtsam.Pose2(2.30, 0.10, -0.20))
69+
initial_estimate.insert(x3, gtsam.Pose2(4.10, 0.10, 0.10))
70+
initial_estimate.insert(l1, gtsam.Point2(1.80, 2.10))
71+
initial_estimate.insert(l2, gtsam.Point2(4.10, 1.80))
72+
73+
print("Initial Estimate:\n{}".format(initial_estimate))
74+
75+
# Optimize using Levenberg-Marquardt
76+
params = gtsam.LevenbergMarquardtParams()
77+
optimizer = gtsam.LevenbergMarquardtOptimizer(graph, initial_estimate, params)
78+
result = optimizer.optimize()
79+
print("\nFinal Result:\n{}".format(result))
80+
81+
# Calculate and print marginal covariances
82+
marginals = gtsam.Marginals(graph, result)
83+
for (key, label) in [(x1, "X1"), (x2, "X2"), (x3, "X3"), (l1, "L1"), (l2, "L2")]:
84+
print("{} covariance:\n{}\n".format(label, marginals.marginalCovariance(key)))
85+
86+
# Validation: expect result size and non-NaN covariances
87+
assert result.size() == 5, "Expected 5 values in result"
88+
_ = marginals.marginalCovariance(x1) # will raise if invalid
89+
print("VALIDATION OK")
90+
return 0
91+
92+
if __name__ == "__main__":
93+
sys.exit(main())

0 commit comments

Comments
 (0)