feat(ansible): add version_lock role for reproducible dependency installation#6934
Conversation
|
Thank you for contributing to the Autoware project! 🚧 If your pull request is in progress, switch it to draft mode. Please ensure:
|
19f2255 to
8a10e7b
Compare
3bf7650 to
966d6c9
Compare
27886d3 to
b21d086
Compare
There was a problem hiding this comment.
Pull request overview
Adds an opt-in “locked” installation path to the dev environment setup to support reproducible dependency versions via Ansible-managed APT pinning and a per-distro/arch lockfile.
Changes:
- Add
--lockedflag tosetup-dev-env.shto enable locked mode and pass lockfile path into Ansible. - Introduce a
version_lockrole that loads a lockfile and writes APT preferences pins, and update existing roles to allow downgrades when locked mode is enabled. - Add lockfile templates plus scripts to generate/validate lockfiles.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| setup-dev-env.sh | Adds --locked flag and passes use_locked_versions + lockfile_path to Ansible. |
| ansible/vars/locked-versions-jazzy-arm64.yaml | Adds jazzy/arm64 lockfile template (empty versions). |
| ansible/vars/locked-versions-jazzy-amd64.yaml | Adds jazzy/amd64 lockfile template (empty versions). |
| ansible/vars/locked-versions-humble-arm64.yaml | Adds humble/arm64 lockfile template (empty versions). |
| ansible/vars/locked-versions-humble-amd64.yaml | Adds humble/amd64 lockfile template (empty versions). |
| ansible/scripts/validate_lockfiles.sh | Adds a script to validate lockfile YAML is a flat dict. |
| ansible/scripts/generate_ansible_lockfile.sh | Adds a script to generate pinned versions from installed packages. |
| ansible/roles/version_lock/tasks/main.yaml | Adds role tasks to load lockfile and manage /etc/apt/preferences.d/autoware-lock. |
| ansible/roles/version_lock/templates/autoware-lock.pref.j2 | Adds APT preferences template for per-package version pins. |
| ansible/roles/ros2_dev_tools/tasks/main.yaml | Allows downgrades when locked mode is enabled. |
| ansible/roles/ros2/tasks/main.yaml | Skips dynamic ros-apt-source install in locked mode; allows downgrades for ROS metapackage install. |
| ansible/roles/rmw_implementation/tasks/main.yaml | Allows downgrades when locked mode is enabled. |
| ansible/roles/geographiclib/tasks/main.yaml | Allows downgrades when locked mode is enabled. |
| ansible/roles/gdown/tasks/main.yaml | Pins gdown version via lockfile in locked mode; allows downgrades for pipx APT install. |
| ansible/roles/docker_engine/tasks/main.yaml | Allows downgrades when locked mode is enabled. |
| ansible/roles/dev_tools/tasks/main.yaml | Allows downgrades when locked mode is enabled. |
| ansible/roles/build_tools/tasks/main.yaml | Allows downgrades when locked mode is enabled. |
| ansible/playbooks/universe.yaml | Adds autoware.dev_env.version_lock role to the universe playbook. |
| .cspell.json | Ignores ansible/vars/ in cspell. |
| .ansible-lint | Excludes ansible/vars/ from ansible-lint. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Could you postpone this dependency pinning task after that issue is completed? |
|
@xmfcx Yes, I could. |
…allation Forward-port of the lockfile mechanism from PR autowarefoundation#6934 onto the install_dev_env.yaml entry point established by autowarefoundation#7052/autowarefoundation#7053: version_lock role (APT preferences, Pin-Priority 1001) with lockfile_path auto-resolution, generator/validator scripts, empty lockfile templates (humble/jazzy x amd64/arm64), and role registration in install_dev_env.yaml. The setup-dev-env.sh --locked wrapper flag from the original PR is intentionally dropped; the supported entry point is --extra-vars use_locked_versions=true. The lockfile package set is aligned with what install_dev_env.yaml actually installs: the Docker Engine packages from the original PR lockfiles are dropped (Docker is installed by the separate install_docker.yaml playbook), and the generator resolves the pipx-managed gdown version via pipx runpip as a fallback to pip3. Refs: autowarefoundation#6862 Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
…allation Forward-port of the lockfile mechanism from PR autowarefoundation#6934 onto the install_dev_env.yaml entry point established by autowarefoundation#7052/autowarefoundation#7053: version_lock role (APT preferences, Pin-Priority 1001) with lockfile_path auto-resolution, generator/validator scripts, empty lockfile templates (humble/jazzy x amd64/arm64), and role registration in install_dev_env.yaml. The setup-dev-env.sh --locked wrapper flag from the original PR is intentionally dropped; the supported entry point is --extra-vars use_locked_versions=true. The lockfile package set is aligned with what install_dev_env.yaml actually installs: the Docker Engine packages from the original PR lockfiles are dropped (Docker is installed by the separate install_docker.yaml playbook), and the generator resolves the pipx-managed gdown version via pipx runpip as a fallback to pip3. Refs: autowarefoundation#6862 Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Add allow_downgrade conditioned on use_locked_versions to all apt tasks in roles managed by the lockfile. APT preferences handle version pinning, so roles only need this single-line addition. Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Wrap ros-apt-source download tasks in a block with when condition to skip network access when using locked versions. The APT source is already configured in locked environments. Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
…allation Forward-port of the lockfile mechanism from PR autowarefoundation#6934 onto the install_dev_env.yaml entry point established by autowarefoundation#7052/autowarefoundation#7053: version_lock role (APT preferences, Pin-Priority 1001) with lockfile_path auto-resolution, generator/validator scripts, empty lockfile templates (humble/jazzy x amd64/arm64), and role registration in install_dev_env.yaml. The setup-dev-env.sh --locked wrapper flag from the original PR is intentionally dropped; the supported entry point is --extra-vars use_locked_versions=true. The lockfile package set is aligned with what install_dev_env.yaml actually installs: the Docker Engine packages from the original PR lockfiles are dropped (Docker is installed by the separate install_docker.yaml playbook), and the generator resolves the pipx-managed gdown version via pipx runpip as a fallback to pip3. Refs: autowarefoundation#6862 Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Generated in clean containers (ubuntu:22.04 / ubuntu:24.04) via generate_ansible_lockfile.sh. The remaining arm64 combos are left empty for the scheduled CI regeneration workflow (Phase C). Refs: autowarefoundation#6862 Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
1bce85b to
27722d4
Compare
|
@xmfcx I'm update this PR with the latest commit. Please review this. |
xmfcx
left a comment
There was a problem hiding this comment.
Thanks for picking this back up, @youtalk! The opt-in design is clean: default-off no-op, the self-cleaning pin file, and one centralized lockfile are all nice. 🙏
I tried use_locked_versions=true in a fresh container and ran into an issue with the ROS packages specifically. I think there's a cleaner mechanism that would make locked mode genuinely reproducible, so wanted to share what I found before approving.
What happens today
In locked mode the ros2 role skips the whole ros-apt-source block (ansible/roles/ros2/tasks/main.yaml:2), which is the only thing that configures packages.ros.org. On a fresh machine the ROS repo then never gets set up, and a later role aborts:
# fresh ubuntu:24.04, from the PR branch / repo root
docker run --rm -v "$PWD":/work:ro -w /work ubuntu:24.04 bash -c '
apt-get update -qq && apt-get install -y -qq sudo git python3-pip pipx >/dev/null
export PATH=$HOME/.local/bin:$PATH
pipx install --include-deps "ansible==10.*" >/dev/null
ansible-galaxy collection install -f -r ansible-galaxy-requirements.yaml >/dev/null
ansible-playbook autoware.dev_env.install_dev_env \
-e use_locked_versions=true --tags version_lock,ros2,ros2_dev_tools
'
# => fatal: ... "No package matching 'python3-colcon-mixin' is available"
# => /etc/apt/sources.list.d/ has no ROS sourceWhy pinning ROS packages against the rolling repo is tricky
I think the block was skipped because the "fetch latest ros-apt-source release" step is non-deterministic. The thing is, ros-apt-source is only the repo-config package. I extracted the .deb and it just installs this:
URIs: http://packages.ros.org/ros2/ubuntu # the rolling repo
Suites: noble
Components: main
Its version (1.2.0) is the config package's own version, not a buildfarm date, so it doesn't influence which ROS package versions you get. And the rolling repo keeps only the newest build, so the date-stamped versions in the lockfile get pruned within weeks. The lockfile in this PR already shows it:
docker run --rm ros:jazzy-ros-base bash -c '
apt-get update -qq
apt-get install -y --dry-run ros-jazzy-desktop=0.11.0-1noble.20260412.072512'
# => E: Version '0.11.0-1noble.20260412.072512' for 'ros-jazzy-desktop' was not foundSuggested direction: dated ROS snapshots
ROS publishes dated snapshot repos at snapshots.ros.org, which are the ROS-native equivalent of snapshot.ubuntu.com. The snapshot nearest the lockfile stamp serves the exact pinned build:
docker run --rm ubuntu:24.04 bash -c '
apt-get update -qq >/dev/null
cat > /etc/apt/sources.list.d/ros2-snap.sources <<EOF
Types: deb
URIs: http://snapshots.ros.org/jazzy/2026-04-13/ubuntu
Suites: noble
Components: main
Trusted: yes
EOF
apt-get update -qq
apt-get install -y --dry-run ros-jazzy-desktop=0.11.0-1noble.20260412.072512 | grep "Inst ros-jazzy-desktop"'
# => Inst ros-jazzy-desktop (0.11.0-1noble.20260412.072512 jazzy/2026-04-13/ubuntu ...) ✅Pointing the ROS apt source at a dated snapshot instead of skipping it would:
- configure the repo deterministically, so locked mode works on a fresh machine (no need to skip the block),
- freeze the entire ROS dependency closure to that date, not just the handful of named packages, which is what actually makes a build reproducible,
- make the lockfile's exact ROS versions installable, and keep them installable over time.
Two small things I noticed while testing: the snapshot is signed with an older key (AD19BAB3CBF125EA), so we'd ship that keyring rather than the rolling ros.key; and the lockfile would want to record the snapshot date (e.g. 2026-04-13), since it can't be recovered from the version string alone.
One option is to keep the APT-preference pins for the stable Ubuntu packages (ccache, git-lfs, etc.) where they work great, and use a dated snapshot for the ROS side. Totally happy to pair on this or help prototype the snapshot source if it's useful. Really glad this is moving again, just want to make sure locked mode holds up on a clean setup. 🙂
…ions Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Move gdown from apt_pins to a new pip_pins section so that the gdown role resolves locked_packages.pip_pins.gdown (fixing the empty-version regression) and pip packages no longer appear as bogus APT pins. Update the validator, generator, and README to handle pip_pins alongside apt_pins. The APT-preferences template is unchanged (already excludes pip_pins by iterating only apt_pins + ros_overrides). Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
|
Thanks @xmfcx — agreed, the dated snapshot is the right mechanism. It was actually the plan I'd noted in the design discussion (#6861, this comment): point locked mode at
Verified on fork CI across amd64 + arm64 × humble + jazzy:
A full clean-container locked install also completes locally, installing the exact snapshot build |
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
|
@xmfcx @mitsudome-r @oguzkaganozt Please review this. |
|
@xmfcx @mitsudome-r @oguzkaganozt @isamu-takagi Please review this. I would like to move forward. |
xmfcx
left a comment
There was a problem hiding this comment.
I ran an automated review and tested the high severity items and verified manually.
For AI generated PRs (especially for high impact ones) I'd suggest spawning some fresh context AI agents locally and ask for "adversarial review". And iterate until nothing sticks out.
The website-copilot reviews are quite bad in my opinion.
On claude /effort ultracode I prompted:
check out locally and adversarially review https://github.com/autowarefoundation/autoware/pull/6934 (dont post anything)
in my autoware workspace and it was pretty useful.
Address the adversarial review on the version_lock PR (findings F1, F3, F4, F5). Each item was reproduced in a container before and after the fix. F1: the locked-mode block only added the dated snapshot source, so a machine that already had the rolling packages.ros.org repo kept installing the newer rolling build (both sit at apt priority 500). Add a "Package: * / Pin: origin snapshots.ros.org / Pin-Priority: 1001" stanza to autoware-lock.pref.j2 so the snapshot outranks the rolling repo. F3: an APT pin to a version that exists in no configured source matches nothing and apt silently installs another candidate. Add a post-install task (version_lock/tasks/verify.yaml, run from the playbook post_tasks) that compares the installed version of every apt_pins/ros_overrides entry against the lockfile and fails listing all drift. Render ros_overrides at Pin-Priority 1002 so an override beats the snapshot-origin pin, and fix the README override example to a reachable version. F4: locked mode left already-installed packages on the wrong version. Pass force in locked mode to the gdown pipx task (pipx is otherwise a no-op on an existing venv) and switch git-lfs from state: present to state: latest so the pin is honored. F5: consumers dereferenced locked_packages with no default, so any run that skipped version_lock (e.g. --tags ros2) crashed mid-play after mutating system state. Tag the version_lock role [always] and add a locked_packages-defined assert in the ros2 and gdown locked branches. Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
d854b15 to
4775550
Compare
|
Thanks for the thorough adversarial review, @xmfcx — the container repros made each issue easy to confirm and fix. Addressed all four inline findings in 4775550, each reproduced before and after the fix:
I took your suggestion and drove the fixes with a fresh-context agent that re-ran each of your reproductions in throwaway containers. The one durable Ubuntu-side follow-up (dated |
xmfcx
left a comment
There was a problem hiding this comment.
Thank you @youtalk for the patient iterations here and for the quick, well-verified turnaround on the review findings. I re-checked the branch: all four findings are addressed correctly, the lockfiles are consistent across distros and architectures, and the fork CI matrix is green. Approving.
The deferred follow-ups we discussed (Ubuntu-side dated snapshots, locked-mode CI coverage) can come in separate PRs whenever you get to them.
Related to #6862 and the design discussion #6861.
Description
Adds an opt-in, reproducible dependency-install mode for
install_dev_env.yaml. Locked mode pins the entire ROS dependency closure to a datedsnapshots.ros.orgsnapshot, and pins the stable Ubuntu/pip packages via APT preferences. It is fully opt-in and leaves default behavior unchanged.How it works
ros2role configures the APT source as a deb822.sourcesfile pointing athttp://snapshots.ros.org/{distro}/{date}/ubuntu, signed by a committed snapshot keyring (the older OSRF keyAD19BAB3CBF125EA). This replaces the previous "skip theros-apt-sourceblock" behavior, so locked mode works on a fresh machine and the whole ROS dependency closure is frozen to the snapshot date — which also keeps the exact pinned ROS builds installable over time. (This is the direction agreed in the review thread and in Dependency Version Pinning for Reproducible Builds #6861.)version_lockrole writes/etc/apt/preferences.d/autoware-lockwithPin-Priority: 1001for the stable Ubuntu-archive packages (apt_pins). pip/pipx packages (e.g.gdown) live in a separatepip_pinssection consumed directly by their role and are not rendered as APT pins.ansible/vars/locked-versions-{distro}-{arch}.yaml):ros_snapshot_date,apt_pins,pip_pins,ros_overrides.ros_overrides(ROS) orapt_pins(Ubuntu) entry in a small, focused PR.Usage
Main changes
ansible/roles/version_lock/— loads the lockfile, assertsros_snapshot_dateis present in locked mode, writes/cleans the APT preferences pin file.ansible/roles/ros2/— dated-snapshot source in locked mode + committed snapshot keyring (files/ros-snapshot-archive-keyring.gpg); default (rolling) path unchanged.ansible/scripts/generate_ansible_lockfile.sh,ansible/scripts/validate_lockfiles.sh— generate / validate the nested lockfiles.ansible/vars/locked-versions-{humble,jazzy}-{amd64,arm64}.yaml— all four filled (amd64 and arm64 regenerated on real runners).How was this PR tested?
pre-commit run --all-files(ansible) andansible-lintpass;ansible-playbook --syntax-checkpasses in both default and-e use_locked_versions=truemodes.ansible/scripts/validate_lockfiles.shpasses on the amd64 lockfiles.python3-colcon-mixinandros-<distro>-desktopinstall from the snapshot (humble + jazzy).install_dev_envcompletes locally and installs the exact snapshot buildros-jazzy-desktop=0.11.0-1noble.20260412.072512.