Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/actions/setup-podman/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ runs:
systemctl --user enable --now podman.socket
echo "DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock" >> $GITHUB_ENV

- name: Expose Podman socket as /var/run/docker.sock
shell: bash
run: |
# GitHub Actions container-based actions mount /var/run/docker.sock
# hardcoded. Symlink it to the Podman socket so those actions work.
sudo ln -sf "$XDG_RUNTIME_DIR/podman/podman.sock" /var/run/docker.sock
# sudo chmod 666 "$XDG_RUNTIME_DIR/podman/podman.sock"

- name: Verify Podman and podman-compose
shell: bash
run: |
Expand Down
66 changes: 66 additions & 0 deletions .github/workflows/_robot-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Robot Tests

on:
workflow_call:
inputs:
release_version:
required: true
type: string

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
robot-tests:
runs-on: ubuntu-24.04

steps:
- name: Checkout repository
uses: actions/checkout@v7

- name: Set up Podman
uses: ./.github/actions/setup-podman

- name: Login to ghcr.io
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | \
podman login ghcr.io \
-u "${{ github.actor }}" \
--password-stdin

- name: Download runtime image (amd64)
uses: actions/download-artifact@v8
with:
name: runtime-image-amd64

- name: Load runtime image
run: |
zstd -d runtime.tar.zst -o runtime.tar
podman load < runtime.tar
podman images
podman tag \
ghcr.io/${{ github.repository }}:latest-amd64 \
packtly-builder:latest

- name: Run Robot Framework tests
run: ./test/robot/run.sh

- name: Fix step summary permissions for Robot Reporter
if: always()
run: chmod o+w "$(dirname "$GITHUB_STEP_SUMMARY")"

- name: Robot Reporter
if: always() && github.event_name == 'pull_request'
uses: joonvena/[email protected]
with:
gh_access_token: ${{ secrets.GITHUB_TOKEN }}
report_path: test/robot/results

- name: Upload Robot results
if: always()
uses: actions/upload-artifact@v7
with:
name: robot-results
path: test/robot/results
if-no-files-found: warn
retention-days: 7
17 changes: 12 additions & 5 deletions .github/workflows/_tooling.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,20 @@ jobs:
command: just build
release-version: ${{ inputs.release_version }}

- name: Publish test results
uses: dorny/test-reporter@v3
- name: Publish Unit Test Results
uses: mikepenz/action-junit-report@v6
if: always()
with:
name: Pytest Results
path: packtly-builder/tooling/python-test-results.xml
reporter: python-xunit
comment: true
detailed_summary: true
group_suite: true
include_passed: true
include_time_in_summary: true
job_summary: true
report_paths: packtly-builder/tooling/python-test-results.xml
simplified_summary: false
skip_success_summary: false
updateComment: true

- name: Coverage summary
uses: orgoro/coverage@ca0c362dc1a4f100447309405e6dfea47e251495
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,13 @@ jobs:
permissions:
packages: write

robot-tests:
needs:
- runtime-manifest
uses: ./.github/workflows/_robot-tests.yml
with:
release_version: ${{ needs.version.outputs.release_version }}
secrets: inherit
permissions:
checks: write
pull-requests: write
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,11 @@ cover/
python-test-results.xml
test-output.xml
*.log

# Robot Framework artifacts
output.xml
report.html
log.html
xunit.xml
debugfile.txt
test/robot/results/
12 changes: 6 additions & 6 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"version": "0.2.0",
"configurations": [
{
"name": "builder_tooling_upload",
"name": "packtly_build_full",
"stopOnEntry": true,
"type": "debugpy",
"request": "launch",
Expand All @@ -13,16 +13,14 @@
"--credentials-file=${workspaceFolder}/test/aptly-credentials",
"--dist=trixie-apollo",
"--component=main",
"--no-build",
"--build-mode=full",
"--upload",
"${workspaceFolder}/test/debhello",
"${workspaceFolder}/test/fixtures/debhello-quilt",
],
"console": "integratedTerminal",
"justMyCode": false
},
{
"name": "builder_tooling_build_full",
"name": "packtly_upload",
"stopOnEntry": true,
"type": "debugpy",
"request": "launch",
Expand All @@ -33,8 +31,10 @@
"--credentials-file=${workspaceFolder}/test/aptly-credentials",
"--dist=trixie-apollo",
"--component=main",
"--no-build",
"--build-mode=full",
"${workspaceFolder}/test/debhello",
"--upload",
"${workspaceFolder}/test/fixtures/debhello-quilt",
],
"console": "integratedTerminal",
"justMyCode": false
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"python.testing.pytestArgs": [
"-s",
"--verbose",
"--dpkgbuild=test/debhello",
"--dpkgbuild=test/fixtures/debhello-quilt",
"--no-cov",
"packtly-builder/tooling/packtly_builder_tooling"
],
Expand Down
46 changes: 46 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,52 @@
"cwd": "${workspaceFolder}/packtly-builder/tooling"
},
"problemMatcher": []
},
{
"label": "run robot tests",
"type": "shell",
"command": "${workspaceFolder}/test/robot/run.sh",
"problemMatcher": [
"$python"
]
},
{
"label": "packtly_build_full",
"type": "shell",
"command": "/opt/venv/bin/python",
"args": [
"-m", "packtly_builder_tooling.cli",
"--aptlyhost=http://localhost:8080",
"--credentials-file=${workspaceFolder}/test/aptly-credentials",
"--dist=trixie-apollo",
"--component=main",
"--build-mode=full",
"${workspaceFolder}/test/fixtures/debhello-quilt"
],
"options": {
"cwd": "${workspaceFolder}/packtly-builder/tooling"
},
"problemMatcher": []
},
{
"label": "packtly_upload",
"type": "shell",
"command": "/opt/venv/bin/python",
"args": [
"-m", "packtly_builder_tooling.cli",
"--aptlyhost=http://localhost:8080",
"--credentials-file=${workspaceFolder}/test/aptly-credentials",
"--dist=trixie-apollo",
"--component=main",
"--no-build",
"--build-mode=full",
"--upload",
"${workspaceFolder}/test/fixtures/debhello-quilt"
],
"options": {
"cwd": "${workspaceFolder}/packtly-builder/tooling"
},
"problemMatcher": []
}
],
"inputs": [
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ RegEx for release version from file
r"^\#\# \[\d{1,}[.]\d{1,}[.]\d{1,}\] \- \d{4}\-\d{2}-\d{2}$"
-->

## [1.3.0] - 2026-07-13
### Added
- Robot Framework integration test environment for end-to-end validation of packtly-builder builds via Podman
- Robot test runner, keywords, and a Debian example build/verification suite
- Debian packaging fixtures (`test/fixtures/debhello-quilt`) for integration testing
- CI workflow for running Robot Framework tests with artifact upload and PR reporting via `robotframework-reporter-action`


## [1.2.0] - 2026-07-04
### Added
- Source package build
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
8 changes: 6 additions & 2 deletions packtly-builder/tooling/packtly_builder_tooling/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,12 @@ def build_package(
logger.info("Skipping build step (--no-build)")
return dbuild

dbuild.install_build_dependencies()
dbuild.build(mode=build_mode)
try:
dbuild.install_build_dependencies()
dbuild.build(mode=build_mode)
except FileNotFoundError as exc:
logger.error("Build failed: %s", exc)
raise SystemExit(1) from exc

logger.info("Changes file: %s", dbuild.deb_changes_file())
logger.info("Architecture: %s", dbuild.deb_changes_arch())
Expand Down
61 changes: 48 additions & 13 deletions packtly-builder/tooling/packtly_builder_tooling/parts/apt.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,24 +193,58 @@ def install_dependencies(
return False

package_name, req_version, req_relation = parsed[0][0]
self.cache.open()
is_virtual = package_name not in self.cache
resolved_name: Optional[str] = None

if req_version and req_relation:
self.cache.open()
resolved_name = self._resolve_package_name(package_name)
Comment thread
Copilot marked this conversation as resolved.
if resolved_name is None:
self.logger.error("Package '%s' not found in cache.", package_name)
return False

pkg = self.cache[resolved_name]
candidate = pkg.candidate
if not apt_pkg.check_dep(candidate.version, req_relation, req_version):
self.logger.error(
"Package '%s' candidate %s does not satisfy %s %s",
resolved_name,
candidate.version,
req_relation,
req_version,
)
if is_virtual:
# For virtual packages the version constraint applies to the
# *provided* version, not the real provider's version. Check
# that at least one provides entry satisfies the constraint.
try:
apt_pkg_entry = self.cache._cache[package_name]
satisfied = any(
prov[1]
and apt_pkg.check_dep(prov[1], req_relation, req_version)
for prov in apt_pkg_entry.provides_list
if prov[2].parent_pkg.name == resolved_name
)
except (KeyError, AttributeError):
satisfied = False
if not satisfied:
self.logger.error(
"No provider for '%s %s %s' found in cache.",
package_name,
req_relation,
req_version,
)
return False
else:
pkg = self.cache[resolved_name]
candidate = pkg.candidate
if not apt_pkg.check_dep(candidate.version, req_relation, req_version):
self.logger.error(
"Package '%s' candidate %s does not satisfy %s %s",
resolved_name,
candidate.version,
req_relation,
req_version,
)
return False

if is_virtual:
# Install the real provider; do not pin to the virtual version.
install_name = resolved_name or self._resolve_package_name(package_name)
if install_name is None:
self.logger.error("Package '%s' not found in cache.", package_name)
return False
return self.install_package(install_name, source_host=source_host)

pinned_version = req_version if req_relation in ("=", "==") else None

Expand Down Expand Up @@ -433,11 +467,12 @@ def _resolve_package_name(self, package_name: str) -> Optional[str]:
if package_name in self.cache:
return package_name

# The name may be a virtual package. Walk rev_provides_list to
# The name may be a virtual package. Walk provides_list to
# find a real package that satisfies it.
# provides_list entries are tuples: (virtual_name, provided_version, apt_pkg.Version)
try:
apt_pkg_entry = self.cache._cache[package_name]
providers = [v.parent_pkg.name for v in apt_pkg_entry.rev_provides_list]
providers = [v[2].parent_pkg.name for v in apt_pkg_entry.provides_list]
except (KeyError, AttributeError):
providers = []

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def __init__(self, builddir: Path) -> None:
self.parsed_control_info = Deb822()
self.parsed_deb_info = Deb822()
if debuild_executable:
print("debuild executable found at:", debuild_executable)
logger.info("debuild executable found at: %s", debuild_executable)
self._debuild = debuild_executable
self._builddir = builddir
self._outdir = Path(builddir).parent.resolve()
Expand Down
2 changes: 1 addition & 1 deletion packtly-builder/tooling/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ packtly_builder_tooling = "packtly_builder_tooling.cli:main"

[tool.pytest.ini_options]
addopts = """\
--dpkgbuild=../../test/debhello \
--dpkgbuild=../../test/fixtures/debhello-quilt \
--doctest-modules \
--junitxml=python-test-results.xml \
--cov-report=term \
Expand Down
Loading