diff --git a/.github/actions/run-packaging-tests/action.yml b/.github/actions/run-packaging-tests/action.yml new file mode 100644 index 00000000..81711582 --- /dev/null +++ b/.github/actions/run-packaging-tests/action.yml @@ -0,0 +1,52 @@ +name: 'Run Packaging Tests' +description: 'Runs the packaging tests' + +inputs: + distro: + description: 'Distribution to test (bookworm, jammy, noble)' + required: true + language: + description: 'Language to test' + required: true + run_piuparts: + description: 'Whether to run piuparts' + required: false + default: 'false' + run_autopkgtest: + description: 'Whether to run autopkgtest' + required: false + default: 'false' + +runs: + using: "composite" + steps: + - name: Package + shell: bash + run: | + cd examples/${{ inputs.distro }}/${{ inputs.language }}/hello-world + pkg-builder package --run-piuparts ${{ inputs.run_piuparts }} --run-autopkgtest ${{ inputs.run_autopkgtest }} + + - name: piuparts + if: ${{ inputs.run_piuparts == 'true' }} + shell: bash + run: | + # installing debian-archive-keyring fails on ubuntu LTS, not sure why, but it says it is already installed + # sudo apt-get install -y debian-archive-keyring + cd examples/${{ inputs.distro }}/${{ inputs.language }}/hello-world + ${HOME}/.local/bin/pkg-builder piuparts + + # TODO version parsing fails, as it doesn't use semver + - name: autopkgtest + if: ${{ inputs.run_autopkgtest == 'true' }} + shell: bash + run: | + sudo cp -R ${HOME}/.pkg-builder /root + apt list --installed autopkgtest + cd examples/${{ inputs.distro }}/${{ inputs.language }}/hello-world + sudo ${HOME}/.local/bin/pkg-builder autopkgtest + + - name: Verify + shell: bash + run: | + cd examples/${{ inputs.distro }}/${{ inputs.language }}/hello-world + ${HOME}/.local/bin/pkg-builder verify --no-package true \ No newline at end of file diff --git a/.github/actions/setup-packaging-env/action.yml b/.github/actions/setup-packaging-env/action.yml new file mode 100644 index 00000000..46abbae4 --- /dev/null +++ b/.github/actions/setup-packaging-env/action.yml @@ -0,0 +1,39 @@ +name: 'Setup Packaging Environment' +description: 'Sets up environment for packaging tests' + +inputs: + include_genisoimage: + description: 'Whether to include genisoimage' + required: false + default: 'false' + +runs: + using: "composite" + steps: + - name: Setup sbuild + uses: ./.github/actions/setup-sbuild + + - name: Additional dependencies + shell: bash + run: | + sudo apt install -y autopkgtest vmdb2 qemu-system-x86 + if [ "${{ inputs.include_genisoimage }}" = "true" ]; then + sudo apt-get install -y genisoimage + fi + + - name: Build pkg-builder + shell: bash + run: | + cargo build --verbose + cargo build --release + + - name: Install pkg-builder + shell: bash + run: | + mkdir -p ${HOME}/.local/bin + mv target/release/pkg-builder ${HOME}/.local/bin + # add to path the prebuilt debcrafter binaries as well + cp workspace/pkg_builder/bin_dependencies/debcrafter_* ${HOME}/.local/bin + chmod +x ${HOME}/.local/bin/debcrafter_* + chmod +x ${HOME}/.local/bin/pkg-builder + echo "${HOME}/.local/bin" >> $GITHUB_PATH \ No newline at end of file diff --git a/.github/actions/setup-sbuild/action.yml b/.github/actions/setup-sbuild/action.yml new file mode 100644 index 00000000..95167cb0 --- /dev/null +++ b/.github/actions/setup-sbuild/action.yml @@ -0,0 +1,18 @@ +name: 'Setup sbuild' +description: 'Sets up sbuild environment' + +runs: + using: "composite" + steps: + - name: Sbuild setup + shell: bash + run: | + sudo apt-get update + # Note this is an older version of sbuild, no need to patch it, yet + sudo apt install -y debhelper schroot ubuntu-dev-tools + sudo apt-get -y install pkg-config libssl-dev uidmap + sudo apt-get install -y libfilesys-df-perl libmime-lite-perl + wget https://github.com/eth-pkg/sbuild-ubuntu/releases/download/0.85-6-1/sbuild_0.85.6_all.deb + wget https://github.com/eth-pkg/sbuild-ubuntu/releases/download/0.85-6-1/libsbuild-perl_0.85.6_all.deb + sudo dpkg -i sbuild_0.85.6_all.deb libsbuild-perl_0.85.6_all.deb || true + sudo sbuild-adduser `whoami` \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4ec91afb..16cda9d0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,30 +14,20 @@ env: jobs: unit_tests: runs-on: ubuntu-24.04 - steps: - uses: actions/checkout@v4 - - - name: Sbuild setup - run: | - sudo apt-get update - # Note this is an older version of sbuild, no need to patch it, yet - sudo apt install -y debhelper schroot ubuntu-dev-tools piuparts - sudo apt-get -y install pkg-config libssl-dev uidmap - sudo apt-get install -y libfilesys-df-perl libmime-lite-perl - wget https://github.com/eth-pkg/sbuild-ubuntu/releases/download/0.85-6-1/sbuild_0.85.6_all.deb - wget https://github.com/eth-pkg/sbuild-ubuntu/releases/download/0.85-6-1/libsbuild-perl_0.85.6_all.deb - sudo dpkg -i sbuild_0.85.6_all.deb libsbuild-perl_0.85.6_all.deb || true - sudo sbuild-adduser `whoami` + + - name: Setup sbuild + uses: ./.github/actions/setup-sbuild + - name: Build run: cargo build --verbose - name: Run unit tests - run: cargo test --lib --verbose + run: cargo test --lib --verbose -- --ignored bookworm_amd64_packaging: runs-on: ubuntu-24.04 - strategy: matrix: language: @@ -52,73 +42,30 @@ jobs: - typescript - virtual #- python + max-parallel: 3 fail-fast: false steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Sbuild setup - run: | - sudo apt-get update - # Note this is an older version of sbuild, no need to patch it, yet - sudo apt install -y debhelper schroot ubuntu-dev-tools piuparts autopkgtest vmdb2 qemu-system-x86 - sudo apt-get install -y pkg-config libssl-dev uidmap - sudo apt-get install -y libfilesys-df-perl libmime-lite-perl - # change this into actually built version and cache it - wget https://github.com/eth-pkg/sbuild-ubuntu/releases/download/0.85-6-1/sbuild_0.85.6_all.deb - wget https://github.com/eth-pkg/sbuild-ubuntu/releases/download/0.85-6-1/libsbuild-perl_0.85.6_all.deb - sudo dpkg -i sbuild_0.85.6_all.deb libsbuild-perl_0.85.6_all.deb || true - - - name: Build - run: | - cargo build --verbose - - - name: Install - run: | - cargo build --release - mkdir -p ${HOME}/.local/bin - mv target/release/pkg-builder ${HOME}/.local/bin - # add to path the prebuilt debcrafter binaries as well - cp bin_dependencies/debcrafter_* ${HOME}/.local/bin - chmod +x ${HOME}/.local/bin/pkg-builder - echo "${HOME}/.local/bin" >> $GITHUB_PATH - + - uses: actions/checkout@v4 + + - name: Setup environment + uses: ./.github/actions/setup-packaging-env + - name: Create chroot env run: | cd examples/bookworm/${{matrix.language}}/hello-world pkg-builder env create echo "${HOME}/.cache/sbuild/bookworm-amd64.tar.gz" >> $GITHUB_PATH - - name: package - run: | - cd examples/bookworm/${{matrix.language}}/hello-world - pkg-builder package --run-piuparts false --run-autopkgtest false - - - - name: piuparts - run: | - # installing debian-archive-keyring fails on ubuntu LTS, not sure why, but it says it is already installed - # sudo apt-get install -y debian-archive-keyring - cd examples/bookworm/${{matrix.language}}/hello-world - ${HOME}/.local/bin/pkg-builder piuparts - - - name: autopkgtest - run: | - sudo cp -R ${HOME}/.pkg-builder /root - apt list --installed autopkgtest - cd examples/bookworm/${{matrix.language}}/hello-world - sudo ${HOME}/.local/bin/pkg-builder autopkgtest - - - - name: verify - run: | - cd examples/bookworm/${{matrix.language}}/hello-world - ${HOME}/.local/bin/pkg-builder verify --no-package true - + - name: Run packaging tests + uses: ./.github/actions/run-packaging-tests + with: + distro: bookworm + language: ${{matrix.language}} + run_piuparts: false + run_autopkgtest: true jammy_amd64_packaging: runs-on: ubuntu-24.04 - strategy: matrix: language: @@ -132,76 +79,30 @@ jobs: - rust - typescript - virtual + max-parallel: 3 fail-fast: false steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Sbuild setup - run: | - sudo apt-get update - # Note this is an older version of sbuild, no need to patch it, yet - sudo apt install -y debhelper schroot ubuntu-dev-tools piuparts autopkgtest vmdb2 qemu-system-x86 - sudo apt-get install -y pkg-config libssl-dev uidmap - sudo apt-get install -y libfilesys-df-perl libmime-lite-perl - sudo apt-get install -y genisoimage - # change this into actually built version and cache it - wget https://github.com/eth-pkg/sbuild-ubuntu/releases/download/0.85-6-1/sbuild_0.85.6_all.deb - wget https://github.com/eth-pkg/sbuild-ubuntu/releases/download/0.85-6-1/libsbuild-perl_0.85.6_all.deb - sudo dpkg -i sbuild_0.85.6_all.deb libsbuild-perl_0.85.6_all.deb || true - - - name: Build - run: | - cargo build --verbose - - - name: Install - run: | - cargo build --release - mkdir -p ${HOME}/.local/bin - mv target/release/pkg-builder ${HOME}/.local/bin - # add to path the prebuilt debcrafter binaries as well - cp bin_dependencies/debcrafter_* ${HOME}/.local/bin - chmod +x ${HOME}/.local/bin/pkg-builder - echo "${HOME}/.local/bin" >> $GITHUB_PATH - + - uses: actions/checkout@v4 + + - name: Setup environment + uses: ./.github/actions/setup-packaging-env + - name: Create chroot env run: | cd examples/jammy/${{matrix.language}}/hello-world pkg-builder env create echo "${HOME}/.cache/sbuild/noble-amd64.tar.gz" >> $GITHUB_PATH - - name: package - run: | - cd examples/jammy/${{matrix.language}}/hello-world - pkg-builder package --run-piuparts false --run-autopkgtest false - - - - name: piuparts - run: | - # installing debian-archive-keyring fails on ubuntu LTS, not sure why, but it says it is already installed - # sudo apt-get install -y debian-archive-keyring - cd examples/jammy/${{matrix.language}}/hello-world - ${HOME}/.local/bin/pkg-builder piuparts - - - name: autopkgtest - run: | - sudo cp -R ${HOME}/.pkg-builder /root - apt list --installed autopkgtest - echo $AUTOPKGTEST_KEEP_APT_SOURCES - echo $AUTOPKGTEST_APT_SOURCES_FILE - cd examples/jammy/${{matrix.language}}/hello-world - sudo ${HOME}/.local/bin/pkg-builder autopkgtest - - - - name: verify - run: | - cd examples/jammy/${{matrix.language}}/hello-world - ${HOME}/.local/bin/pkg-builder verify --no-package true - + - name: Run packaging tests + uses: ./.github/actions/run-packaging-tests + with: + distro: jammy + language: ${{matrix.language}} + run_piuparts: false + run_autopkgtest: true noble_amd64_packaging: runs-on: ubuntu-24.04 - strategy: matrix: language: @@ -217,73 +118,34 @@ jobs: - typescript - virtual #- python + max-parallel: 3 fail-fast: false steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Sbuild setup + - uses: actions/checkout@v4 + + - name: Setup environment + uses: ./.github/actions/setup-packaging-env + with: + include_genisoimage: true + + - name: Additional noble setup run: | - sudo apt-get update - # Note this is an older version of sbuild, no need to patch it, yet - sudo apt install -y debhelper schroot ubuntu-dev-tools autopkgtest vmdb2 qemu-system-x86 - sudo apt-get install -y pkg-config libssl-dev uidmap - sudo apt-get install -y libfilesys-df-perl libmime-lite-perl - sudo apt-get install -y genisoimage - # change this into actually built version and cache it - wget https://github.com/eth-pkg/sbuild-ubuntu/releases/download/0.85-6-1/sbuild_0.85.6_all.deb - wget https://github.com/eth-pkg/sbuild-ubuntu/releases/download/0.85-6-1/libsbuild-perl_0.85.6_all.deb - sudo dpkg -i sbuild_0.85.6_all.deb libsbuild-perl_0.85.6_all.deb || true sudo sbuild-adduser `whoami` - #newgrp sbuild - - - name: Build - run: | - cargo build --verbose - - - name: Install - run: | - cargo build --release - mkdir -p ${HOME}/.local/bin - mv target/release/pkg-builder ${HOME}/.local/bin - # add to path the prebuilt debcrafter binaries as well - cp bin_dependencies/debcrafter_* ${HOME}/.local/bin - chmod +x ${HOME}/.local/bin/pkg-builder - echo "${HOME}/.local/bin" >> $GITHUB_PATH - - - name: Create chroot env - run: | - cd examples/noble/${{matrix.language}}/hello-world sudo sysctl -w kernel.unprivileged_userns_clone=1 #sudo ln -s /usr/share/debootstrap/scripts/gutsy /usr/share/debootstrap/scripts/noble - # sudo ${HOME}/.local/bin/pkg-builder env create - # First will fail, try again #sudo cat /etc/subuid - pkg-builder env create - echo "${HOME}/.cache/sbuild/noble-amd64.tar.gz" >> $GITHUB_PATH - - name: package + - name: Create chroot env run: | cd examples/noble/${{matrix.language}}/hello-world - pkg-builder package --run-piuparts false --run-autopkgtest false - - - # - name: piuparts - # run: | - # # installing debian-archive-keyring fails on ubuntu LTS, not sure why, but it says it is already installed - # # sudo apt-get install -y debian-archive-keyring - # cd examples/noble/${{matrix.language}}/hello-world - # ${HOME}/.local/bin/pkg-builder piuparts - - - name: autopkgtest - run: | - sudo cp -R ${HOME}/.pkg-builder /root - apt list --installed autopkgtest - cd examples/noble/${{matrix.language}}/hello-world - sudo ${HOME}/.local/bin/pkg-builder autopkgtest - + pkg-builder env create + echo "${HOME}/.cache/sbuild/noble-amd64.tar.gz" >> $GITHUB_PATH - - name: verify - run: | - cd examples/noble/${{matrix.language}}/hello-world - ${HOME}/.local/bin/pkg-builder verify --no-package true + - name: Run packaging tests + uses: ./.github/actions/run-packaging-tests + with: + distro: noble + language: ${{matrix.language}} + run_piuparts: false + run_autopkgtest: true + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 34bdecd5..2613317c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target .idea -/bin_dependencies \ No newline at end of file +workspace/pkg_builder/bin_dependencies +workspace/**/target/** \ No newline at end of file diff --git a/CONFIG.md b/CONFIG.md new file mode 100644 index 00000000..68c87bfa --- /dev/null +++ b/CONFIG.md @@ -0,0 +1,182 @@ +# Configuration Reference + +pkg-builder uses TOML configuration files to define package metadata, build environments, and dependencies. + +## Basic Structure + +```toml +[package_fields] +spec_file = "path/to/spec.sss" +package_name = "package-name" +version_number = "1.0.0" +revision_number = "1" +homepage = "https://example.com" + +[package_type] +package_type = "default|git|virtual" +# Fields vary based on package_type + +[build_env] +codename = "bookworm" +arch = "amd64" +pkg_builder_version = "0.3.0" +debcrafter_version = "8189263" +# Additional build environment options +``` + +## Package Fields + +```toml +[package_fields] +spec_file = "hello-world.sss" # Path to spec file +package_name = "hello-world" # Package name +version_number = "1.0.0" # Version +revision_number = "1" # Revision number +homepage = "https://github.com/..." # Project homepage +``` + +## Package Types + +### Virtual Package + +```toml +[package_type] +package_type = "virtual" +``` + +### Default Package Type + +```toml +[package_type] +package_type = "default" +tarball_url = "hello-world-1.0.0.tar.gz" +tarball_hash = "c93bdd829eca65af1e303d..." # Optional checksum + +[package_type.language_env] +language_env = "c|rust|go|javascript|typescript|java|dotnet|nim|python" +# Additional fields based on language_env +``` + +### Git Package Type + +```toml +[package_type] +package_type = "git" +git_tag = "v1.0.0" +git_url = "https://github.com/user/repo.git" + +[[package_type.submodules]] +commit = "abcdef123456" +path = "path/to/submodule" + +[package_type.language_env] +language_env = "c|rust|go|javascript|typescript|java|dotnet|nim|python" +# Additional fields based on language_env +``` + +## Language Environments + +### C (Default) + +```toml +[package_type.language_env] +language_env = "c" +``` + +### Python + +```toml +[package_type.language_env] +language_env = "python" +``` + +### Rust + +```toml +[package_type.language_env] +language_env = "rust" +rust_version = "1.67.0" +rust_binary_url = "https://static.rust-lang.org/..." +rust_binary_gpg_asc = "..." +``` + +### Go + +```toml +[package_type.language_env] +language_env = "go" +go_version = "1.19.0" +go_binary_url = "https://go.dev/dl/..." +go_binary_checksum = "..." +``` + +### JavaScript/TypeScript + +```toml +[package_type.language_env] +language_env = "javascript" # or "typescript" +node_version = "18.12.0" +node_binary_url = "https://nodejs.org/dist/..." +node_binary_checksum = "..." +yarn_version = "1.22.19" # Optional +``` + +### Java + +```toml +[package_type.language_env] +language_env = "java" +is_oracle = false +jdk_version = "17.0.5" +jdk_binary_url = "https://..." +jdk_binary_checksum = "..." + +# Optional Gradle configuration +[package_type.language_env.gradle] +gradle_version = "7.6" +gradle_binary_url = "https://..." +gradle_binary_checksum = "..." +``` + +### .NET + +```toml +[package_type.language_env] +language_env = "dotnet" +use_backup_version = false +deps = ["dependency1", "dependency2"] # Optional + +[[package_type.language_env.dotnet_packages]] +name = "package-name" +hash = "checksum" +url = "https://..." +``` + +### Nim + +```toml +[package_type.language_env] +language_env = "nim" +nim_version = "1.6.8" +nim_binary_url = "https://nim-lang.org/..." +nim_version_checksum = "..." +``` + +## Build Environment + +```toml +[build_env] +codename = "bookworm" # Target distribution +arch = "amd64" # Target architecture +pkg_builder_version = "0.3.0" # Tool version +debcrafter_version = "8189263" # Debcrafter version +sbuild_cache_dir = "/path/to/cache" # Optional cache directory, defaults to ~/.cache/sbuild +run_lintian = true # Enable lintian checks +run_piuparts = true # Enable piuparts tests +run_autopkgtest = true # Enable autopkgtest +lintian_version = "2.116.3" # Lintian version +piuparts_version = "1.1.7" # Piuparts version +autopkgtest_version = "5.28" # Autopkgtest version +sbuild_version = "0.85.6" # Sbuild version +workdir = "~/.pkg-builder/..." # Working directory +``` \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index e55eb2bd..66daa228 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,21 +1,21 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aho-corasick" @@ -28,50 +28,52 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", ] [[package]] @@ -116,28 +118,26 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", - "event-listener 5.2.0", - "event-listener-strategy 0.5.0", + "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-executor" -version = "1.8.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ - "async-lock 3.3.0", "async-task", "concurrent-queue", - "fastrand 2.0.2", - "futures-lite 2.3.0", + "fastrand", + "futures-lite", "slab", ] @@ -147,135 +147,108 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ - "async-channel 2.2.0", + "async-channel 2.3.1", "async-executor", - "async-io 2.3.2", - "async-lock 3.3.0", + "async-io", + "async-lock", "blocking", - "futures-lite 2.3.0", + "futures-lite", "once_cell", ] [[package]] name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.27", - "slab", - "socket2 0.4.10", - "waker-fn", -] - -[[package]] -name = "async-io" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ - "async-lock 3.3.0", + "async-lock", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.3.0", + "futures-lite", "parking", - "polling 3.6.0", - "rustix 0.38.32", + "polling", + "rustix 0.38.44", "slab", "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", + "windows-sys 0.59.0", ] [[package]] name = "async-lock" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 4.0.3", - "event-listener-strategy 0.4.0", + "event-listener 5.4.0", + "event-listener-strategy", "pin-project-lite", ] [[package]] name = "async-object-pool" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeb901c30ebc2fc4ab46395bbfbdba9542c16559d853645d75190c3056caf3bc" +checksum = "333c456b97c3f2d50604e8b2624253b7f787208cb72eb75e64b0ad11b221652c" dependencies = [ "async-std", ] [[package]] name = "async-process" -version = "1.8.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" dependencies = [ - "async-io 1.13.0", - "async-lock 2.8.0", + "async-channel 2.3.1", + "async-io", + "async-lock", "async-signal", + "async-task", "blocking", "cfg-if", - "event-listener 3.1.0", - "futures-lite 1.13.0", - "rustix 0.38.32", - "windows-sys 0.48.0", + "event-listener 5.4.0", + "futures-lite", + "rustix 0.38.44", + "tracing", ] [[package]] name = "async-signal" -version = "0.2.5" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io 2.3.2", - "async-lock 2.8.0", + "async-io", + "async-lock", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.32", + "rustix 0.38.44", "signal-hook-registry", "slab", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "async-std" -version = "1.12.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24" dependencies = [ "async-attributes", "async-channel 1.9.0", "async-global-executor", - "async-io 1.13.0", - "async-lock 2.8.0", + "async-io", + "async-lock", "async-process", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite 1.13.0", + "futures-lite", "gloo-timers", "kv-log-macro", "log", @@ -289,19 +262,19 @@ dependencies = [ [[package]] name = "async-task" -version = "4.7.0" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.100", ] [[package]] @@ -312,23 +285,23 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -365,67 +338,58 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.5.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "block-buffer" -version = "0.11.0-pre.5" +version = "0.11.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ded684142010808eb980d9974ef794da2bcf97d13396143b1515e9f0fb4a10e" +checksum = "a229bfd78e4827c91b9b95784f69492c1b77c1ab75a45a8a037b139215086f94" dependencies = [ - "crypto-common", + "hybrid-array", ] [[package]] name = "blocking" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ - "async-channel 2.2.0", - "async-lock 3.3.0", + "async-channel 2.3.1", "async-task", - "fastrand 2.0.2", "futures-io", - "futures-lite 2.3.0", + "futures-lite", "piper", - "tracing", ] [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytes" -version = "1.6.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "camino" -version = "1.1.6" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -444,16 +408,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cc" -version = "1.0.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" -dependencies = [ - "jobserver", - "libc", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -462,9 +416,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.4" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" dependencies = [ "clap_builder", "clap_derive", @@ -472,9 +426,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" dependencies = [ "anstream", "anstyle", @@ -484,80 +438,108 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.100", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "cli" +version = "0.3.0" +dependencies = [ + "cargo_metadata", + "clap", + "env_logger", + "log", + "packager_deb", + "regex", + "serde", + "tempfile", + "thiserror", + "toml", + "types", +] [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "const-oid" -version = "0.10.0-pre.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e3352a27098ba6b09546e5f13b15165e6a88b5c2723afecb3ea9576b27e3ea" +checksum = "1cb3c4a0d3776f7535c32793be81d6d5fec0d48ac70955d9834e643aa249a52f" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" -version = "0.2.0-pre.5" +version = "0.2.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7aa2ec04f5120b830272a481e8d9d8ba4dda140d2cda59b0f1110d5eb93c38e" +checksum = "170d71b5b14dec99db7739f6fc7d6ec2db80b78c3acb77db48392ccc3d8a9ea0" dependencies = [ - "getrandom", "hybrid-array", - "rand_core 0.6.4", +] + +[[package]] +name = "debian" +version = "0.1.0" +dependencies = [ + "log", + "serde", + "shellexpand", + "tempfile", + "thiserror", + "toml", + "types", ] [[package]] name = "digest" -version = "0.11.0-pre.8" +version = "0.11.0-pre.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065d93ead7c220b85d5b4be4795d8398eac4ff68b5ee63895de0a3c1fb6edf25" +checksum = "6c478574b20020306f98d61c8ca3322d762e1ff08117422ac6106438605ea516" dependencies = [ "block-buffer", "const-oid", @@ -606,26 +588,37 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "either" -version = "1.10.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "ena" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" dependencies = [ "log", ] [[package]] name = "env_filter" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", @@ -633,31 +626,31 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.3" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff", "log", ] [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -668,31 +661,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener" -version = "4.0.3" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", @@ -701,59 +672,30 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" -dependencies = [ - "event-listener 4.0.3", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" dependencies = [ - "event-listener 5.2.0", + "event-listener 5.4.0", "pin-project-lite", ] -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fastrand" -version = "2.0.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "filetime" -version = "0.2.23" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", - "redox_syscall", - "windows-sys 0.52.0", + "libredox", + "windows-sys 0.59.0", ] [[package]] @@ -779,47 +721,32 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-lite" -version = "1.13.0" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.3.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" dependencies = [ - "fastrand 2.0.2", + "fastrand", "futures-core", "futures-io", "parking", @@ -828,26 +755,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.100", ] [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-macro", @@ -859,47 +786,38 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] -name = "gimli" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" - -[[package]] -name = "git2" -version = "0.18.3" +name = "getrandom" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" dependencies = [ - "bitflags 2.5.0", + "cfg-if", "libc", - "libgit2-sys", - "log", - "openssl-probe", - "openssl-sys", - "url", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", ] [[package]] -name = "glob" -version = "0.3.1" +name = "gimli" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gloo-timers" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ "futures-channel", "futures-core", @@ -909,9 +827,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -921,9 +839,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "http" @@ -949,9 +867,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -987,26 +905,20 @@ dependencies = [ "url", ] -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hybrid-array" -version = "0.2.0-rc.8" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53668f5da5a41d9eaf4bf7064be46d1ebe6a4e1ceed817f387587b18f2b51047" +checksum = "4dab50e193aebe510fe0e40230145820e02f48dae0cf339ea4204e6e708ff7bd" dependencies = [ "typenum", ] [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", @@ -1018,7 +930,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.6", + "socket2", "tokio", "tower-service", "tracing", @@ -1026,51 +938,160 @@ dependencies = [ ] [[package]] -name = "idna" -version = "0.5.0" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "indenter" -version = "0.3.3" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] [[package]] -name = "indexmap" -version = "2.2.6" +name = "icu_locid_transform" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ - "equivalent", - "hashbrown", + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "instant" -version = "0.1.12" +name = "icu_locid_transform_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ - "cfg-if", + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", ] [[package]] -name = "io-lifetimes" -version = "1.0.11" +name = "icu_normalizer_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", ] +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.11.0" @@ -1082,25 +1103,41 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] -name = "jobserver" -version = "0.1.28" +name = "jiff" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" dependencies = [ - "libc", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -1146,9 +1183,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "levenshtein" @@ -1158,78 +1195,44 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" - -[[package]] -name = "libgit2-sys" -version = "0.16.2+1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" -dependencies = [ - "cc", - "libc", - "libssh2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", -] +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.5.0", + "bitflags", "libc", "redox_syscall", ] [[package]] -name = "libssh2-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libz-sys" -version = "1.1.16" +name = "linux-raw-sys" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" [[package]] -name = "linux-raw-sys" -version = "0.4.13" +name = "litemap" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1237,37 +1240,37 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" dependencies = [ "value-bag", ] [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "0.8.11" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", - "windows-sys 0.48.0", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", ] [[package]] @@ -1276,66 +1279,60 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" -version = "0.32.2" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" [[package]] -name = "openssl-probe" -version = "0.1.5" +name = "option-ext" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] -name = "openssl-sys" -version = "0.9.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +name = "packager_deb" +version = "0.1.0" dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", + "cargo_metadata", + "debian", + "dirs", + "env_logger", + "filetime", + "httpmock", + "log", + "rand", + "serde", + "sha1", + "sha2", + "shellexpand", + "tempfile", + "thiserror", + "toml", + "types", ] -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1343,15 +1340,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -1362,9 +1359,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", "indexmap", @@ -1372,9 +1369,9 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] @@ -1387,9 +1384,9 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1399,84 +1396,63 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.0.2", + "fastrand", "futures-io", ] [[package]] name = "pkg-builder" -version = "0.2.11" +version = "0.3.0" dependencies = [ - "cargo_metadata", - "clap", - "dirs", - "env_logger", - "eyre", - "filetime", - "git2", - "glob", - "httpmock", - "log", - "rand", - "regex", + "cli", "serde", - "sha1", - "sha2", - "shellexpand", - "tempfile", - "test-case", "thiserror", "toml", - "whoami", ] -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - [[package]] name = "polling" -version = "2.8.0" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ - "autocfg", - "bitflags 1.3.2", "cfg-if", "concurrent-queue", - "libc", - "log", + "hermit-abi", "pin-project-lite", - "windows-sys 0.48.0", + "rustix 0.38.44", + "tracing", + "windows-sys 0.59.0", ] [[package]] -name = "polling" -version = "3.6.0" +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix 0.38.32", - "tracing", - "windows-sys 0.52.0", + "portable-atomic", ] [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] [[package]] name = "precomputed-hash" @@ -1486,87 +1462,77 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" dependencies = [ "proc-macro2", ] [[package]] name = "rand" -version = "0.9.0-alpha.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31e63ea85be51c423e52ba8f2e68a3efd53eed30203ee029dd09947333693e" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha", - "rand_core 0.9.0-alpha.1", + "rand_core", "zerocopy", ] [[package]] name = "rand_chacha" -version = "0.9.0-alpha.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78674ef918c19451dbd250f8201f8619b494f64c9aa6f3adb28fd8a0f1f6da46" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.0-alpha.1", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", + "rand_core", ] [[package]] name = "rand_core" -version = "0.9.0-alpha.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc89dffba8377c5ec847d12bb41492bda235dba31a25e8b695cd0fe6589eb8c9" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom", - "zerocopy", + "getrandom 0.3.1", ] [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom", + "getrandom 0.2.15", "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.10.4" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1576,9 +1542,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1587,54 +1553,53 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.37.27" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 1.3.2", + "bitflags", "errno", - "io-lifetimes", "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", ] [[package]] name = "rustix" -version = "0.38.32" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" dependencies = [ - "bitflags 2.5.0", + "bitflags", "errno", "libc", - "linux-raw-sys 0.4.13", - "windows-sys 0.52.0", + "linux-raw-sys 0.9.2", + "windows-sys 0.59.0", ] [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -1653,40 +1618,41 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.22" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.209" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.100", ] [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1703,18 +1669,18 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] [[package]] name = "sha1" -version = "0.11.0-pre.3" +version = "0.11.0-pre.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3885de8cb916f223718c1ccd47a840b91f806333e76002dc5cb3862154b4fed3" +checksum = "55f44e40722caefdd99383c25d3ae52a1094a1951215ae76f68837ece4e7f566" dependencies = [ "cfg-if", "cpufeatures", @@ -1723,9 +1689,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.11.0-pre.3" +version = "0.11.0-pre.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f33549bf3064b62478926aa89cbfc7c109aab66ae8f0d5d2ef839e482cc30d6" +checksum = "19b4241d1a56954dce82cecda5c8e9c794eef6f53abe5e5216bac0a0ea71ffa7" dependencies = [ "cfg-if", "cpufeatures", @@ -1743,24 +1709,24 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "similar" -version = "2.4.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "siphasher" -version = "0.3.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" @@ -1773,38 +1739,33 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" -version = "0.4.10" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", - "winapi", + "windows-sys 0.52.0", ] [[package]] -name = "socket2" -version = "0.5.6" +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "string_cache" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +checksum = "938d512196766101d333398efde81bc1f37b00cb42c2f8350e5df639f040bbbe" dependencies = [ "new_debug_unreachable", - "once_cell", "parking_lot", "phf_shared", "precomputed-hash", @@ -1829,25 +1790,38 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.55" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "tempfile" -version = "3.10.1" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" dependencies = [ "cfg-if", - "fastrand 2.0.2", - "rustix 0.38.32", - "windows-sys 0.52.0", + "fastrand", + "getrandom 0.3.1", + "once_cell", + "rustix 1.0.2", + "windows-sys 0.59.0", ] [[package]] @@ -1861,57 +1835,24 @@ dependencies = [ "winapi", ] -[[package]] -name = "test-case" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" -dependencies = [ - "test-case-macros", -] - -[[package]] -name = "test-case-core" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "test-case-macros" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", - "test-case-core", -] - [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.100", ] [[package]] @@ -1924,53 +1865,47 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" -version = "1.36.0" +version = "1.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" dependencies = [ "backtrace", "libc", "mio", - "num_cpus", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.6", + "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.100", ] [[package]] name = "toml" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", @@ -1989,9 +1924,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "serde", @@ -2002,15 +1937,15 @@ dependencies = [ [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-core", @@ -2018,9 +1953,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", ] @@ -2033,42 +1968,40 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +name = "types" +version = "0.1.0" +dependencies = [ + "log", + "semver", + "serde", + "tempfile", + "thiserror", + "toml", + "url", +] [[package]] name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.23" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "url" -version = "2.5.0" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -2076,28 +2009,28 @@ dependencies = [ ] [[package]] -name = "utf8parse" -version = "0.2.1" +name = "utf16_iter" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[package]] -name = "value-bag" -version = "1.8.1" +name = "utf8_iter" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74797339c3b98616c009c7c3eb53a0ce41e85c8ec66bd3db96ed132d20cfdee8" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] -name = "vcpkg" -version = "0.2.15" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "waker-fn" -version = "1.1.1" +name = "value-bag" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" [[package]] name = "walkdir" @@ -2125,53 +2058,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasite" -version = "0.1.0" +name = "wasi" +version = "0.13.3+wasi-0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.100", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2179,44 +2117,36 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "whoami" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" -dependencies = [ - "redox_syscall", - "wasite", - "web-sys", -] - [[package]] name = "winapi" version = "0.3.9" @@ -2235,11 +2165,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -2263,7 +2193,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -2283,17 +2222,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2304,9 +2244,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2316,9 +2256,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2328,9 +2268,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2340,9 +2286,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2352,9 +2298,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2364,9 +2310,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2376,35 +2322,123 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.18" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "synstructure", +] + [[package]] name = "zerocopy" -version = "0.8.0-alpha.6" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db678a6ee512bd06adf35c35be471cae2f9c82a5aed2b5d15e03628c98bddd57" +checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.0-alpha.6" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201585ea96d37ee69f2ac769925ca57160cef31acb137c16f38b02b76f4c1e62" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.100", ] diff --git a/Cargo.toml b/Cargo.toml index 70dca258..fca3066d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,39 +1,44 @@ -[package] -name = "pkg-builder" -version = "0.2.11" -edition = "2021" -build = "src/build.rs" -[build-dependencies] -eyre = "0.6.12" -thiserror = "1.0.63" -serde = { version = "1.0.209", features = ["derive"] } -toml = "0.8.19" +[workspace] +members = [ + "workspace/packager_deb", + "workspace/cli", + "workspace/types", + "workspace/debian", + "workspace/pkg_builder", +] +resolver = "2" + +[workspace.package] +edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[dependencies] +[workspace.dependencies] clap = { version = "4.5.4", features = ["derive"] } toml = "0.8.12" serde = { version = "1.0", features = ["derive"] } +env_logger = "0.11.3" +log = "0.4" +cargo_metadata = "0.18" +regex = "1.10.4" thiserror = "1.0" +shellexpand = "3.1.0" tempfile = "3.1" -log = "0.4" -env_logger = "0.11.3" git2 = "0.18.3" -cargo_metadata = "0.18" test-case = "3.3.1" glob = "0.3.1" dirs = "5.0.1" rand = "0.9.0-alpha.1" -shellexpand = "3.1.0" -eyre = "0.6.12" sha2 = "0.11.0-pre.3" whoami = "1.5.1" sha1 = "0.11.0-pre.3" filetime = "0.2.23" -regex = "1.10.4" - -[dev-dependencies] -env_logger = "*" httpmock = "0.7.0" +semver = "1.0.20" # Replace with the version you need +url = "2.4.1" # Use the latest version + +# Local crates +cli = {path = "workspace/cli"} +debian = {path = "workspace/debian"} +packager_deb = {path = "workspace/packager_deb"} +types = {path = "workspace/types"} diff --git a/EXAMPLES.md b/EXAMPLES.md new file mode 100644 index 00000000..49ad6801 --- /dev/null +++ b/EXAMPLES.md @@ -0,0 +1,72 @@ +# Examples + +This document contains example usage for different package types. All examples assume you have already built and installed pkg-builder: + +```bash +cargo build && cargo install --path . +``` + +## Virtual Package + +```bash +pkg-builder env create examples/bookworm/virtual-package/pkg-builder.toml +pkg-builder package examples/bookworm/virtual-package/pkg-builder.toml +``` + +## Rust Package + +```bash +pkg-builder env create examples/bookworm/rust/hello-world/pkg-builder.toml +pkg-builder package examples/bookworm/rust/hello-world/pkg-builder.toml +``` + +## TypeScript Package + +```bash +pkg-builder env create examples/bookworm/typescript/hello-world/pkg-builder.toml +pkg-builder package examples/bookworm/typescript/hello-world/pkg-builder.toml +``` + +## JavaScript Package + +```bash +pkg-builder env create examples/bookworm/javascript/hello-world/pkg-builder.toml +pkg-builder package examples/bookworm/javascript/hello-world/pkg-builder.toml +``` + +## Nim Package + +```bash +pkg-builder env create examples/bookworm/nim/hello-world/pkg-builder.toml +pkg-builder package examples/bookworm/nim/hello-world/pkg-builder.toml +``` + +## .NET Package + +```bash +pkg-builder env create examples/bookworm/dotnet/hello-world/pkg-builder.toml +pkg-builder package examples/bookworm/dotnet/hello-world/pkg-builder.toml +``` + +## Java Package + +```bash +pkg-builder env create examples/bookworm/java/hello-world/pkg-builder.toml +pkg-builder package examples/bookworm/java/hello-world/pkg-builder.toml +``` + +## Testing Examples + +After building a package, you can run specific tests: + +### Piuparts Only + +```bash +pkg-builder piuparts examples/bookworm/virtual-package/pkg-builder.toml +``` + +### Autopkgtest Only + +```bash +pkg-builder autopkgtests examples/bookworm/virtual-package/pkg-builder.toml +``` \ No newline at end of file diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 00000000..5032d24f --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,42 @@ +# Installation Guide + +## Debian Prerequisites + +```bash +sudo apt install libssl-dev pkg-config quilt debhelper tar wget autopkgtest vmdb2 qemu-system-x86 git-lfs uidmap +sudo sbuild-adduser `whoami` +``` + +## Installing sbuild + +```bash +# Clone repository +git clone https://github.com/eth-pkg/sbuild.git +cd sbuild + +# Install dependencies +sudo apt-get install -y dh-python dh-sequence-python3 libyaml-tiny-perl python3-all genisoimage + +# Build the package +dpkg-buildpackage -us -uc + +# Install the newly built package +cd .. && sudo dpkg -i sbuild_0.85.6_all.deb libsbuild-perl_0.85.6_all.deb +``` + +## Setting up chroot + +```bash +# Create chroot directory if it doesn't exist +sudo mkdir /srv/chroot +sudo chown :sbuild /srv/chroot + +# For noble builds +sudo ln -s /usr/share/debootstrap/scripts/gutsy /usr/share/debootstrap/scripts/noble +``` + +## Ubuntu-specific Setup + +If building for Ubuntu on Bookworm, manually download the ubuntu-archive-keyring: +1. Get the keyring from [ubuntu-archive-keyring](https://salsa.debian.org/debian/ubuntu-keyring/-/raw/master/keyrings/ubuntu-archive-keyring.gpg?ref_type=heads) +2. Copy it into `/usr/share/keyrings` \ No newline at end of file diff --git a/Readme.md b/Readme.md index de10ee39..757de803 100644 --- a/Readme.md +++ b/Readme.md @@ -1,161 +1,58 @@ # pkg-builder -pkg-builder simplifies the process of creating packages for Linux distributions. It automates the packaging process based on a configuration file, leveraging debcrafter, a framework for building Debian packages. By specifying package metadata, dependencies, and other configuration details in a structured format, developers can easily generate packages ready for Linux distributions. Pkg-builder abstracts away much of the complexity involved in packaging, allowing developers to focus on their software rather than packaging intricacies. +[![Tests](https://github.com/eth-pkg/pkg-builder/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/eth-pkg/pkg-builder/actions/workflows/tests.yml) -## Table of Contents +A tool that simplifies building packages for Linux distributions by automating the packaging process using a configuration file approach. -1. [Prerequisites](#prerequisites) -2. [Getting Started](#getting-started) - - [Example Virtual Package](#example-virtual-package) - - [Example Rust Package](#example-rust-package) - - [Example TypeScript Package](#example-typescript-package) - - [Example JavaScript Package](#example-javascript-package) - - [Example Nim Package](#example-nim-package) - - [Example .NET Package](#example-net-package) - - [Example Java Package](#example-java-package) -3. [Piuparts Only](#piuparts-only) -4. [Autopkgtest Only](#autopkgtest-only) +## Overview -## Prerequisites +pkg-builder leverages debcrafter to generate Debian packages from a structured configuration file. This abstraction allows developers to focus on their software rather than packaging complexities. -If you are using Debian, install sbuild, and various dependencies: +## Quick Start +### Prerequisites + +For Debian systems: ```bash sudo apt install libssl-dev pkg-config quilt debhelper tar wget autopkgtest vmdb2 qemu-system-x86 git-lfs uidmap sudo sbuild-adduser `whoami` - -# Install sbuild -git clone https://github.com/eth-pkg/sbuild.git -cd sbuild -# Install dependencies -sudo apt-get install -y dh-python dh-sequence-python3 libyaml-tiny-perl python3-all -sudo apt-get install -y genisoimage -# Build the package -dpkg-buildpackage -us -uc -# Install the newly built package -cd .. && sudo dpkg -i sbuild_0.85.6_all.deb libsbuild-perl_0.85.6_all.deb - -# if chroot not exists create it -sudo mkdir /srv/chroot -sudo chown :sbuild /srv/chroot - -# for noble builds -sudo ln -s /usr/share/debootstrap/scripts/gutsy /usr/share/debootstrap/scripts/noble -``` - -If you are building for Ubuntu on Bookworm, you need to manually download the ubuntu-archive-keyring: -[ubuntu-archive-keyring](https://salsa.debian.org/debian/ubuntu-keyring/-/raw/master/keyrings/ubuntu-archive-keyring.gpg?ref_type=heads) -and copy it into `/usr/share/keyrings`. - -## Getting Started - -### Example Virtual Package -
-Click to expand - -```bash -cargo build && cargo install --path . -pkg-builder env create examples/bookworm/virtual-package/pkg-builder.toml -pkg-builder package examples/bookworm/virtual-package/pkg-builder.toml -``` - -This will build the package using the provided configuration file. -
- -### Example Rust Package -
-Click to expand - -```bash -cargo build && cargo install --path . -pkg-builder env create examples/bookworm/rust/hello-world/pkg-builder.toml -pkg-builder package examples/bookworm/rust/hello-world/pkg-builder.toml -``` -
- -### Example TypeScript Package -
-Click to expand - -```bash -cargo build && cargo install --path . -pkg-builder env create examples/bookworm/typescript/hello-world/pkg-builder.toml -pkg-builder package examples/bookworm/typescript/hello-world/pkg-builder.toml -``` -
- -### Example JavaScript Package -
-Click to expand - -```bash -cargo build && cargo install --path . -pkg-builder env create examples/bookworm/javascript/hello-world/pkg-builder.toml -pkg-builder package examples/bookworm/javascript/hello-world/pkg-builder.toml ``` -
-### Example Nim Package -
-Click to expand +For sbuild installation and setup, see [installation docs](INSTALL.md). -```bash -cargo build && cargo install --path . -pkg-builder env create examples/bookworm/nim/hello-world/pkg-builder.toml -pkg-builder package examples/bookworm/nim/hello-world/pkg-builder.toml -``` -
- -### Example .NET Package -
-Click to expand +### Basic Usage ```bash -cargo build && cargo install --path . -pkg-builder env create examples/bookworm/dotnet/hello-world/pkg-builder.toml -pkg-builder package examples/bookworm/dotnet/hello-world/pkg-builder.toml -``` -
+# Install pkg-builder +cargo install --path . -### Example Java Package -
-Click to expand - -```bash -cargo build && cargo install --path . -pkg-builder env create examples/bookworm/java/hello-world/pkg-builder.toml -pkg-builder package examples/bookworm/java/hello-world/pkg-builder.toml +# Create environment and build package +pkg-builder env create path/to/pkg-builder.toml +pkg-builder package path/to/pkg-builder.toml ``` -
-## Piuparts Only - -Assuming that you already packaged your source before as such: +### Testing ```bash -cargo build && cargo install --path . -pkg-builder env create examples/bookworm/virtual-package/pkg-builder.toml -pkg-builder package examples/bookworm/virtual-package/pkg-builder.toml -``` - -you can run only piuparts: +# Run piuparts tests only +pkg-builder piuparts path/to/pkg-builder.toml -```bash -pkg-builder piuparts examples/bookworm/virtual-package/pkg-builder.toml +# Run autopkgtests only +pkg-builder autopkgtests path/to/pkg-builder.toml ``` -## Autopkgtest Only +## Examples -Assuming that you already packaged your source before as such: +See [examples documentation](EXAMPLES.md) for sample configurations for various languages: +- Virtual Packages +- Rust +- TypeScript/JavaScript +- Nim +- .NET +- Java -```bash -cargo build && cargo install --path . -pkg-builder env create examples/bookworm/virtual-package/pkg-builder.toml -pkg-builder package examples/bookworm/virtual-package/pkg-builder.toml -``` - -you can run only autopkgtests: +## Documentation -```bash -pkg-builder autopkgtests examples/bookworm/virtual-package/pkg-builder.toml -``` +- [Installation Guide](INSTALL.md) +- [Configuration Reference](CONFIG.md) +- [Examples](EXAMPLES.md) \ No newline at end of file diff --git a/bin_dependencies/.crates.toml b/bin_dependencies/.crates.toml new file mode 100644 index 00000000..4fb4c9fa --- /dev/null +++ b/bin_dependencies/.crates.toml @@ -0,0 +1,2 @@ +[v1] +"debcrafter 0.2.0 (git+https://github.com/Kixunil/debcrafter?rev=8189263#8189263400e69b747dcbf45de0a702993566fe47)" = ["debcrafter"] diff --git a/bin_dependencies/.crates2.json b/bin_dependencies/.crates2.json new file mode 100644 index 00000000..fa608923 --- /dev/null +++ b/bin_dependencies/.crates2.json @@ -0,0 +1 @@ +{"installs":{"debcrafter 0.2.0 (git+https://github.com/Kixunil/debcrafter?rev=8189263#8189263400e69b747dcbf45de0a702993566fe47)":{"version_req":null,"bins":["debcrafter"],"features":[],"all_features":false,"no_default_features":false,"profile":"release","target":"x86_64-unknown-linux-gnu","rustc":"rustc 1.85.0 (4d91de4e4 2025-02-17)\nbinary: rustc\ncommit-hash: 4d91de4e48198da2e33413efdcd9cd2cc0c46688\ncommit-date: 2025-02-17\nhost: x86_64-unknown-linux-gnu\nrelease: 1.85.0\nLLVM version: 19.1.7\n"}}} \ No newline at end of file diff --git a/bin_dependencies/debcrafter_2711b53 b/bin_dependencies/debcrafter_2711b53 new file mode 100755 index 00000000..aa767271 Binary files /dev/null and b/bin_dependencies/debcrafter_2711b53 differ diff --git a/bin_dependencies/debcrafter_8189263 b/bin_dependencies/debcrafter_8189263 new file mode 100755 index 00000000..2412e6b4 Binary files /dev/null and b/bin_dependencies/debcrafter_8189263 differ diff --git a/examples/bookworm/c/hello-world/pkg-builder.toml b/examples/bookworm/c/hello-world/pkg-builder.toml index d12e6423..654442ba 100644 --- a/examples/bookworm/c/hello-world/pkg-builder.toml +++ b/examples/bookworm/c/hello-world/pkg-builder.toml @@ -17,7 +17,7 @@ language_env = "c" [build_env] codename="bookworm" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/bookworm/dotnet/hello-world/pkg-builder.toml b/examples/bookworm/dotnet/hello-world/pkg-builder.toml index 674ebef7..1973eae1 100644 --- a/examples/bookworm/dotnet/hello-world/pkg-builder.toml +++ b/examples/bookworm/dotnet/hello-world/pkg-builder.toml @@ -32,7 +32,7 @@ use_backup_version = true [build_env] codename = "bookworm" arch = "amd64" -pkg_builder_version = "0.2.11" +pkg_builder_version = "0.3.0" debcrafter_version = "8189263" run_lintian = true run_piuparts = true @@ -42,13 +42,4 @@ piuparts_version = "1.1.7" autopkgtest_version = "5.28" sbuild_version = "0.85.6" # package directory -workdir = "~/.pkg-builder/packages/bookworm" - - -[verify] -# if tarball_url is specified -tarball_hash = "" -# if git_source is specified and package_is_git -git_commit = "" -# output hash -bin_bash = "" +workdir = "~/.pkg-builder/packages/bookworm" \ No newline at end of file diff --git a/examples/bookworm/git-package/nimbus/pkg-builder.toml b/examples/bookworm/git-package/nimbus/pkg-builder.toml index 5ade300e..b90e4866 100644 --- a/examples/bookworm/git-package/nimbus/pkg-builder.toml +++ b/examples/bookworm/git-package/nimbus/pkg-builder.toml @@ -70,7 +70,7 @@ nim_version_checksum = "047dde8ff40b18628ac1188baa9ca992d05f1f45c5121d1d07a76224 [build_env] codename="bookworm" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "latest" run_lintian=false run_piuparts=false diff --git a/examples/bookworm/go/hello-world/pkg-builder.toml b/examples/bookworm/go/hello-world/pkg-builder.toml index 421ee859..9f932002 100644 --- a/examples/bookworm/go/hello-world/pkg-builder.toml +++ b/examples/bookworm/go/hello-world/pkg-builder.toml @@ -20,7 +20,7 @@ go_binary_checksum = "5901c52b7a78002aeff14a21f93e0f064f74ce1360fce51c6ee68cd471 [build_env] codename="bookworm" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/bookworm/java-gradle/hello-world/pkg-builder.toml b/examples/bookworm/java-gradle/hello-world/pkg-builder.toml index 385acf9a..7f3f5038 100644 --- a/examples/bookworm/java-gradle/hello-world/pkg-builder.toml +++ b/examples/bookworm/java-gradle/hello-world/pkg-builder.toml @@ -26,7 +26,7 @@ gradle_binary_checksum="544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2 [build_env] codename = "bookworm" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/bookworm/java/hello-world/pkg-builder.toml b/examples/bookworm/java/hello-world/pkg-builder.toml index 2e442ed9..d3088a9a 100644 --- a/examples/bookworm/java/hello-world/pkg-builder.toml +++ b/examples/bookworm/java/hello-world/pkg-builder.toml @@ -21,7 +21,7 @@ jdk_binary_checksum="e4fb2df9a32a876afb0a6e17f54c594c2780e18badfa2e8fc99bc2656b0 [build_env] codename = "bookworm" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/bookworm/javascript/hello-world/pkg-builder.toml b/examples/bookworm/javascript/hello-world/pkg-builder.toml index f516f04c..2e639cd1 100644 --- a/examples/bookworm/javascript/hello-world/pkg-builder.toml +++ b/examples/bookworm/javascript/hello-world/pkg-builder.toml @@ -21,7 +21,7 @@ yarn_version = "1.22.19" [build_env] codename="bookworm" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/bookworm/nim/hello-world/pkg-builder.toml b/examples/bookworm/nim/hello-world/pkg-builder.toml index 44682736..6116beb7 100644 --- a/examples/bookworm/nim/hello-world/pkg-builder.toml +++ b/examples/bookworm/nim/hello-world/pkg-builder.toml @@ -20,7 +20,7 @@ nim_version_checksum = "047dde8ff40b18628ac1188baa9ca992d05f1f45c5121d1d07a76224 [build_env] codename="bookworm" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/bookworm/python/hello-world/pkg-builder.toml b/examples/bookworm/python/hello-world/pkg-builder.toml index d2a9fd58..add185b2 100644 --- a/examples/bookworm/python/hello-world/pkg-builder.toml +++ b/examples/bookworm/python/hello-world/pkg-builder.toml @@ -17,7 +17,7 @@ language_env = "python" [build_env] codename="bookworm" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/bookworm/rust/hello-world/pkg-builder.toml b/examples/bookworm/rust/hello-world/pkg-builder.toml index 2eefbb3a..746343dc 100644 --- a/examples/bookworm/rust/hello-world/pkg-builder.toml +++ b/examples/bookworm/rust/hello-world/pkg-builder.toml @@ -37,7 +37,7 @@ KGFMBQELjcFWLGcBVE45DRuVR8E3XYunjSdgLFXjfZfeGF3uiS6fNHGCH41ryqfj [build_env] codename="bookworm" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/bookworm/typescript/hello-world/pkg-builder.toml b/examples/bookworm/typescript/hello-world/pkg-builder.toml index 14df8c73..d4bfedb0 100644 --- a/examples/bookworm/typescript/hello-world/pkg-builder.toml +++ b/examples/bookworm/typescript/hello-world/pkg-builder.toml @@ -21,7 +21,7 @@ yarn_version = "1.22.19" [build_env] codename="bookworm" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/bookworm/virtual/hello-world/pkg-builder.toml b/examples/bookworm/virtual/hello-world/pkg-builder.toml index f1339abd..c3c291bb 100644 --- a/examples/bookworm/virtual/hello-world/pkg-builder.toml +++ b/examples/bookworm/virtual/hello-world/pkg-builder.toml @@ -11,7 +11,7 @@ package_type="virtual" [build_env] codename="bookworm" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/jammy/c/hello-world/pkg-builder.toml b/examples/jammy/c/hello-world/pkg-builder.toml index ed728372..7bda0d78 100644 --- a/examples/jammy/c/hello-world/pkg-builder.toml +++ b/examples/jammy/c/hello-world/pkg-builder.toml @@ -17,7 +17,7 @@ language_env = "c" [build_env] codename="jammy jellyfish" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/jammy/dotnet/hello-world/pkg-builder.toml b/examples/jammy/dotnet/hello-world/pkg-builder.toml index ebcb1044..566d5708 100644 --- a/examples/jammy/dotnet/hello-world/pkg-builder.toml +++ b/examples/jammy/dotnet/hello-world/pkg-builder.toml @@ -31,7 +31,7 @@ use_backup_version = true [build_env] codename = "jammy jellyfish" arch = "amd64" -pkg_builder_version = "0.2.11" +pkg_builder_version = "0.3.0" debcrafter_version = "8189263" run_lintian = true run_piuparts = true diff --git a/examples/jammy/git-package/nimbus/pkg-builder.toml b/examples/jammy/git-package/nimbus/pkg-builder.toml index bf5d9d50..12a095b8 100644 --- a/examples/jammy/git-package/nimbus/pkg-builder.toml +++ b/examples/jammy/git-package/nimbus/pkg-builder.toml @@ -70,7 +70,7 @@ nim_version_checksum = "047dde8ff40b18628ac1188baa9ca992d05f1f45c5121d1d07a76224 [build_env] codename="bookworm" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "latest" run_lintian=false run_piuparts=false diff --git a/examples/jammy/go/hello-world/pkg-builder.toml b/examples/jammy/go/hello-world/pkg-builder.toml index d85a0436..70e10978 100644 --- a/examples/jammy/go/hello-world/pkg-builder.toml +++ b/examples/jammy/go/hello-world/pkg-builder.toml @@ -20,7 +20,7 @@ go_binary_checksum = "5901c52b7a78002aeff14a21f93e0f064f74ce1360fce51c6ee68cd471 [build_env] codename="jammy jellyfish" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/jammy/java-gradle/hello-world/pkg-builder.toml b/examples/jammy/java-gradle/hello-world/pkg-builder.toml index acff30a9..6d75aebb 100644 --- a/examples/jammy/java-gradle/hello-world/pkg-builder.toml +++ b/examples/jammy/java-gradle/hello-world/pkg-builder.toml @@ -26,7 +26,7 @@ gradle_binary_checksum="544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2 [build_env] codename="jammy jellyfish" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/jammy/java/hello-world/pkg-builder.toml b/examples/jammy/java/hello-world/pkg-builder.toml index 96db5435..582c2eeb 100644 --- a/examples/jammy/java/hello-world/pkg-builder.toml +++ b/examples/jammy/java/hello-world/pkg-builder.toml @@ -21,7 +21,7 @@ jdk_binary_checksum="e4fb2df9a32a876afb0a6e17f54c594c2780e18badfa2e8fc99bc2656b0 [build_env] codename="jammy jellyfish" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/jammy/javascript/hello-world/pkg-builder.toml b/examples/jammy/javascript/hello-world/pkg-builder.toml index 2fc2f2e3..412615eb 100644 --- a/examples/jammy/javascript/hello-world/pkg-builder.toml +++ b/examples/jammy/javascript/hello-world/pkg-builder.toml @@ -21,7 +21,7 @@ yarn_version = "1.22.19" [build_env] codename="jammy jellyfish" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/jammy/nim/hello-world/pkg-builder.toml b/examples/jammy/nim/hello-world/pkg-builder.toml index fc81044a..c9e651ba 100644 --- a/examples/jammy/nim/hello-world/pkg-builder.toml +++ b/examples/jammy/nim/hello-world/pkg-builder.toml @@ -20,7 +20,7 @@ nim_version_checksum = "047dde8ff40b18628ac1188baa9ca992d05f1f45c5121d1d07a76224 [build_env] codename="jammy jellyfish" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/jammy/rust/hello-world/pkg-builder.toml b/examples/jammy/rust/hello-world/pkg-builder.toml index 9051ec85..4b3261bf 100644 --- a/examples/jammy/rust/hello-world/pkg-builder.toml +++ b/examples/jammy/rust/hello-world/pkg-builder.toml @@ -37,7 +37,7 @@ KGFMBQELjcFWLGcBVE45DRuVR8E3XYunjSdgLFXjfZfeGF3uiS6fNHGCH41ryqfj [build_env] codename="jammy jellyfish" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/jammy/typescript/hello-world/pkg-builder.toml b/examples/jammy/typescript/hello-world/pkg-builder.toml index 67529649..9390a81b 100644 --- a/examples/jammy/typescript/hello-world/pkg-builder.toml +++ b/examples/jammy/typescript/hello-world/pkg-builder.toml @@ -21,7 +21,7 @@ yarn_version = "1.22.19" [build_env] codename="jammy jellyfish" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/jammy/virtual/hello-world/pkg-builder.toml b/examples/jammy/virtual/hello-world/pkg-builder.toml index 4b97424c..4e1e4158 100644 --- a/examples/jammy/virtual/hello-world/pkg-builder.toml +++ b/examples/jammy/virtual/hello-world/pkg-builder.toml @@ -11,7 +11,7 @@ package_type="virtual" [build_env] codename="jammy jellyfish" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/noble/c/hello-world/pkg-builder.toml b/examples/noble/c/hello-world/pkg-builder.toml index 460190d8..e35d13c6 100644 --- a/examples/noble/c/hello-world/pkg-builder.toml +++ b/examples/noble/c/hello-world/pkg-builder.toml @@ -17,7 +17,7 @@ language_env = "c" [build_env] codename="noble numbat" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/noble/dotnet-9/hello-world/pkg-builder.toml b/examples/noble/dotnet-9/hello-world/pkg-builder.toml index ed6b12e6..f9f34c78 100644 --- a/examples/noble/dotnet-9/hello-world/pkg-builder.toml +++ b/examples/noble/dotnet-9/hello-world/pkg-builder.toml @@ -32,7 +32,7 @@ use_backup_version = true [build_env] codename = "noble numbat" arch = "amd64" -pkg_builder_version = "0.2.11" +pkg_builder_version = "0.3.0" debcrafter_version = "8189263" run_lintian = true run_piuparts = true diff --git a/examples/noble/dotnet/hello-world/pkg-builder.toml b/examples/noble/dotnet/hello-world/pkg-builder.toml index 840fce83..cf7350b1 100644 --- a/examples/noble/dotnet/hello-world/pkg-builder.toml +++ b/examples/noble/dotnet/hello-world/pkg-builder.toml @@ -36,7 +36,7 @@ use_backup_version = true [build_env] codename = "noble numbat" arch = "amd64" -pkg_builder_version = "0.2.11" +pkg_builder_version = "0.3.0" debcrafter_version = "8189263" run_lintian = true run_piuparts = true diff --git a/examples/noble/git-package/nimbus/pkg-builder.toml b/examples/noble/git-package/nimbus/pkg-builder.toml index fda6fc99..22850b54 100644 --- a/examples/noble/git-package/nimbus/pkg-builder.toml +++ b/examples/noble/git-package/nimbus/pkg-builder.toml @@ -70,7 +70,7 @@ nim_version_checksum = "047dde8ff40b18628ac1188baa9ca992d05f1f45c5121d1d07a76224 [build_env] codename="bookworm" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "latest" run_lintian=false run_piuparts=false diff --git a/examples/noble/go/hello-world/pkg-builder.toml b/examples/noble/go/hello-world/pkg-builder.toml index f42de706..9379fb0d 100644 --- a/examples/noble/go/hello-world/pkg-builder.toml +++ b/examples/noble/go/hello-world/pkg-builder.toml @@ -20,7 +20,7 @@ go_binary_checksum = "5901c52b7a78002aeff14a21f93e0f064f74ce1360fce51c6ee68cd471 [build_env] codename="noble numbat" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/noble/java-gradle/hello-world/pkg-builder.toml b/examples/noble/java-gradle/hello-world/pkg-builder.toml index d3f89c7d..fde23949 100644 --- a/examples/noble/java-gradle/hello-world/pkg-builder.toml +++ b/examples/noble/java-gradle/hello-world/pkg-builder.toml @@ -26,7 +26,7 @@ gradle_binary_checksum="544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2 [build_env] codename="noble numbat" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/noble/java/hello-world/pkg-builder.toml b/examples/noble/java/hello-world/pkg-builder.toml index 34483f10..02370fa7 100644 --- a/examples/noble/java/hello-world/pkg-builder.toml +++ b/examples/noble/java/hello-world/pkg-builder.toml @@ -21,7 +21,7 @@ jdk_binary_checksum="e4fb2df9a32a876afb0a6e17f54c594c2780e18badfa2e8fc99bc2656b0 [build_env] codename="noble numbat" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/noble/javascript/hello-world/pkg-builder.toml b/examples/noble/javascript/hello-world/pkg-builder.toml index 5d6e4fc0..ca6563ee 100644 --- a/examples/noble/javascript/hello-world/pkg-builder.toml +++ b/examples/noble/javascript/hello-world/pkg-builder.toml @@ -21,7 +21,7 @@ yarn_version = "1.22.19" [build_env] codename="noble numbat" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/noble/nim/hello-world/pkg-builder.toml b/examples/noble/nim/hello-world/pkg-builder.toml index afe31f30..dcf88431 100644 --- a/examples/noble/nim/hello-world/pkg-builder.toml +++ b/examples/noble/nim/hello-world/pkg-builder.toml @@ -20,7 +20,7 @@ nim_version_checksum = "047dde8ff40b18628ac1188baa9ca992d05f1f45c5121d1d07a76224 [build_env] codename="noble numbat" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/noble/python/hello-world/pkg-builder.toml b/examples/noble/python/hello-world/pkg-builder.toml index 5ea7e903..00bc90a4 100644 --- a/examples/noble/python/hello-world/pkg-builder.toml +++ b/examples/noble/python/hello-world/pkg-builder.toml @@ -17,7 +17,7 @@ language_env = "python" [build_env] codename="noble numbat" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/noble/rust/hello-world/pkg-builder.toml b/examples/noble/rust/hello-world/pkg-builder.toml index 6c1b7bef..a53fff78 100644 --- a/examples/noble/rust/hello-world/pkg-builder.toml +++ b/examples/noble/rust/hello-world/pkg-builder.toml @@ -37,7 +37,7 @@ KGFMBQELjcFWLGcBVE45DRuVR8E3XYunjSdgLFXjfZfeGF3uiS6fNHGCH41ryqfj [build_env] codename="noble numbat" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/noble/typescript/hello-world/pkg-builder.toml b/examples/noble/typescript/hello-world/pkg-builder.toml index 7f74e39e..8e7a4f2b 100644 --- a/examples/noble/typescript/hello-world/pkg-builder.toml +++ b/examples/noble/typescript/hello-world/pkg-builder.toml @@ -21,7 +21,7 @@ yarn_version = "1.22.19" [build_env] codename="noble numbat" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/examples/noble/virtual/hello-world/pkg-builder.toml b/examples/noble/virtual/hello-world/pkg-builder.toml index 17e8cadc..40d58e81 100644 --- a/examples/noble/virtual/hello-world/pkg-builder.toml +++ b/examples/noble/virtual/hello-world/pkg-builder.toml @@ -11,7 +11,7 @@ package_type="virtual" [build_env] codename="noble numbat" arch = "amd64" -pkg_builder_version="0.2.11" +pkg_builder_version="0.3.0" debcrafter_version = "8189263" run_lintian=true run_piuparts=true diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..382a5f36 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +# imports_granularity = "Module" +reorder_imports = true \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 9c85be90..00000000 --- a/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -// src/lib.rs -pub mod v1; -pub mod build; \ No newline at end of file diff --git a/src/v1/build/debcrafter_helper.rs b/src/v1/build/debcrafter_helper.rs deleted file mode 100644 index 1450718d..00000000 --- a/src/v1/build/debcrafter_helper.rs +++ /dev/null @@ -1,199 +0,0 @@ -use log::info; -use std::fs; -use std::io; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; -use tempfile::tempdir; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum Error { - #[error("Command not found: {0}")] - CommandNotFound(#[from] io::Error), - - #[error("Failed to execute command: {0}")] - CommandFailed(CommandError), - - #[error("Failed to clone: {0}")] - GitClone(#[from] git2::Error), -} - -#[derive(Debug, Error)] -pub enum CommandError { - #[error("{0}")] - StringError(String), - #[error("{0}")] - IOError(io::Error), -} - -impl From for CommandError { - fn from(err: String) -> Self { - CommandError::StringError(err) - } -} - -impl From for CommandError { - fn from(err: io::Error) -> Self { - CommandError::IOError(err) - } -} - -// TODO use from crates.io -pub fn check_if_dpkg_parsechangelog_installed() -> Result<(), Error> { - let mut cmd = Command::new("which"); - cmd.arg("dpkg-parsechangelog"); - - handle_failure( - &mut cmd, - "dpkg-parsechangelog is not installed, please install it.".to_string(), - )?; - Ok(()) -} - -pub fn check_if_installed(debcrafter_version: &String) -> eyre::Result<()> { - match Command::new("which") - .arg(format!("debcrafter_{}", debcrafter_version)) - .output() - { - Ok(output) => { - if output.status.success() { - Ok(()) - } else { - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); - - eyre::bail!( - "Command failed with exit code {:?}\nstdout: {}\nstderr: {}", - output.status.code(), - stdout, - stderr - ); - } - } - Err(err) => eyre::bail!("Failed to execute 'which' command: {}", err), - } -} - -// pub fn check_version_compatibility(debcrafter_version: &str) -> Result<(), String> { -// let output = Command::new("debcrafter") -// .arg("--version") -// .output() -// .map_err(|_| "Failed to execute debcrafter --version")?; - -// let output_str = String::from_utf8_lossy(&output.stdout); - -// if !output.status.success() && output_str.contains(debcrafter_version) { -// return Err("debcrafter does not have the required version".to_string()); -// } -// Ok(()) -// } - -pub fn create_debian_dir(specification_file: &str, target_dir: &str, debcrafter_version: &String) -> Result<(), Error> { - let debcrafter_dir = tempdir().expect("Failed to create temporary directory"); - - let spec_file_path = fs::canonicalize(PathBuf::from(specification_file)).map_err(|_| { - Error::CommandFailed(format!("{} spec_file doesn't exist", specification_file).into()) - })?; - if !spec_file_path.exists() { - return Err(Error::CommandFailed( - format!("{} spec_file doesn't exist", specification_file).into(), - )); - } - let spec_dir = spec_file_path.parent().unwrap(); - let spec_file_name = spec_file_path.file_name().unwrap(); - info!("Spec directory: {:?}", spec_dir.to_str().unwrap()); - info!("Spec file: {:?}", spec_file_name); - info!("Debcrafter directory: {:?}", debcrafter_dir); - let mut cmd = Command::new(format!("debcrafter_{}", debcrafter_version)); - cmd.arg(spec_file_name) - .current_dir(spec_dir) - .arg(debcrafter_dir.path()); - - handle_failure(&mut cmd, "Debcrafter error".to_string())?; - - if let Some(first_directory) = get_first_directory(debcrafter_dir.path()) { - let tmp_debian_dir = first_directory.join("debian"); - let dest_dir = Path::new(target_dir).join("debian"); - copy_dir_contents_recursive(&tmp_debian_dir, &dest_dir) - .map_err(|err| Error::CommandFailed(err.into()))?; - } else { - return Err(Error::CommandFailed( - "Unable to create debian dir.".to_string().into(), - )); - } - Ok(()) -} - -fn copy_dir_contents_recursive(src_dir: &Path, dest_dir: &Path) -> io::Result<()> { - info!( - "Copying directory: {:?} to {:?}", - src_dir.display(), - dest_dir.display() - ); - - if src_dir.is_dir() { - if !dest_dir.exists() { - fs::create_dir_all(dest_dir)?; - } - - for entry in fs::read_dir(src_dir)? { - let entry = entry?; - let src_path = entry.path(); - let dest_path = dest_dir.join(entry.file_name()); - - if src_path.is_dir() { - copy_dir_contents_recursive(&src_path, &dest_path)?; - } else { - fs::copy(&src_path, &dest_path)?; - } - } - } - - Ok(()) -} -fn handle_failure(cmd: &mut Command, error: String) -> Result<(), Error> { - let output = cmd - .output() - .map_err(|_| Error::CommandFailed(error.clone().into()))?; - - if !output.status.success() { - //let stderr = String::from_utf8_lossy(&output.stderr).to_string(); - return Err(Error::CommandFailed(error.into())); - } - Ok(()) -} -fn get_first_directory(dir: &Path) -> Option { - if dir.is_dir() { - for entry in fs::read_dir(dir).ok()? { - let entry = entry.unwrap(); - let path = entry.path(); - if path.is_dir() { - return Some(path); - } - } - } - None -} - -#[cfg(test)] -mod tests { - // use super::*; - - // #[test] - // fn test_debcrafter_installed() { - // let result = check_if_installed(); - // assert!(result.is_err()); - // assert_eq!(result.unwrap_err(), "debcrafter is not installed"); - // } - - // #[test] - // fn test_debcrafter_version_incompatibility() { - // let result = check_version_compatibility("1.0.0"); - // assert!(result.is_err()); - // assert_eq!( - // result.unwrap_err(), - // "debcrafter does not have the required version" - // ); - // } -} diff --git a/src/v1/build/dir_setup.rs b/src/v1/build/dir_setup.rs deleted file mode 100644 index 519f1afd..00000000 --- a/src/v1/build/dir_setup.rs +++ /dev/null @@ -1,901 +0,0 @@ -use std::io::BufRead; -use std::io::BufReader; -use std::{env, fs, io}; - -use eyre::{eyre, Result}; - -use crate::v1::build::debcrafter_helper; -use crate::v1::pkg_config::SubModule; -use dirs::home_dir; -use filetime::FileTime; -use log::info; -use sha2::{Digest, Sha256, Sha512}; -use std::io::{Read, Write}; -use std::os::unix::fs::PermissionsExt; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; - -pub fn create_package_dir(build_artifacts_dir: &String) -> Result<()> { - if fs::metadata(build_artifacts_dir).is_ok() { - info!("Remove previous package folder {}", &build_artifacts_dir); - fs::remove_dir_all(build_artifacts_dir)?; - } - info!("Creating package folder {}", &build_artifacts_dir); - fs::create_dir_all(build_artifacts_dir)?; - Ok(()) -} - -pub fn download_source(tarball_path: &str, tarball_url: &str, config_root: &str) -> Result<()> { - info!("Downloading source {}", tarball_path); - let is_web = tarball_url.starts_with("http"); - let tarball_url = get_tarball_url(tarball_url, config_root); - if is_web { - info!( - "Downloading tar: {} to location: {}", - tarball_url, tarball_path - ); - let status = Command::new("wget") - .arg("-q") - .arg("-O") - .arg(tarball_path) - .arg(tarball_url) - .status()?; - if !status.success() { - return Err(eyre!("Download failed".to_string())); - } - } else { - info!("Copying tar: {} to location: {}", tarball_url, tarball_path); - fs::copy(tarball_url, tarball_path)?; - } - Ok(()) -} - -pub fn update_submodules(git_submodules: &Vec, current_dir: &str) -> Result<()> { - // DO not use git2, it has very little git supported functionality - // Initialize all submodules if they are not already initialized - // Update submodules to specific commits - for submodule in git_submodules.clone() { - let output = Command::new("git") - .current_dir(Path::new(current_dir).join(submodule.path.clone())) - .args(&["checkout", &submodule.commit.clone()]) - .output() - .map_err(|err| eyre!(format!("Failed to checkout submodule {}", err)))?; - if !output.status.success() { - return Err(eyre!( - "Failed to checkout commit {} for submodule {}: {}", - submodule.commit, - submodule.path, - String::from_utf8_lossy(&output.stderr) - )); - } - } - - Ok(()) -} - -pub fn clone_and_checkout_tag( - git_url: &str, - tag_version: &str, - path: &str, - git_submodules: &Vec, -) -> Result<()> { - match Command::new("which").arg("git-lfs").output() { - Ok(_) => Ok(()), - Err(_) => Err(eyre!("git-lfs is not installed, please install it!")), - }?; - - let output = Command::new("git") - .args(&[ - "clone", - "--depth", - "1", - "--branch", - tag_version, - git_url, - path, - ]) - .output() - .expect("Failed to execute git clone command"); - if !output.status.success() { - return Err(eyre!( - "Failed to checkout tag {}: {}", - tag_version, - String::from_utf8_lossy(&output.stderr) - )); - } - - // Initialize submodules - let output = Command::new("git") - .current_dir(path) - .args(&["submodule", "update", "--init", "--recursive"]) - .output()?; - - if !output.status.success() { - return Err(eyre!( - "Failed to initialize submodules: {}", - String::from_utf8_lossy(&output.stderr) - )); - } - - update_submodules(git_submodules, path)?; - - Ok(()) -} - -fn set_creation_time>(dir_path: P, timestamp: FileTime) -> io::Result<()> { - filetime::set_file_mtime(&dir_path, timestamp)?; - filetime::set_file_atime(&dir_path, timestamp)?; - - let mut stack = vec![PathBuf::from(dir_path.as_ref())]; - - while let Some(current) = stack.pop() { - for entry in fs::read_dir(¤t)? { - let entry = entry?; - let file_type = entry.file_type()?; - let file_path = entry.path(); - - if file_type.is_dir() { - stack.push(file_path.clone()); // Push directory onto stack for processing - filetime::set_file_mtime(&file_path, timestamp)?; - filetime::set_file_atime(&file_path, timestamp)?; - } else if file_type.is_file() { - filetime::set_file_mtime(&file_path, timestamp)?; - filetime::set_file_atime(&file_path, timestamp)?; - } else if file_type.is_symlink() { - filetime::set_symlink_file_times(&file_path, timestamp, timestamp)?; - } - } - } - - Ok(()) -} - -pub fn download_git( - build_artifacts_dir: &str, - tarball_path: &str, - package_name: &str, - git_url: &str, - tag_version: &str, - git_submodules: &Vec, -) -> Result<()> { - let path = Path::new(build_artifacts_dir).join(package_name); - if path.exists() { - fs::remove_dir_all(path.clone())?; - } - fs::create_dir_all(&path.clone())?; - //let path = Path::new("/tmp/nimbus"); - clone_and_checkout_tag( - git_url, - tag_version, - path.clone().to_str().unwrap(), - &git_submodules, - )?; - // remove .git directory, no need to package it - fs::remove_dir_all(path.join(".git"))?; - - // // Back in the path for reproducibility: January 1, 2022 - let timestamp = FileTime::from_unix_time(1640995200, 0); - set_creation_time(path.clone(), timestamp)?; - - info!("Creating tar from git repo from {}", path.display()); - let output = Command::new("tar") - .args(&[ - "--sort=name", - "--owner=0", - "--group=0", - "--numeric-owner", - // does not work - // "--mtime='2019-01-01 00:00'", - "--pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime", - "-czf", - tarball_path, - package_name, - ]) - .current_dir(build_artifacts_dir) - .output()?; - if !output.status.success() { - return Err(eyre!(format!( - "Failed to create tarball: {}", - String::from_utf8_lossy(&output.stderr) - )) - .into()); - } - - Ok(()) -} - -pub fn create_empty_tar(build_artifacts_dir: &str, tarball_path: &str) -> Result<()> { - info!("Creating empty .tar.gz for virtual package"); - let output = Command::new("tar") - .args(["czvf", tarball_path, "--files-from", "/dev/null"]) - .current_dir(build_artifacts_dir) - .output()?; - if !output.status.success() { - return Err(eyre!("Virtual package .tar.gz creation failed".to_string(),)); - } - - Ok(()) -} - -pub fn calculate_sha512(mut reader: R) -> Result { - let mut hasher = Sha512::new(); - io::copy(&mut reader, &mut hasher)?; - let digest_bytes = hasher.finalize(); - let hex_digest = digest_bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::(); - - Ok(hex_digest) -} - -pub fn calculate_sha256(mut reader: R) -> Result { - let mut hasher = Sha256::new(); - io::copy(&mut reader, &mut hasher)?; - let digest_bytes = hasher.finalize(); - let hex_digest = digest_bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::(); - - Ok(hex_digest) -} - -pub fn verify_tarball_checksum(tarball_path: &str, expected_checksum: &str) -> Result { - let mut file = fs::File::open(tarball_path).map_err(|_| eyre!("Could not open tarball."))?; - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer) - .map_err(|_| eyre!("Could not read tarball."))?; - - let actual_sha512 = calculate_sha512(&*buffer.clone()).unwrap_or_default(); - info!("sha512 hash {}", &actual_sha512); - - if actual_sha512 == expected_checksum { - return Ok(true); - } - - let actual_sha256 = calculate_sha256(&*buffer).unwrap_or_default(); - info!("sha256 hash {}", &actual_sha256); - - if actual_sha256 == expected_checksum { - return Ok(true); - } - Err(eyre!("Hashes do not match.")) -} - -pub fn verify_hash(tarball_path: &str, expected_checksum: Option) -> Result<()> { - match expected_checksum { - Some(tarball_hash) => match verify_tarball_checksum(tarball_path, &tarball_hash) { - Ok(true) => Ok(()), - Ok(false) => Err(eyre!("Checksum is invalid.")), - Err(err) => Err(eyre!("Error checking hash: {}", err)), - }, - None => Ok(()), // If no checksum is provided, consider it verified - } -} - -pub fn extract_source(tarball_path: &str, build_files_dir: &str) -> Result<()> { - info!("Extracting source {}", &build_files_dir); - fs::create_dir_all(build_files_dir)?; - - let mut args = vec!["zxvf", &tarball_path, "-C", &build_files_dir]; - let numbers_to_strip = components_to_strip(tarball_path.to_string().clone()); - let numbers_to_strip = numbers_to_strip.unwrap_or_default(); - let strip = format!("--strip-components={}", numbers_to_strip); - if numbers_to_strip > 0 { - args.push(&strip); - } - info!("Stripping components: {} {:?}", numbers_to_strip, args); - let output = Command::new("tar").args(args).output()?; - if !output.status.success() { - let error_message = String::from_utf8(output.stderr) - .unwrap_or_else(|_| "Unknown error occurred during extraction".to_string()); - return Err(eyre!(error_message)); - } - info!("Extracted source to build_files_dir: {:?}", build_files_dir); - - Ok(()) -} - -pub fn create_debian_dir( - build_files_dir: &String, - debcrafter_version: &String, - spec_file: &str, -) -> Result<()> { - debcrafter_helper::check_if_dpkg_parsechangelog_installed()?; - debcrafter_helper::check_if_installed(debcrafter_version)?; - - debcrafter_helper::create_debian_dir(spec_file, build_files_dir, debcrafter_version)?; - info!( - "Created /debian dir under build_files_dir folder: {:?}", - build_files_dir - ); - Ok(()) -} - -pub fn patch_quilt(build_files_dir: &String) -> Result<()> { - let debian_source_format_path = format!("{}/debian/source/format", build_files_dir); - info!( - "Setting up quilt format for patching. Debian source format path: {}", - debian_source_format_path - ); - let debian_source_dir = PathBuf::from(&build_files_dir).join("debian/source"); - if !debian_source_dir.exists() { - fs::create_dir_all(&debian_source_dir)?; - info!( - "Created debian/source directory at: {:?}", - debian_source_dir - ); - } - - if !Path::new(&debian_source_format_path).exists() { - fs::write(&debian_source_format_path, "3.0 (quilt)\n")?; - info!( - "Quilt format file created at: {}", - debian_source_format_path - ); - } else { - info!( - "Quilt format file already exists at: {}", - debian_source_format_path - ); - } - Ok(()) -} - -pub fn patch_pc_dir(build_files_dir: &String) -> Result<()> { - let pc_version_path = format!("{}/.pc/.version", &build_files_dir); - info!("Creating necessary directories for patching"); - fs::create_dir_all(format!("{}/.pc", &build_files_dir))?; - let mut pc_version_file = fs::File::create(pc_version_path)?; - writeln!(pc_version_file, "2")?; - Ok(()) -} - -pub fn patch_standards_version(build_files_dir: &String, homepage: &String) -> Result<()> { - let debian_control_path = format!("{}/debian/control", build_files_dir); - info!( - "Adding Standards-Version to the control file. Debian control path: {}", - debian_control_path - ); - let input_file = fs::File::open(&debian_control_path)?; - let reader = BufReader::new(input_file); - - let original_content: Vec = reader.lines().map(|line| line.unwrap()).collect(); - let has_standards_version = original_content - .iter() - .any(|line| line.starts_with("Standards-Version")); - let standards_version_line = "Standards-Version: 4.5.1"; - let homepage_line = format!("Homepage: {}", homepage); - if !has_standards_version { - let mut insert_index = 0; - for (i, line) in original_content.iter().enumerate() { - if line.starts_with("Priority:") { - insert_index = i + 1; - break; - } - } - - let mut updated_content = original_content.clone(); - updated_content.insert(insert_index, standards_version_line.to_string()); - updated_content.insert(insert_index + 1, homepage_line.to_string()); - - let mut output_file = fs::File::create(&debian_control_path)?; - for line in updated_content { - writeln!(output_file, "{}", line)?; - } - - info!("Standards-Version added to the control file."); - } else { - info!("Standards-Version already exists in the control file. No changes made."); - } - Ok(()) -} - -pub fn copy_src_dir(build_files_dir: &String, src_dir: &String) -> Result<()> { - let src_dir_path = Path::new(src_dir); - if src_dir_path.exists() { - copy_directory_recursive(Path::new(src_dir), Path::new(&build_files_dir)) - .map_err(|err| eyre!(format!("Failed to copy src directory: {}", err)))?; - } - Ok(()) -} - -pub fn patch_rules_permission(build_files_dir: &str) -> Result<()> { - info!( - "Adding executable permission for {}/debian/rules", - build_files_dir - ); - - let debian_rules = format!("{}/debian/rules", build_files_dir); - let mut permissions = fs::metadata(debian_rules.clone()) - .map_err(|_| eyre!("Failed to get debian/rules permission."))? - .permissions(); - permissions.set_mode(permissions.mode() | 0o111); - fs::set_permissions(debian_rules, permissions) - .map_err(|_| eyre!("Failed to set debian/rules permission."))?; - Ok(()) -} - -pub fn patch_source(build_files_dir: &String, homepage: &String, src_dir: &String) -> Result<()> { - // Patch quilt - patch_quilt(build_files_dir)?; - - // Patch .pc dir setup - patch_pc_dir(build_files_dir)?; - - // Patch .pc patch version number - patch_standards_version(build_files_dir, homepage)?; - - // Only copy if src dir exists - copy_src_dir(build_files_dir, src_dir)?; - - patch_rules_permission(build_files_dir)?; - - info!("Patching finished successfully!"); - Ok(()) -} - -pub fn setup_sbuild() -> Result<()> { - let home_dir = home_dir().expect("Home dir is empty"); - let dest_path = home_dir.join(".sbuildrc"); - let content = include_str!(".sbuildrc"); - let home_dir = home_dir.to_str().unwrap_or("/home/runner").to_string(); - let replaced_contents = content.replace("", &home_dir); - let mut file = - fs::File::create(dest_path).map_err(|_| eyre!("Failed to create ~/.sbuildrc."))?; - file.write_all(replaced_contents.as_bytes()) - .map_err(|_| eyre!("Failed to write ~/.sbuildrc."))?; - - Ok(()) -} - -pub fn copy_directory_recursive(src_dir: &Path, dest_dir: &Path) -> Result<(), io::Error> { - if !src_dir.exists() { - return Err(io::Error::new( - io::ErrorKind::NotFound, - format!("Source directory {:?} does not exist", src_dir), - )); - } - - if !dest_dir.exists() { - fs::create_dir_all(dest_dir)?; - } - - for entry in fs::read_dir(src_dir)? { - let entry = entry?; - let entry_path = entry.path(); - let file_name = entry.file_name(); - - let dest_path = dest_dir.join(&file_name); - - if entry_path.is_dir() { - copy_directory_recursive(&entry_path, &dest_path)?; - } else { - if let Err(e) = fs::copy(&entry_path, &dest_path) { - eprintln!( - "Failed to copy file from {:?} to {:?}: {}", - entry_path, dest_path, e - ); - return Err(e); - } - } - } - - Ok(()) -} - -pub fn components_to_strip(tar_gz_file: String) -> Result { - let output = Command::new("tar") - .arg("--list") - .arg("-z") - .arg("-f") - .arg(tar_gz_file) - .output()?; - - let output_str = String::from_utf8_lossy(&output.stdout); - let lines: Vec<&str> = output_str.lines().filter(|l| !l.ends_with('/')).collect(); - - let common_prefix = longest_common_prefix(&lines.clone()); - - let components_to_strip = common_prefix.split('/').filter(|&x| !x.is_empty()).count(); - - Ok(components_to_strip) -} - -pub fn longest_common_prefix(strings: &[&str]) -> String { - if strings.is_empty() { - return String::new(); - } - if strings.len() == 1 { - let mut path_buf = PathBuf::from(strings[0]); - path_buf.pop(); - let common_prefix = path_buf.to_string_lossy().to_string(); - return common_prefix; - } - - let first_string = &strings[0]; - let mut prefix = String::new(); - - 'outer: for (i, c) in first_string.char_indices() { - for string in &strings[1..] { - if let Some(next_char) = string.chars().nth(i) { - if next_char != c { - break 'outer; - } - } else { - break 'outer; - } - } - prefix.push(c); - } - - prefix -} - -pub fn get_build_artifacts_dir( - package_name: &str, - work_dir: &str, - version_number: &str, - revision_number: &str, -) -> String { - let build_artifacts_dir = format!( - "{}/{}-{}-{}", - work_dir, &package_name, version_number, revision_number - ); - build_artifacts_dir -} - -pub fn get_tarball_path( - package_name: &str, - version_number: &str, - build_artifacts_dir: &str, -) -> String { - let tarball_path = format!( - "{}/{}_{}.orig.tar.gz", - &build_artifacts_dir, &package_name, &version_number - ); - tarball_path -} - -pub fn get_build_files_dir( - package_name: &str, - version_number: &str, - build_artifacts_dir: &str, -) -> String { - let build_files_dir = format!( - "{}/{}-{}", - build_artifacts_dir, &package_name, &version_number - ); - build_files_dir -} - -pub fn get_tarball_url(tarball_url: &str, config_root: &str) -> String { - if tarball_url.starts_with("http") { - tarball_url.to_string() - } else { - expand_path(tarball_url, Some(config_root)) - } -} - -pub fn expand_path(dir: &str, dir_to_expand: Option<&str>) -> String { - if dir.starts_with('~') { - let expanded_path = shellexpand::tilde(dir).to_string(); - expanded_path - } else if dir.starts_with('/') { - dir.to_string() - } else { - let parent_dir = match dir_to_expand { - None => env::current_dir().unwrap(), - Some(path) => PathBuf::from(path), - }; - let dir = parent_dir.join(dir); - let path = fs::canonicalize(dir.clone()).unwrap_or(dir); - let path = path.to_str().unwrap().to_string(); - path - } -} - -#[cfg(test)] -mod tests { - use super::*; - use httpmock::prelude::*; - use std::fs::File; - use std::path::PathBuf; - // use std::sync::Once; - // use env_logger::Env; - use crate::v1::pkg_config::{PackageType, PkgConfig}; - use tempfile::tempdir; - - // static INIT: Once = Once::new(); - - fn setup() { - // INIT.call_once(|| { - // env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); - // }); - } - - fn setup_mock_server() -> MockServer { - // Start the mock server - let server = MockServer::start(); - - // Mock the endpoint to serve the tarball file - server.mock(|when, then| { - when.method(GET).path("/test_package.tar.gz"); - then.status(200) - .header("Content-Type", "application/octet-stream") - .body_from_file("tests/misc/test_package.tar.gz"); - }); - - server - } - - #[test] - fn expand_path_expands_tilde_correctly() { - setup(); - let result = expand_path("~", None); - assert_ne!(result, "~"); - assert!(!result.contains('~')); - } - - #[test] - fn expand_path_handles_absolute_paths() { - setup(); - let result = expand_path("/absolute/path", None); - assert_eq!(result, "/absolute/path"); - } - - #[test] - fn expand_path_expands_relative_paths_with_parent() { - setup(); - let result = expand_path("somefile", Some("/tmp")); - assert_eq!(result, "/tmp/somefile"); - } - - #[test] - fn expand_path_expands_relative_paths_without_parent() { - setup(); - let result = expand_path("somefile", None); - assert!(result.starts_with('/')); - } - - #[test] - fn test_create_package_dir() { - setup(); - - let temp_dir = tempdir().expect("Failed to create temporary directory"); - - let build_artifacts_dir = temp_dir.path().join("test_package"); - - let result = create_package_dir(&String::from(build_artifacts_dir.to_str().unwrap())); - - assert!(result.is_ok()); - assert!(build_artifacts_dir.exists()); - } - - #[test] - fn test_create_package_dir_if_already_exists() { - setup(); - - let temp_dir = tempdir().expect("Failed to create temporary directory"); - - let build_artifacts_dir = temp_dir.path().join("test_package"); - let result = fs::create_dir(build_artifacts_dir.clone()); - assert!(result.is_ok()); - let test_file = build_artifacts_dir.clone().join("test_file"); - File::create(test_file.clone()).expect("Failed to create test_file"); - assert!(test_file.clone().exists()); - let result = create_package_dir(&String::from(build_artifacts_dir.to_str().unwrap())); - - assert!(result.is_ok()); - assert!(!test_file.clone().exists()); - assert!(build_artifacts_dir.exists()); - } - - #[test] - fn test_download_source_virtual_package() { - setup(); - - let temp_dir = tempdir().expect("Failed to create temporary directory"); - - let build_artifacts_dir = String::from(temp_dir.path().to_str().unwrap()); - let tarball_name = "test_package.tar.gz"; - let tarball_path = temp_dir.path().join(tarball_name); - let tarball_path_str = String::from(temp_dir.path().join(tarball_name).to_str().unwrap()); - - let result = create_empty_tar(&build_artifacts_dir, &tarball_path_str); - - assert!(result.is_ok()); - assert!(tarball_path.exists()); - } - - #[test] - fn test_download_source_non_virtual_package() { - setup(); - - let server = setup_mock_server(); - - let temp_dir = tempdir().expect("Failed to create temporary directory"); - - let tarball_name = "test_package.tar.gz"; - let tarball_path = temp_dir.path().join(tarball_name); - let tarball_url = format!("{}/{}", server.base_url(), tarball_name); - - let result = download_source(tarball_path.to_str().unwrap(), &tarball_url, "/examples"); - - assert!(result.is_ok()); - assert!(tarball_path.exists()); - } - - #[test] - #[ignore] - fn test_download_source_with_git_package() {} - - #[test] - fn test_extract_source() { - setup(); - let package_name = "test_package"; - let temp_dir = tempdir().expect("Failed to create temporary directory"); - let temp_dir = temp_dir.path(); - let tarball_path: PathBuf = PathBuf::from("tests/misc/test_package.tar.gz"); - - let build_files_dir = temp_dir.join(package_name).to_string_lossy().to_string(); - - assert!(tarball_path.exists()); - let result = extract_source(tarball_path.to_str().unwrap(), &build_files_dir); - - assert!(result.is_ok(), "{:?}", result); - assert!(Path::new(&build_files_dir).exists()); - - let test_file_path = PathBuf::from(build_files_dir.clone()).join("empty_file.txt"); - - assert!( - test_file_path.exists(), - "Empty file not found after extraction" - ); - } - - #[test] - fn patch_rules_permission_adds_exec_permission() -> Result<(), Box> { - setup(); - - let temp_dir = tempdir()?; - let rules_path = temp_dir.path().join("debian/rules"); - fs::create_dir_all(temp_dir.path().join("debian")).expect("Could not create dir"); - File::create(&rules_path)?; - - patch_rules_permission(temp_dir.path().to_str().unwrap())?; - - let permissions = fs::metadata(&rules_path)?.permissions(); - assert_ne!(permissions.mode() & 0o111, 0); - - Ok(()) - } - - #[test] - fn patch_rules_permission_handles_nonexistent_directory() { - setup(); - - let result = patch_rules_permission("/nonexistent/dir"); - - assert!(result.is_err()); - } - - #[test] - fn patch_quilt_creates_source_dir_and_format_file() -> Result<(), Box> { - setup(); - - let temp_dir = tempdir()?; - let build_files_dir = temp_dir.path().to_str().unwrap().to_string(); - - patch_quilt(&build_files_dir)?; - - let debian_source_dir = temp_dir.path().join("debian/source"); - assert!(debian_source_dir.exists()); - - let debian_source_format_path = temp_dir.path().join("debian/source/format"); - let format_content = fs::read_to_string(debian_source_format_path)?; - assert_eq!(format_content, "3.0 (quilt)\n"); - - Ok(()) - } - - #[test] - fn patch_quilt_skips_creation_if_already_exists() -> Result<(), Box> { - setup(); - - let temp_dir = tempdir()?; - let temp_dir = temp_dir.path(); - let build_files_dir = temp_dir.to_str().unwrap().to_string(); - - fs::create_dir_all(temp_dir.join("debian/source")).expect("Failed to create dir for test."); - File::create(temp_dir.join("debian/source/format")).expect("Failed to create file."); - - let result = patch_quilt(&build_files_dir); - assert!(result.is_ok()); - - let entries: Vec<_> = fs::read_dir(temp_dir)?.collect(); - assert_eq!(entries.len(), 1); - - Ok(()) - } - - #[test] - fn test_verify_hash_valid_checksum_512() { - setup(); - let tarball_path = "tests/misc/test_package.tar.gz"; - let expected_checksum = "abd0b8e99f983926dbf60bdcbaef13f83ec7b31d56e68f6252ed05981b237c837044ce768038fc34b71f925e2fb19b7dee451897db512bb4a99e0e1bc96d8ab3"; - - let result = verify_hash(tarball_path, Some(expected_checksum.to_string())); - - assert!(result.is_ok()); - } - - #[test] - fn test_verify_hash_invalid_checksum_512() { - setup(); - let tarball_path = "tests/misc/test_package.tar.gz"; - let expected_checksum = "abd0b8e99f983926dbf60bdcbaef13f83ec7b31d56e68f6252ed05981b237c837044ce768038fc34b71f925e2fb19b7dee451897db512bb4a99e0e1bc96d8ab2"; - - let result = verify_hash(tarball_path, Some(expected_checksum.to_string())); - - assert!(result.is_err()); - assert_eq!( - result.err().unwrap().to_string(), - "Error checking hash: Hashes do not match." - ); - } - - #[test] - fn test_verify_hash_valid_checksum_256() { - setup(); - let tarball_path = "tests/misc/test_package.tar.gz"; - let expected_checksum = "b610e83c026d4c465636779240b6ed40a076593a61df5f6b9f9f59f1a929478d"; - - let result = verify_hash(tarball_path, Some(expected_checksum.to_string())); - - assert!(result.is_ok()); - } - - #[test] - fn test_verify_hash_invalid_checksum_256() { - setup(); - let tarball_path = "tests/misc/test_package.tar.gz"; - let expected_checksum = "b610e83c026d4c465636779240b6ed40a076593a61df5f6b9f9f59f1a929478_"; - - let result = verify_hash(tarball_path, Some(expected_checksum.to_string())); - - assert!(result.is_err()); - assert_eq!( - result.err().unwrap().to_string(), - "Error checking hash: Hashes do not match." - ); - } - - #[test] - fn test_clone_and_checkout_tag() { - let url = "https://github.com/status-im/nimbus-eth2.git"; - let temp_dir = tempdir().expect("Failed to create temporary directory"); - let repo_path = temp_dir.path(); - let repo_path_str = repo_path.to_str().unwrap(); - let tag_version = "v24.3.0"; - let str = fs::read_to_string("examples/bookworm/git-package/nimbus/pkg-builder.toml") - .expect("File does not exist"); - let config: PkgConfig = toml::from_str(&str).expect("Cannot parse file."); - match config.package_type { - PackageType::Git(gitconfig) => { - let result = - clone_and_checkout_tag(url, tag_version, repo_path_str, &gitconfig.submodules); - assert!( - result.is_ok(), - "Failed to clone and checkout tag: {:?}", - result - ); - } - _ => panic!("Wrong type of file."), - } - - fs::remove_dir_all(temp_dir).unwrap(); - } -} diff --git a/src/v1/build/mod.rs b/src/v1/build/mod.rs deleted file mode 100644 index 78dc9aaa..00000000 --- a/src/v1/build/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod sbuild; -pub mod sbuild_packager; -pub mod dir_setup; -pub mod debcrafter_helper; - diff --git a/src/v1/build/sbuild.rs b/src/v1/build/sbuild.rs deleted file mode 100644 index 0031984e..00000000 --- a/src/v1/build/sbuild.rs +++ /dev/null @@ -1,1021 +0,0 @@ -use crate::v1::packager::BackendBuildEnv; -use crate::v1::pkg_config::{LanguageEnv, PackageType, PkgConfig}; -use crate::v1::pkg_config_verify::PkgVerifyConfig; -use cargo_metadata::semver::Version; -use eyre::{eyre, Report, Result}; -use log::{info, warn}; -use rand::random; -use sha1::{Digest, Sha1}; -use std::fs::create_dir_all; -use std::io::{BufRead, BufReader, Read, Write}; -use std::path::{Path, PathBuf}; -use std::process::{Child, Command, Stdio}; -use std::{env, fs, io}; // Import from the sha1 crate - -pub struct Sbuild { - config: PkgConfig, - build_files_dir: String, - cache_dir: String, -} - -impl Sbuild { - pub fn new(config: PkgConfig, build_files_dir: String) -> Sbuild { - Sbuild { - cache_dir: config - .build_env - .sbuild_cache_dir - .clone() - .unwrap_or("~/.cache/sbuild".to_string()), - config, - build_files_dir, - } - } - - fn get_build_deps_based_on_langenv(&self, lang_env: &LanguageEnv) -> Vec { - match lang_env { - LanguageEnv::C => { - let lang_deps = vec![]; - lang_deps - } - LanguageEnv::Python => { - let lang_deps = vec![]; - lang_deps - } - LanguageEnv::Rust(config) => { - // TODO - // let rust_version = &config.rust_version; - let rust_binary_url = &config.rust_binary_url; - let rust_binary_gpg_asc = &config.rust_binary_gpg_asc; - let lang_deps = vec![ - "apt install -y wget gpg gpg-agent".to_string(), - format!("cd /tmp && wget -q -O rust.tar.xz {}", rust_binary_url), - format!( - "cd /tmp && echo \"{}\" >> rust.tar.xz.asc && cat rust.tar.xz.asc ", - rust_binary_gpg_asc - ), - "wget -qO- https://keybase.io/rust/pgp_keys.asc | gpg --import".to_string(), - "cd /tmp && gpg --verify rust.tar.xz.asc rust.tar.xz".to_string(), - "cd /tmp && tar xvJf rust.tar.xz -C . --strip-components=1 --exclude=rust-docs" - .to_string(), - "cd /tmp && /bin/bash install.sh --without=rust-docs".to_string(), - "apt remove -y wget gpg gpg-agent".to_string(), - ]; - lang_deps - } - LanguageEnv::Go(config) => { - // TODO - //let go_version = &config.go_version; - let go_binary_url = &config.go_binary_url; - let go_binary_checksum = &config.go_binary_checksum; - let install = vec![ - "apt install -y wget".to_string(), - format!("cd /tmp && wget -q -O go.tar.gz {}", go_binary_url), - format!("cd /tmp && echo \"{} go.tar.gz\" >> hash_file.txt && cat hash_file.txt", go_binary_checksum), - "cd /tmp && sha256sum -c hash_file.txt".to_string(), - "cd /tmp && rm -rf /usr/local/go && mkdir /usr/local/go && tar -C /usr/local -xzf go.tar.gz".to_string(), - "ln -s /usr/local/go/bin/go /usr/bin/go".to_string(), - "go version".to_string(), - // add write permission, this is a chroot env, with one user, should be fine - "chmod -R a+rwx /usr/local/go/pkg".to_string(), - "apt remove -y wget".to_string(), - ]; - install - } - LanguageEnv::JavaScript(config) | LanguageEnv::TypeScript(config) => { - // let node_version = &config.go_version; - let node_binary_url = &config.node_binary_url; - let node_binary_checksum = &config.node_binary_checksum; - let mut install = vec![ - "apt install -y wget".to_string(), - format!("cd /tmp && wget -q -O node.tar.gz {}", node_binary_url), - format!("cd /tmp && echo \"{} node.tar.gz\" >> hash_file.txt && cat hash_file.txt", node_binary_checksum), - "cd /tmp && sha256sum -c hash_file.txt".to_string(), - "cd /tmp && rm -rf /usr/share/node && mkdir /usr/share/node && tar -C /usr/share/node -xzf node.tar.gz --strip-components=1".to_string(), - "ls -l /usr/share/node/bin".to_string(), - "ln -s /usr/share/node/bin/node /usr/bin/node".to_string(), - "ln -s /usr/share/node/bin/npm /usr/bin/npm".to_string(), - "ln -s /usr/share/node/bin/npx /usr/bin/npx".to_string(), - "ln -s /usr/share/node/bin/corepack /usr/bin/corepack".to_string(), - "apt remove -y wget".to_string(), - "node --version".to_string(), - "npm --version".to_string(), - ]; - if let Some(yarn_version) = &config.yarn_version { - install.push(format!("npm install --global yarn@{}", yarn_version)); - install.push("ln -s /usr/share/node/bin/yarn /usr/bin/yarn".to_string()); - install.push("yarn --version".to_string()); - } - install - } - LanguageEnv::Java(config) => { - let is_oracle = config.is_oracle; - if is_oracle { - let jdk_version = &config.jdk_version; - let jdk_binary_url = &config.jdk_binary_url; - let jdk_binary_checksum = &config.jdk_binary_checksum; - let mut install = vec![ - "apt install -y wget".to_string(), - format!("mkdir -p /opt/lib/jvm/jdk-{version}-oracle && mkdir -p /usr/lib/jvm", version = jdk_version), - format!("cd /tmp && wget -q --output-document jdk.tar.gz {}", jdk_binary_url), - format!("cd /tmp && echo \"{} jdk.tar.gz\" >> hash_file.txt && cat hash_file.txt", jdk_binary_checksum), - "cd /tmp && sha256sum -c hash_file.txt".to_string(), - format!("cd /tmp && tar -zxf jdk.tar.gz -C /opt/lib/jvm/jdk-{version}-oracle --strip-components=1", version = jdk_version), - format!("ln -s /opt/lib/jvm/jdk-{version}-oracle/bin/java /usr/bin/java", version = jdk_version), - format!("ln -s /opt/lib/jvm/jdk-{version}-oracle/bin/javac /usr/bin/javac", version = jdk_version), - "java -version".to_string(), - "apt remove -y wget".to_string(), - ]; - if let Some(gradle_config) = &config.gradle { - let gradle_version = &gradle_config.gradle_version; - let gradle_binary_url = &gradle_config.gradle_binary_url; - let gradle_binary_checksum = &gradle_config.gradle_binary_checksum; - - install.push("apt install -y wget unzip".to_string()); - install.push(format!( - "mkdir -p /opt/lib/gradle-{version}", - version = gradle_version - )); - install.push(format!( - "cd /tmp && wget -q --output-document gradle.tar.gz {}", - gradle_binary_url - )); - install.push(format!("cd /tmp && echo \"{} gradle.tar.gz\" > hash_file.txt && cat hash_file.txt", gradle_binary_checksum)); - install.push("cd /tmp && sha256sum -c hash_file.txt".to_string()); - install.push(format!( - "cd /tmp && unzip gradle.tar.gz && mv gradle-{version} /opt/lib", - version = gradle_version - )); - install.push(format!( - "ln -s /opt/lib/gradle-{version}/bin/gradle /usr/bin/gradle", - version = gradle_version - )); - install.push("gradle -version".to_string()); - install.push("apt remove -y wget".to_string()); - } - return install; - } - vec![] - } - LanguageEnv::Dotnet(config) => { - let dotnet_packages = &config.dotnet_packages; - let deps = config.deps.clone().unwrap_or_default(); - let mut install: Vec = vec![]; - if config.use_backup_version { - install.push("apt install -y wget".to_string()); - install.push("apt install -y libicu-dev".to_string()); - for package in deps { - install.push(format!("apt install -y {}", package)); - } - for package in dotnet_packages { - install.push(format!("cd /tmp && wget -q {}", package.url)); - install.push(format!("cd /tmp && ls && dpkg -i {}.deb", package.name)); - // check package version - install.push(format!("cd /tmp && ls && sha1sum {}.deb", package.name)); - install.push(format!( - "cd /tmp && echo {} {}.deb > hash_file.txt && cat hash_file.txt", - package.hash, package.name - )); - install.push(format!("cd /tmp && sha1sum -c hash_file.txt")); - } - install.push("dotnet --version".to_string()); - install.push("apt remove -y wget".to_string()); - } else if self.config.build_env.codename == "bookworm" - || self.config.build_env.codename == "jammy jellyfish" - { - install.push("apt install -y wget".to_string()); - install.push("cd /tmp && wget -q https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb -O packages-microsoft-prod.deb".to_string()); - install.push("cd /tmp && dpkg -i packages-microsoft-prod.deb".to_string()); - install.push("apt update -y".to_string()); - for package in dotnet_packages { - let pkg = transform_name(&package.name, &self.config.build_env.arch); - install.push(format!("cd /tmp && wget -q {}", package.url)); - install.push(format!("cd /tmp && apt install -y --allow-downgrades {}", pkg)); - install.push(format!("cd /tmp && apt download -y {}", pkg)); - // check package version - install.push(format!("cd /tmp && ls && sha1sum {}.deb", package.name)); - install.push(format!( - "cd /tmp && echo {} {}.deb >> hash_file.txt && cat hash_file.txt", - package.hash, package.name - )); - install.push(format!("cd /tmp && sha1sum -c hash_file.txt")); - } - install.push("dotnet --version".to_string()); - install.push("apt remove -y wget".to_string()); - } else if self.config.build_env.codename == "noble numbat" { - // Note all the previous builds of dotnet fails, as they removed from main repo the dotnet packages - // when new distrubution 24.10 released - install.push("apt-get install software-properties-common -y".to_string()); - install.push("add-apt-repository ppa:dotnet/backports".to_string()); - install.push("apt-get update -y".to_string()); - install.push("apt install -y wget".to_string()); - for package in dotnet_packages { - let pkg = transform_name(&package.name, &self.config.build_env.arch); - install.push(format!("cd /tmp && wget -q {}", package.url)); - install.push(format!("cd /tmp && apt install -y {}", pkg)); - install.push(format!("cd /tmp && apt download -y {}", pkg)); - // check package version - install.push(format!("cd /tmp && ls && sha1sum {}.deb", package.name)); - install.push(format!( - "cd /tmp && echo {} {}.deb >> hash_file.txt && cat hash_file.txt", - package.hash, package.name - )); - install.push(format!("cd /tmp && sha1sum -c hash_file.txt")); - } - install.push("dotnet --version".to_string()); - install.push("apt remove -y wget".to_string()); - } - // validate dotnet packages - return install; - } - LanguageEnv::Nim(config) => { - let nim_version = &config.nim_version; - let nim_binary_url = &config.nim_binary_url; - let nim_version_checksum = &config.nim_version_checksum; - let install = vec![ - "apt install -y wget".to_string(), - format!("rm -rf /tmp/nim-{version} && rm -rf /usr/lib/nim/nim-{version}&& rm -rf /opt/lib/nim/nim-{version} && mkdir /tmp/nim-{version}", version = nim_version), - "mkdir -p /opt/lib/nim && mkdir -p /usr/lib/nim".to_string(), - format!("cd /tmp && wget -q {}", nim_binary_url), - format!("cd /tmp && echo {} >> hash_file.txt && cat hash_file.txt", nim_version_checksum), - "cd /tmp && sha256sum -c hash_file.txt".to_string(), - format!("cd /tmp && tar xJf nim-{version}-linux_x64.tar.xz -C nim-{version} --strip-components=1", version = nim_version), - format!("cd /tmp && mv nim-{version} /opt/lib/nim", version = nim_version), - format!("ln -s /opt/lib/nim/nim-{version}/bin/nim /usr/bin/nim", version = nim_version), - // equality check not working - // format!("installed_version=`nim --version | head -n 1 | awk '{{print $4}}'` && echo \"installed version: $installed_version\" && [ \"$installed_version\" != \"{}\" ] && exit 1", nim_version), - "nim --version".to_string(), - "apt remove -y wget".to_string(), - ]; - install - } - } - } - fn get_build_deps_not_in_debian(&self) -> Vec { - let package_type = &self.config.package_type; - let lang_env = match package_type { - PackageType::Default(config) => Some(&config.language_env), - PackageType::Git(config) => Some(&config.language_env), - PackageType::Virtual => None, - }; - match lang_env { - None => { - vec![] - } - Some(lang_env) => self.get_build_deps_based_on_langenv(lang_env), - } - } - fn get_test_deps_based_on_langenv(&self, lang_env: &LanguageEnv) -> Vec { - match lang_env { - LanguageEnv::C => { - let lang_deps = vec![]; - lang_deps - } - LanguageEnv::Python => { - let lang_deps = vec![]; - lang_deps - } - LanguageEnv::Rust(_) => { - // rust compiles to binary, no need to install under test_bed - let lang_deps = vec![]; - lang_deps - } - LanguageEnv::Go(_) => { - // go compiles to binary, no need to install under test_bed - let lang_deps = vec![]; - lang_deps - } - LanguageEnv::JavaScript(_) | LanguageEnv::TypeScript(_) => { - // do not install node, as we cannot depend on it, make the testbed install it - // let node_version = &config.go_version; - let lang_deps = vec![]; - lang_deps - } - LanguageEnv::Java(_) => { - // do not install jdk, or gradle, as we cannot depend on it, make the testbed install it - // let node_version = &config.go_version; - let lang_deps = vec![]; - lang_deps - } - LanguageEnv::Dotnet(_) => { - // add ms repo, but do not install dotnet, let test_bed add it as intall dependency - if self.config.build_env.codename == "bookworm" - || self.config.build_env.codename == "jammy jellyfish" - { - let install = vec![ - "apt install -y wget".to_string(), - "cd /tmp && wget https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb -O packages-microsoft-prod.deb".to_string(), - "cd /tmp && dpkg -i packages-microsoft-prod.deb ".to_string(), - "apt-get update -y".to_string(), - "apt remove -y wget".to_string(), - ]; - install - } else if self.config.build_env.codename == "noble numbat" { - return vec![]; - } else { - return vec![]; - } - } - LanguageEnv::Nim(_) => { - // nim compiles to binary, no need to install under test_bed - let lang_deps = vec![]; - lang_deps - } - } - } - fn get_test_deps_not_in_debian(&self) -> Vec { - let package_type = &self.config.package_type; - let lang_env = match package_type { - PackageType::Default(config) => Some(&config.language_env), - PackageType::Git(config) => Some(&config.language_env), - PackageType::Virtual => None, - }; - match lang_env { - None => { - vec![] - } - Some(lang_env) => self.get_test_deps_based_on_langenv(lang_env), - } - } - - pub fn get_cache_file(&self) -> String { - let dir = &self.cache_dir; - let expanded_path = if dir.starts_with('~') { - let expanded_path = shellexpand::tilde(dir).to_string(); - expanded_path - } else if dir.starts_with('/') { - self.cache_dir.clone() - } else { - let parent_dir = env::current_dir().unwrap(); - let dir = parent_dir.join(dir); - let path = fs::canonicalize(dir.clone()).unwrap(); - let path = path.to_str().unwrap().to_string(); - path - }; - - let codename = normalize_codename(&self.config.build_env.codename).unwrap(); - let cache_file_name = - format!("{}-{}.tar.gz", codename, self.config.build_env.arch).to_string(); - let path = Path::new(&expanded_path); - let cache_file = path.join(cache_file_name); - cache_file.to_str().unwrap().to_string() - } - - pub fn get_deb_dir(&self) -> &Path { - let deb_dir = Path::new(&self.build_files_dir).parent().unwrap(); - deb_dir - } - pub fn get_deb_name(&self) -> PathBuf { - let deb_dir = self.get_deb_dir(); - let deb_file_name = format!( - "{}_{}-{}_{}.deb", - self.config.package_fields.package_name, - self.config.package_fields.version_number, - self.config.package_fields.revision_number, - self.config.build_env.arch - ); - let deb_name = deb_dir.join(deb_file_name); - deb_name - } - - //hello-world_1.0.0-1_amd64.changes - pub fn get_changes_file(&self) -> PathBuf { - let deb_dir = self.get_deb_dir(); - let deb_file_name = format!( - "{}_{}-{}_{}.changes", - self.config.package_fields.package_name, - self.config.package_fields.version_number, - self.config.package_fields.revision_number, - self.config.build_env.arch - ); - let deb_name = deb_dir.join(deb_file_name); - deb_name - } -} - -impl BackendBuildEnv for Sbuild { - fn clean(&self) -> Result<()> { - let cache_file = self.get_cache_file(); - info!("Cleaning cached build: {}", cache_file); - let path = Path::new(&cache_file); - if path.exists() { - remove_file_or_directory(&cache_file, false) - .map_err(|_| eyre!("Could not remove previous cache file!"))?; - } - Ok(()) - } - - fn create(&self) -> Result<()> { - let mut temp_dir = env::temp_dir(); - let dir_name = format!("temp_{}", random::()); - temp_dir.push(dir_name); - fs::create_dir(&temp_dir)?; - - let cache_file = self.get_cache_file(); - let cache_dir = Path::new(&cache_file).parent().unwrap(); - create_dir_all(cache_dir).map_err(|_| eyre!("Failed to create cache_dir"))?; - let codename = normalize_codename(&self.config.build_env.codename)?; - - let repo_url = get_repo_url(&self.config.build_env.codename.as_str())?; - let create_result = Command::new("sbuild-createchroot") - .arg("--chroot-mode=unshare") - .arg("--make-sbuild-tarball") - .arg(cache_file) - .arg(codename) - .arg(temp_dir) - .arg(repo_url) - .status(); - - if let Err(err) = create_result { - return Err(eyre!(format!("Failed to create new chroot: {}", err))); - } - Ok(()) - } - fn package(&self) -> Result<()> { - let codename = normalize_codename(&self.config.build_env.codename)?; - - let mut cmd_args = vec![ - "-d".to_string(), - codename.to_string(), - "-A".to_string(), // build_arch_all - "-s".to_string(), // build source - "--source-only-changes".to_string(), // source_only_changes - "-c".to_string(), // override cache file location, default is ~/.cache/sbuild both by sbuild and pkg-builder - self.get_cache_file(), - "-v".to_string(), // verbose - "--chroot-mode=unshare".to_string(), - ]; - - let mut lang_deps = self.get_build_deps_not_in_debian(); - - if &self.config.build_env.codename == "noble numbat" { - lang_deps.push("apt install -y software-properties-common".to_string()); - lang_deps.push("add-apt-repository universe".to_string()); - lang_deps.push("add-apt-repository restricted".to_string()); - lang_deps.push("add-apt-repository multiverse".to_string()); - lang_deps.push("apt update".to_string()); - } - - for action in lang_deps.iter() { - cmd_args.push(format!("--chroot-setup-commands={}", action)) - } - - cmd_args.push("--no-run-piuparts".to_string()); - cmd_args.push("--no-apt-upgrade".to_string()); - cmd_args.push("--no-apt-distupgrade".to_string()); - - if let Some(true) = self.config.build_env.run_lintian { - cmd_args.push("--run-lintian".to_string()); - cmd_args.push("--lintian-opt=-i".to_string()); - cmd_args.push("--lintian-opt=--I".to_string()); - cmd_args.push("--lintian-opt=--suppress-tags".to_string()); - cmd_args.push("--lintian-opt=bad-distribution-in-changes-file".to_string()); - cmd_args.push("--lintian-opt=--suppress-tags".to_string()); - cmd_args.push("--lintian-opt=debug-file-with-no-debug-symbols".to_string()); - cmd_args.push("--lintian-opt=--tag-display-limit=0".to_string()); - cmd_args.push("--lintian-opts=--fail-on=error".to_string()); - cmd_args.push("--lintian-opts=--fail-on=warning".to_string()); - } else { - cmd_args.push("--no-run-lintian".to_string()); - } - - cmd_args.push("--no-run-autopkgtest".to_string()); - - info!( - "Building package by invoking: sbuild {}", - cmd_args.join(" ") - ); - - let mut cmd = Command::new("sbuild") - .current_dir(self.build_files_dir.clone()) - .args(&cmd_args) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .spawn()?; - run_process(&mut cmd)?; - - if let Some(true) = self.config.build_env.run_piuparts { - self.run_piuparts()?; - }; - - if let Some(true) = self.config.build_env.run_autopkgtest { - self.run_autopkgtests()?; - } - - Ok(()) - } - - fn verify(&self, verify_config: PkgVerifyConfig) -> Result<()> { - let output_dir = Path::new(&self.build_files_dir).parent().unwrap(); - let package_hash = verify_config.verify.package_hash; - let mut errors: Vec = vec![]; - for output in package_hash.iter() { - let file = output_dir.join(output.name.clone()); - if !file.exists() { - return Err(eyre!(format!( - "File to be verified does not exist {}", - output.name - ))); - } - let mut file = fs::File::open(file).map_err(|_| eyre!("Could not open file."))?; - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer) - .map_err(|_| eyre!("Could not read file."))?; - let actual_sha1 = calculate_sha1(&*buffer.clone()).unwrap_or_default(); - if actual_sha1 != output.hash { - errors.push(eyre!(format!( - "file {} actual sha1 is {}", - output.name, &actual_sha1 - ))); - } - } - let result = if errors.is_empty() { - println!("Verify is successful!"); - Ok(()) - } else { - let mut combined_report = errors - .pop() - .unwrap_or_else(|| Report::msg("No errors found")); - - for report in errors.into_iter() { - combined_report = combined_report.wrap_err(report); - } - Err(combined_report) - }; - result - } - - fn run_lintian(&self) -> Result<()> { - info!("Running lintian outside, not as same as on CI..",); - check_lintian_version(self.config.build_env.lintian_version.clone())?; - // let deb_dir = self.get_deb_dir(); - let changes_file = self.get_changes_file(); - let changes_file = changes_file.to_str().unwrap(); - let mut cmd_args = vec![ - "--suppress-tags".to_string(), - "bad-distribution-in-changes-file".to_string(), - "-i".to_string(), - "--I".to_string(), - changes_file.to_string(), - "--tag-display-limit=0".to_string(), - "--fail-on=warning".to_string(), // fail on warning - "--fail-on=error".to_string(), // fail on error - "--suppress-tags".to_string(), // overrides fails for this message - "debug-file-with-no-debug-symbols".to_string(), - ]; - let codename = normalize_codename(&self.config.build_env.codename)?; - - if codename == "jammy".to_string() || codename == "noble".to_string() { - // changed a format of .deb packages on ubuntu, it's not a bug - // but some lintian will report as such - cmd_args.push("--suppress-tags".to_string()); - cmd_args.push("malformed-deb-archive".to_string()); - } - - info!( - "Testing package by invoking: lintian {}", - cmd_args.join(" ") - ); - - let mut cmd = Command::new("lintian") - // for CI - .args(&cmd_args) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .spawn()?; - run_process(&mut cmd) - } - - fn run_piuparts(&self) -> Result<()> { - info!("Running piuparts command with elevated privileges..",); - info!( - "Piuparts must run as root user through sudo, please provide your password, if prompted." - ); - check_piuparts_version(self.config.build_env.piuparts_version.clone())?; - - let repo_url = get_repo_url(&self.config.build_env.codename.as_str())?; - let keyring = get_keyring(&self.config.build_env.codename)?; - let codename = normalize_codename(&self.config.build_env.codename)?; - - let mut cmd_args = vec![ - "-d".to_string(), - codename.to_string(), - "-m".to_string(), - repo_url.to_string(), - "--bindmount=/dev".to_string(), - format!("--keyring={}", keyring), - "--verbose".to_string(), - ]; - let package_type = &self.config.package_type; - - let lang_env = match package_type { - PackageType::Default(config) => Some(&config.language_env), - PackageType::Git(config) => Some(&config.language_env), - PackageType::Virtual => None, - }; - if let Some(env) = lang_env { - match env { - LanguageEnv::Dotnet(_) => { - if self.config.build_env.codename == "bookworm" - || self.config.build_env.codename == "jammy jellyfish" - { - let ms_repo = format!( - "deb https://packages.microsoft.com/debian/12/prod {} main", - self.config.build_env.codename - ); - cmd_args.push(format!("--extra-repo={}", ms_repo)); - cmd_args.push("--do-not-verify-signatures".to_string()); - } else if self.config.build_env.codename == "noble numbat" { - } - } - _ => { - // no other package repositories supported - // might supply my own, but not for now - } - } - } - let deb_dir = self.get_deb_dir(); - let deb_name = self.get_deb_name(); - info!( - "Testing package by invoking: sudo -S piuparts {} {}", - cmd_args.join(" "), - deb_name.to_str().unwrap() - ); - info!( - "Note this command run inside of directory: {}", - deb_dir.display() - ); - - let mut cmd = Command::new("sudo") - .current_dir(deb_dir) - // for CI - .arg("-S") - .arg("piuparts") - .args(&cmd_args) - .arg(deb_name) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .spawn()?; - run_process(&mut cmd) - } - - fn run_autopkgtests(&self) -> Result<()> { - info!("Running autopkgtests command outside of build env.",); - check_autopkgtest_version(self.config.build_env.autopkgtest_version.clone())?; - let codename = normalize_codename(&self.config.build_env.codename)?; - - let image_name = format!( - "autopkgtest-{}-{}.img", - codename, self.config.build_env.arch - ); - let mut cache_dir = self.cache_dir.clone(); - if cache_dir.starts_with('~') { - cache_dir = shellexpand::tilde(&cache_dir).to_string() - } - let image_path = Path::new(&cache_dir).join(image_name.clone()); - create_autopkgtest_image( - image_path.clone(), - self.config.build_env.codename.to_string(), - self.config.build_env.arch.to_string(), - )?; - - let deb_dir = self.get_deb_dir(); - // let deb_name = self.get_deb_name(); - let changes_file = self.get_changes_file(); - let mut cmd_args = vec![ - changes_file.to_str().unwrap().to_string(), - // this will not going rebuild the package, which we want to avoid - // as some packages can take an hour to build, - // we don't want to build for 2 hours - "--no-built-binaries".to_string(), - // needed dist-upgrade as testbed is outdated, when new version of distribution released - "--apt-upgrade".to_string(), - ]; - let lang_deps = self.get_test_deps_not_in_debian(); - - for action in lang_deps.iter() { - cmd_args.push(format!("--setup-commands={}", action)) - } - cmd_args.push("--".to_string()); - cmd_args.push("qemu".to_string()); - cmd_args.push(image_path.to_str().unwrap().to_string()); - info!( - "Testing package by invoking: autopkgtest {}", - cmd_args.join(" ") - ); - info!( - "Note this command run inside of directory: {}", - deb_dir.display() - ); - let mut cmd = Command::new("autopkgtest") - .current_dir(deb_dir) - .args(&cmd_args) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .spawn()?; - run_process(&mut cmd) - } -} - -fn check_lintian_version(expected_version: String) -> Result<()> { - let output = Command::new("lintian").arg("--version").output()?; - - if output.status.success() { - let mut output_str = String::from_utf8_lossy(&output.stdout) - .to_string() - .replace("Lintian v", "") - .replace("\n", "") - .trim() - .to_string(); - if let Some(pos) = output_str.find("ubuntu") { - output_str.truncate(pos); - output_str = output_str.trim().to_string(); - } - warn_compare_versions(expected_version, &output_str, "lintian")?; - Ok(()) - } else { - Err(eyre!("Failed to execute lintian --version")) - } -} - -fn check_piuparts_version(expected_version: String) -> Result<()> { - let output = Command::new("piuparts").arg("--version").output()?; - - if output.status.success() { - let output_str = String::from_utf8_lossy(&output.stdout) - .to_string() - .replace("piuparts ", "") - .replace("\n", "") - .trim() - .to_string(); - warn_compare_versions(expected_version, &output_str, "piuparts")?; - Ok(()) - } else { - Err(eyre!("Failed to execute piuparts --version")) - } -} - -fn check_autopkgtest_version(expected_version: String) -> Result<()> { - let output = Command::new("apt") - .arg("list") - .arg("--installed") - .arg("autopkgtest") - .output()?; - - //autopkgtest/jammy-updates,now 5.32ubuntu3~22.04.1 all [installed] - if output.status.success() { - let version: String = String::from_utf8_lossy(&output.stdout) - .to_string() - .split_whitespace() - .find(|s| s.chars().next().unwrap_or(' ').is_digit(10)) - .map(|version| { - version - .chars() - .take_while(|c| c.is_digit(10) || *c == '.') - .collect() - }) - .unwrap_or_default(); - info!("autopkgtest version {}", version); - // append versions, to it looks like semver - let expected_version = format!("{}.0", expected_version); - let actual_version = format!("{}.0", version); - warn_compare_versions(expected_version, &actual_version, "autopkgtest")?; - Ok(()) - } else { - Err(eyre!("Failed to execute apt list --installed autopkgtest")) - } -} - -pub fn warn_compare_versions( - expected_version: String, - actual_version: &str, - program_name: &str, -) -> Result<()> { - let expected_version = Version::parse(&expected_version).unwrap(); - let actual_version = Version::parse(actual_version).unwrap(); - match expected_version.cmp(&actual_version) { - std::cmp::Ordering::Less => { - warn!("Warning: using newer versions than expected version."); - Ok(()) - } - std::cmp::Ordering::Greater => { - warn!("Using older version of {}", program_name); - Ok(()) - } - std::cmp::Ordering::Equal => { - info!("Versions match. Proceeding."); - Ok(()) - } - } -} - -pub fn normalize_codename(codename: &str) -> Result<&str> { - match codename { - "bookworm" => Ok("bookworm"), - "noble numbat" => Ok("noble"), - "jammy jellyfish" => Ok("jammy"), - _ => Err(eyre!("Not supported distribution")), - } -} - -pub fn get_keyring(codename: &str) -> Result<&str> { - match codename { - "bookworm" => Ok("/usr/share/keyrings/debian-archive-keyring.gpg"), - "noble numbat" | "jammy jellyfish" => Ok("/usr/share/keyrings/ubuntu-archive-keyring.gpg"), - _ => Err(eyre!("Not supported distribution")), - } -} - -pub fn get_repo_url(codename: &str) -> Result<&str> { - match codename { - "bookworm" => Ok("http://deb.debian.org/debian"), - "noble numbat" | "jammy jellyfish" => Ok("http://archive.ubuntu.com/ubuntu"), - _ => Err(eyre!("Not supported distribution")), - } -} - -pub fn calculate_sha1(mut reader: R) -> Result { - let mut hasher = Sha1::new(); - io::copy(&mut reader, &mut hasher)?; - let digest_bytes = hasher.finalize(); - let hex_digest = digest_bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::(); - - Ok(hex_digest) -} - -fn create_autopkgtest_image(image_path: PathBuf, codename: String, arch: String) -> Result<()> { - // do not recreate image if exists - if image_path.exists() { - return Ok(()); - } - info!("autopkgtests environment does not exist. Creating it."); - info!("please provide your password through sudo to as autopkgtest env creation requires it."); - create_dir_all(image_path.parent().unwrap())?; - let repo_url = get_repo_url(&codename)?; - - match codename.as_str() { - "bookworm" => { - let codename = normalize_codename(&codename)?; - let cmd_args = vec![ - codename.to_string(), - image_path.to_str().unwrap().to_string(), - format!("--mirror={}", repo_url), - format!("--arch={}", arch), - ]; - let mut cmd = Command::new("sudo") - // for CI - .arg("-S") - .arg("autopkgtest-build-qemu") - .args(&cmd_args) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .spawn()?; - run_process(&mut cmd) - } - "noble numbat" | "jammy jellyfish" => { - let codename = normalize_codename(&codename)?; - let cmd_args = vec![ - format!("--release={}", codename.to_string()), - format!("--mirror={}", repo_url), - format!("--arch={}", arch), - "-v".to_string(), - ]; - let mut cmd = Command::new("sudo") - // for CI - .arg("-S") - .arg("autopkgtest-buildvm-ubuntu-cloud") - .args(&cmd_args) - .current_dir(image_path.parent().unwrap().to_str().unwrap()) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .spawn()?; - run_process(&mut cmd) - } - _ => Err(eyre!("Not supported distribution")), - } -} - -fn run_process(child: &mut Child) -> Result<()> { - if let Some(stdout) = child.stdout.take() { - let reader = BufReader::new(stdout); - - for line in reader.lines() { - let line = line?; - info!("{}", line); - } - } - io::stdout().flush()?; - - let status = child.wait().map_err(|err| eyre!(err.to_string()))?; - if status.success() { - Ok(()) - } else { - Err(eyre!("Sbuild exited with non-zero status code. Please see build output for potential causes.")) - } -} - -fn remove_file_or_directory(path: &str, is_directory: bool) -> io::Result<()> { - if is_directory { - fs::remove_dir_all(path)?; - } else { - fs::remove_file(path)?; - } - Ok(()) -} - -fn transform_name(input: &str, arch: &str) -> String { - if let Some(pos) = input.find(format!("_{}", arch).as_str()) { - let trimmed = &input[..pos]; - trimmed.replace('_', "=") - } else { - input.replace('_', "=") - } -} - -#[cfg(test)] -mod tests { - use super::*; - use env_logger::Env; - use std::fs::File; - use std::sync::Once; - use tempfile::tempdir; - - static INIT: Once = Once::new(); - - // Set up logging for tests - fn setup() { - INIT.call_once(|| { - env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); - }); - } - - #[test] - fn test_clean_sbuild_env_when_file_does_not_exist() { - setup(); - let mut pkg_config = PkgConfig::default(); - let build_files_dir = tempdir().unwrap().path().to_str().unwrap().to_string(); - pkg_config.build_env.codename = "bookworm".to_string(); - pkg_config.build_env.arch = "amd64".to_string(); - let sbuild_cache_dir = tempdir().unwrap().path().to_str().unwrap().to_string(); - pkg_config.build_env.sbuild_cache_dir = Some(sbuild_cache_dir); - let build_env = Sbuild::new(pkg_config, build_files_dir); - let result = build_env.clean(); - assert!(result.is_ok()); - let cache_file = build_env.get_cache_file(); - let cache_file_path = Path::new(&cache_file); - assert!(!cache_file_path.exists()) - } - - #[test] - fn test_clean_sbuild_env() { - setup(); - let mut pkg_config = PkgConfig::default(); - let build_files_dir = tempdir().unwrap().path().to_str().unwrap().to_string(); - pkg_config.build_env.codename = "bookworm".to_string(); - pkg_config.build_env.arch = "amd64".to_string(); - let sbuild_cache = tempdir().unwrap(); - // create dir manually, as it doesn't exist - create_dir_all(sbuild_cache.path()) - .expect("Could not create temporary directory for testing."); - let sbuild_cache_dir = sbuild_cache.path().to_str().unwrap().to_string(); - pkg_config.build_env.sbuild_cache_dir = Some(sbuild_cache_dir.clone()); - let build_env = Sbuild::new(pkg_config, build_files_dir); - let cache_file = build_env.get_cache_file(); - let cache_file_path = Path::new(&cache_file); - - File::create(cache_file_path) - .expect("File needs to be created manually before testing deletion."); - assert!(Path::new(&sbuild_cache_dir).exists(),); - - assert!( - cache_file_path.exists(), - "File should exist before testing deletion." - ); - - let result = build_env.clean(); - assert!(result.is_ok()); - assert!(!cache_file_path.exists()) - } - - #[test] - fn test_create_sbuild_env() { - setup(); - let mut pkg_config = PkgConfig::default(); - pkg_config.build_env.codename = "bookworm".to_string(); - pkg_config.build_env.arch = "amd64".to_string(); - let sbuild_cache_dir = tempdir().unwrap().path().to_str().unwrap().to_string(); - pkg_config.build_env.sbuild_cache_dir = Some(sbuild_cache_dir); - - let build_files_dir = tempdir().unwrap().path().to_str().unwrap().to_string(); - let build_env = Sbuild::new(pkg_config, build_files_dir); - build_env.clean().expect("Could not clean previous env."); - let cache_file = build_env.get_cache_file(); - let cache_file_path = Path::new(&cache_file); - assert!(!cache_file_path.exists()); - let result = build_env.create(); - assert!(result.is_ok()); - assert!(cache_file_path.exists()) - } -} diff --git a/src/v1/build/sbuild_packager.rs b/src/v1/build/sbuild_packager.rs deleted file mode 100644 index 1df123c5..00000000 --- a/src/v1/build/sbuild_packager.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crate::v1::build::sbuild::Sbuild; -use crate::v1::packager::{BackendBuildEnv, Packager}; - -use eyre::{Result}; - -use crate::v1::pkg_config::{PackageType, PkgConfig}; -use log::info; -use std::path::PathBuf; -use crate::v1::build::dir_setup::{*}; - -pub struct SbuildPackager { - config: PkgConfig, - source_to_patch_from_path: String, - debian_artifacts_dir: String, - debian_orig_tarball_path: String, - build_files_dir: String, - config_root: String, -} - -impl Packager for SbuildPackager { - type BuildEnv = Sbuild; - - fn new(config: PkgConfig, config_root: String) -> Self { - let package_fields = config.package_fields.clone(); - let config_root_path = PathBuf::from(&config_root); - let source_to_patch_from_path = config_root_path.join("src").to_str().unwrap().to_string(); - let workdir = config - .build_env - .workdir - .clone() - .unwrap_or(format!("~/.pkg-builder/packages/{}", config.build_env.codename)); - let workdir = expand_path(&workdir, None); - let debian_artifacts_dir = get_build_artifacts_dir(&package_fields.package_name, &workdir, &package_fields.version_number, &package_fields.revision_number); - let debian_orig_tarball_path = get_tarball_path( - &package_fields.package_name, - &package_fields.version_number, - &debian_artifacts_dir, - ); - let build_files_dir = get_build_files_dir( - &package_fields.package_name, - &package_fields.version_number, - &debian_artifacts_dir, - ); - let mut updated_config = SbuildPackager { - config, - source_to_patch_from_path, - build_files_dir, - debian_artifacts_dir, - debian_orig_tarball_path, - config_root, - }; - updated_config.config.build_env.workdir = Some(workdir); - let spec_file = package_fields.spec_file; - let spec_file_canonical = config_root_path.join(spec_file); - updated_config.config.package_fields.spec_file = - spec_file_canonical.to_str().unwrap().to_string(); - updated_config - } - - fn package(&self) -> Result<()> { - let pre_build: Result<()> = match &self.config.package_type { - PackageType::Default(config) => { - create_package_dir(&self.debian_artifacts_dir.clone())?; - download_source( - &self.debian_orig_tarball_path, - &config.tarball_url, - &self.config_root, - )?; - verify_hash(&self.debian_orig_tarball_path, config.tarball_hash.clone())?; - extract_source(&self.debian_orig_tarball_path, &self.build_files_dir)?; - create_debian_dir( - &self.build_files_dir.clone(), - &self.config.build_env.debcrafter_version, - &self.config.package_fields.spec_file, - )?; - patch_source( - &self.build_files_dir.clone(), - &self.config.package_fields.homepage, - &self.source_to_patch_from_path, - )?; - setup_sbuild()?; - Ok(()) - } - PackageType::Git(config) => { - create_package_dir(&self.debian_artifacts_dir.clone())?; - download_git( - &self.debian_artifacts_dir, - &self.debian_orig_tarball_path, - &self.config.package_fields.package_name, - &config.git_url, - &config.git_tag, - &config.submodules, - )?; - extract_source(&self.debian_orig_tarball_path, &self.build_files_dir)?; - create_debian_dir( - &self.build_files_dir.clone(), - &self.config.build_env.debcrafter_version, - &self.config.package_fields.spec_file, - )?; - patch_source( - &self.build_files_dir.clone(), - &self.config.package_fields.homepage, - &self.source_to_patch_from_path, - )?; - setup_sbuild()?; - Ok(()) - } - PackageType::Virtual => { - info!("creating virtual package"); - create_package_dir(&self.debian_artifacts_dir.clone())?; - create_empty_tar(&self.debian_artifacts_dir, &self.debian_orig_tarball_path)?; - extract_source(&self.debian_orig_tarball_path, &self.build_files_dir)?; - create_debian_dir( - &self.build_files_dir.clone(), - &self.config.build_env.debcrafter_version, - &self.config.package_fields.spec_file, - )?; - patch_source( - &self.build_files_dir.clone(), - &self.config.package_fields.homepage, - &self.source_to_patch_from_path, - )?; - setup_sbuild()?; - Ok(()) - } - }; - pre_build?; - let build_env = self.get_build_env().unwrap(); - build_env.package()?; - Ok(()) - } - - fn get_build_env(&self) -> Result { - let backend_build_env = Sbuild::new(self.config.clone(), self.build_files_dir.clone()); - Ok(backend_build_env) - } -} - diff --git a/src/v1/cli.rs b/src/v1/cli.rs deleted file mode 100644 index 8dc1b38d..00000000 --- a/src/v1/cli.rs +++ /dev/null @@ -1,193 +0,0 @@ -use super::args::{ActionType, BuildEnvSubCommand, PkgBuilderArgs}; -use super::packager::DistributionPackager; -use crate::v1::pkg_config::{get_config, PkgConfig}; -use clap::Parser; -use env_logger::Env; -use eyre::{eyre, Result}; -use std::{env, fs, path::Path}; -use std::process::Command; -use cargo_metadata::semver; -use log::{error, info, warn}; -use crate::v1::pkg_config_verify::PkgVerifyConfig; -use semver::Version; -use regex::Regex; - -const CONFIG_FILE_NAME: &str = "pkg-builder.toml"; -const VERIFY_CONFIG_FILE_NAME: &str = "pkg-builder-verify.toml"; - - -pub fn run_cli() -> Result<()> { - let args = PkgBuilderArgs::parse(); - env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); - let program_name: &str = env!("CARGO_PKG_NAME"); - let program_version: &str = env!("CARGO_PKG_VERSION"); - match args.action { - ActionType::Verify(command) => { - let config_file = get_config_file(command.config, CONFIG_FILE_NAME)?; - let config = get_config::(config_file.clone())?; - - fail_compare_versions(config.build_env.pkg_builder_version.clone(), program_version, program_name)?; - - let distribution = get_distribution(config, config_file)?; - let verify_config_file = get_config_file(command.verify_config, VERIFY_CONFIG_FILE_NAME)?; - let verify_config_file = get_config::(verify_config_file.clone())?; - let no_package = command.no_package.unwrap_or_default(); - distribution.verify(verify_config_file, !no_package)?; - } - ActionType::Lintian(command) => { - let config_file = get_config_file(command.config, CONFIG_FILE_NAME)?; - let config = get_config::(config_file.clone())?; - - fail_compare_versions(config.build_env.pkg_builder_version.clone(), program_version, program_name)?; - - let distribution = get_distribution(config, config_file)?; - distribution.run_lintian()?; - } - ActionType::Piuparts(command) => { - let config_file = get_config_file(command.config, CONFIG_FILE_NAME)?; - let config = get_config::(config_file.clone())?; - fail_compare_versions(config.build_env.pkg_builder_version.clone(), program_version, program_name)?; - - let distribution = get_distribution(config, config_file)?; - distribution.run_piuparts()?; - } - ActionType::Autopkgtest(command) => { - let config_file = get_config_file(command.config, CONFIG_FILE_NAME)?; - let config = get_config::(config_file.clone())?; - fail_compare_versions(config.build_env.pkg_builder_version.clone(), program_version, program_name)?; - - let distribution = get_distribution(config, config_file)?; - distribution.run_autopkgtests()?; - } - ActionType::Package(command) => { - let config_file = get_config_file(command.config, CONFIG_FILE_NAME)?; - let mut config = get_config::(config_file.clone())?; - fail_compare_versions(config.build_env.pkg_builder_version.clone(), program_version, program_name)?; - - check_sbuild_version(config.build_env.sbuild_version.clone())?; - if let Some(run_piuparts) = command.run_piuparts { - config.build_env.run_piuparts = Some(run_piuparts); - } - if let Some(run_autopkgttests) = command.run_autopkgtest { - config.build_env.run_autopkgtest = Some(run_autopkgttests); - } - if let Some(run_lintian) = command.run_lintian { - config.build_env.run_lintian = Some(run_lintian); - } - let distribution = get_distribution(config, config_file)?; - distribution.package()?; - } - ActionType::Env(build_env_action) => { - match build_env_action.build_env_sub_command { - BuildEnvSubCommand::Create(sub_command) => { - let config_file = get_config_file(sub_command.config, CONFIG_FILE_NAME)?; - let config = get_config::(config_file.clone())?; - fail_compare_versions(config.build_env.pkg_builder_version.clone(), program_version, program_name)?; - - let distribution = get_distribution(config, config_file)?; - distribution.create_build_env()?; - } - BuildEnvSubCommand::Clean(sub_command) => { - let config_file = get_config_file(sub_command.config, CONFIG_FILE_NAME)?; - let config = get_config::(config_file.clone())?; - fail_compare_versions(config.build_env.pkg_builder_version.clone(), program_version, program_name)?; - let distribution = get_distribution(config, config_file)?; - distribution.clean_build_env()?; - } - }; - } - ActionType::Version => { - println!("Version: {}", env!("CARGO_PKG_VERSION")); - } - } - Ok(()) -} - -pub fn check_sbuild_version(expected_version: String) -> Result<()> { - let output = Command::new("sbuild") - .arg("--version") - .output()?; - - if output.status.success() { - let actual_version = String::from_utf8_lossy(&output.stdout).to_string(); - let actual_version = get_first_line(&actual_version); - let actual_version = extract_version(actual_version).unwrap(); - info!("sbuild version {}", actual_version); - fail_compare_versions(expected_version, &actual_version, "sbuild")?; - Ok(()) - } else { - Err(eyre!("Failed to execute sbuild --version")) - } -} - -fn extract_version(input: &str) -> Option<&str> { - // Define a regular expression pattern to match the version number - let re = Regex::new(r"sbuild \(Debian sbuild\) ([\d.]+)").unwrap(); - - // Use the regular expression to capture the version number - if let Some(captures) = re.captures(input) { - if let Some(version) = captures.get(1) { - return Some(version.as_str()); - } - } - None -} -fn get_first_line(text: &str) -> &str { - text.split_once('\n').map_or(text, |(first_line, _rest)| first_line) -} - -pub fn fail_compare_versions(expected_version: String, actual_version: &str, program_name: &str) -> Result<()> { - let expected_version = Version::parse(&expected_version).unwrap(); - let actual_version = Version::parse(actual_version).unwrap(); - match expected_version.cmp(&actual_version) { - std::cmp::Ordering::Less => { - warn!("Warning: {} using newer versions than expected version.", program_name); - Ok(()) - } - std::cmp::Ordering::Greater => { - error!("Error: Actual version is less than expected. Halting. Please install newer version."); - Err(eyre!("{} version is older than expected.!", program_name)) - } - std::cmp::Ordering::Equal => { - info!("{} versions match. Proceeding.", program_name); - Ok(()) - } - } -} - -pub fn get_distribution(config: PkgConfig, config_file_path: String) -> Result { - let path = Path::new(&config_file_path); - let config_file_path = fs::canonicalize(path)?; - let config_root = config_file_path - .parent() - .unwrap() - .to_str() - .unwrap() - .to_string(); - Ok(DistributionPackager::new(config, config_root)) -} - - -pub fn get_config_file(config: Option, config_file_name: &str) -> Result { - return if let Some(location) = config { - let path = Path::new(&location); - if !path.exists() { - return Err(eyre!("Directory or file does not exist {}", location)); - } - if path.is_dir() { - let config_file = path.join(config_file_name); - if config_file.exists() { - return Ok(config_file.to_str().unwrap().to_string()); - } - return Err(eyre!("Could not find {} in dir: {}", config_file_name, path.to_str().unwrap())); - } - Ok(location) - } else { - let path = env::current_dir().unwrap(); - let config_file = path.join(config_file_name); - if config_file.exists() { - return Ok(config_file.to_str().unwrap().to_string()); - } - Err(eyre!("Could not find {} in current directory.", config_file_name)) - }; -} \ No newline at end of file diff --git a/src/v1/mod.rs b/src/v1/mod.rs deleted file mode 100644 index 7c8ad0cf..00000000 --- a/src/v1/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -// src/v1/mod.rs -pub mod cli; -pub mod build; -pub mod packager; -mod args; -pub mod pkg_config; -pub mod pkg_config_verify; diff --git a/src/v1/packager.rs b/src/v1/packager.rs deleted file mode 100644 index e0f6e16d..00000000 --- a/src/v1/packager.rs +++ /dev/null @@ -1,175 +0,0 @@ -use eyre::{eyre, Result}; -use crate::v1::build::sbuild_packager::SbuildPackager; - - -use crate::v1::pkg_config::PkgConfig; -use crate::v1::pkg_config_verify::PkgVerifyConfig; - - -pub trait Packager { - type BuildEnv: BackendBuildEnv; - fn new(config: PkgConfig, config_root: String) -> Self; - fn package(&self) -> Result<()>; - fn get_build_env(&self) -> Result; -} - -pub struct DistributionPackager { - config: PkgConfig, - config_root: String, -} - -pub trait BackendBuildEnv { - fn clean(&self) -> Result<()>; - fn create(&self) -> Result<()>; - fn package(&self) -> Result<()>; - - fn verify(&self, verify_config: PkgVerifyConfig) -> Result<()>; - - fn run_lintian(&self) -> Result<()>; - fn run_piuparts(&self) -> Result<()>; - fn run_autopkgtests(&self) -> Result<()>; -} - -impl DistributionPackager { - pub fn new(config: PkgConfig, config_root: String) -> Self { - DistributionPackager { - config, - config_root, - } - } - pub fn package(&self) -> Result<()> { - let config = self.config.clone(); - - match self.config.build_env.codename.clone().as_str() { - "bookworm" | "noble numbat" | "jammy jellyfish" => { - let packager = SbuildPackager::new(config, self.config_root.clone()); - packager.package()?; - } - invalid_codename => { - return Err(eyre!(format!( - "Invalid codename '{}' specified", - invalid_codename - ))); - } - } - Ok(()) - } - pub fn run_lintian(&self) -> Result<()> { - let config = self.config.clone(); - - match self.config.build_env.codename.clone().as_str() { - "bookworm" | "noble numbat" | "jammy jellyfish" => { - let packager = SbuildPackager::new(config, self.config_root.clone()); - let build_env = packager.get_build_env()?; - build_env.run_lintian()?; - } - invalid_codename => { - return Err(eyre!(format!( - "Invalid codename '{}' specified", - invalid_codename - ))); - } - } - Ok(()) - } - pub fn run_piuparts(&self) -> Result<()> { - let config = self.config.clone(); - - match self.config.build_env.codename.clone().as_str() { - "bookworm" | "noble numbat" | "jammy jellyfish" => { - let packager = SbuildPackager::new(config, self.config_root.clone()); - let build_env = packager.get_build_env()?; - build_env.run_piuparts()?; - } - invalid_codename => { - return Err(eyre!(format!( - "Invalid codename '{}' specified", - invalid_codename - ))); - } - } - Ok(()) - } - pub fn run_autopkgtests(&self) -> Result<()> { - let config = self.config.clone(); - - match self.config.build_env.codename.clone().as_str() { - "bookworm" | "noble numbat" | "jammy jellyfish" => { - let packager = SbuildPackager::new(config, self.config_root.clone()); - let build_env = packager.get_build_env()?; - build_env.run_autopkgtests()?; - } - invalid_codename => { - return Err(eyre!(format!( - "Invalid codename '{}' specified", - invalid_codename - ))); - } - } - Ok(()) - } - pub fn clean_build_env(&self) -> Result<()> { - let config = self.config.clone(); - - match self.config.build_env.codename.clone().as_str() { - "bookworm" | "noble numbat" | "jammy jellyfish" => { - let packager = SbuildPackager::new(config, self.config_root.clone()); - - let build_env = packager.get_build_env()?; - build_env.clean()?; - } - invalid_codename => { - return Err(eyre!(format!( - "Invalid codename '{}' specified", - invalid_codename - ))); - } - } - Ok(()) - } - pub fn create_build_env(&self) -> Result<()> { - let config = self.config.clone(); - - match self.config.build_env.codename.clone().as_str() { - "bookworm" | "noble numbat" | "jammy jellyfish" => { - let packager = SbuildPackager::new(config, self.config_root.clone()); - let build_env = packager.get_build_env()?; - build_env.create()?; - } - invalid_codename => { - return Err(eyre!(format!( - "Invalid codename '{}' specified", - invalid_codename - ))); - } - } - Ok(()) - } - - pub fn verify(&self, verify_config: PkgVerifyConfig, package: bool) -> Result<()> { - let config = self.config.clone(); - - match self.config.build_env.codename.clone().as_str() { - "bookworm" | "noble numbat" | "jammy jellyfish" => { - let mut config = config.clone(); - config.build_env.run_autopkgtest = Some(false); - config.build_env.run_lintian = Some(false); - config.build_env.run_piuparts = Some(false); - let packager = SbuildPackager::new(config, self.config_root.clone()); - if package { - packager.package()?; - } - let build_env = packager.get_build_env()?; - // files to verify - build_env.verify(verify_config)?; - } - invalid_codename => { - return Err(eyre!(format!( - "Invalid codename '{}' specified", - invalid_codename - ))); - } - } - Ok(()) - } -} diff --git a/src/v1/pkg_config.rs b/src/v1/pkg_config.rs deleted file mode 100644 index ab994f3f..00000000 --- a/src/v1/pkg_config.rs +++ /dev/null @@ -1,951 +0,0 @@ -use std::fs; -use std::path::Path; -use eyre::{eyre, Report, Result}; -use serde::{Deserialize, Deserializer}; -use std::str::FromStr; -use serde::de::DeserializeOwned; - -pub fn deserialize_option_empty_string<'de, T, D>(deserializer: D) -> Result, D::Error> - where - T: FromStr, - T::Err: std::fmt::Display, - D: Deserializer<'de>, -{ - let s: String = Deserialize::deserialize(deserializer)?; - if s.is_empty() { - Ok(None) - } else { - T::from_str(&s).map(Some).map_err(serde::de::Error::custom) - } -} - -pub trait Validation { - fn validate(&self) -> Result<(), Vec>; -} - -pub fn validate_not_empty(name: &str, value: &str) -> Result<()> { - if value.trim().is_empty() { - return Err(eyre!("field: {} cannot be empty", name)); - } - Ok(()) -} - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -pub struct RustConfig { - pub rust_version: String, - pub rust_binary_url: String, - pub rust_binary_gpg_asc: String, -} - -impl Validation for RustConfig { - fn validate(&self) -> Result<(), Vec> { - let mut errors = Vec::new(); - - if let Err(err) = validate_not_empty("rust_version", &self.rust_version) { - errors.push(err); - } - - if let Err(err) = validate_not_empty("rust_binary_url", &self.rust_binary_url) { - errors.push(err); - } - - if let Err(err) = validate_not_empty("rust_binary_gpg_asc", &self.rust_binary_gpg_asc) { - errors.push(err); - } - - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -pub struct GoConfig { - pub go_version: String, - pub go_binary_url: String, - pub go_binary_checksum: String, -} - -impl Validation for GoConfig { - fn validate(&self) -> Result<(), Vec> { - let mut errors = Vec::new(); - - if let Err(err) = validate_not_empty("go_version", &self.go_version) { - errors.push(err); - } - - if let Err(err) = validate_not_empty("go_binary_url", &self.go_binary_url) { - errors.push(err); - } - - if let Err(err) = validate_not_empty("go_binary_checksum", &self.go_binary_checksum) { - errors.push(err); - } - - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -pub struct JavascriptConfig { - pub node_version: String, - pub node_binary_url: String, - pub node_binary_checksum: String, - pub yarn_version: Option, -} - -impl Validation for JavascriptConfig { - fn validate(&self) -> Result<(), Vec> { - let mut errors = Vec::new(); - - if let Err(err) = validate_not_empty("node_version", &self.node_version) { - errors.push(err); - } - if let Err(err) = validate_not_empty("node_binary_url", &self.node_binary_url) { - errors.push(err); - } - if let Err(err) = validate_not_empty("node_binary_checksum", &self.node_binary_checksum) { - errors.push(err); - } - if let Some(yarn_version) = &self.yarn_version { - if let Err(err) = validate_not_empty("yarn_version", yarn_version) { - errors.push(err); - } - } - - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -pub struct GradleConfig { - pub gradle_version: String, - pub gradle_binary_url: String, - pub gradle_binary_checksum: String, -} - -impl Validation for GradleConfig { - fn validate(&self) -> Result<(), Vec> { - let mut errors = Vec::new(); - - if let Err(err) = validate_not_empty("gradle_version", &self.gradle_version) { - errors.push(err); - } - if let Err(err) = validate_not_empty("gradle_binary_url", &self.gradle_binary_url) { - errors.push(err); - } - if let Err(err) = validate_not_empty("gradle_binary_checksum", &self.gradle_binary_checksum) { - errors.push(err); - } - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -pub struct JavaConfig { - pub is_oracle: bool, - pub jdk_version: String, - pub jdk_binary_url: String, - pub jdk_binary_checksum: String, - pub gradle: Option, -} - -impl Validation for JavaConfig { - fn validate(&self) -> Result<(), Vec> { - let mut errors = Vec::new(); - - if let Err(err) = validate_not_empty("jdk_version", &self.jdk_version) { - errors.push(err); - } - if let Err(err) = validate_not_empty("jdk_binary_url", &self.jdk_binary_url) { - errors.push(err); - } - if let Err(err) = validate_not_empty("jdk_binary_checksum", &self.jdk_binary_checksum) { - errors.push(err); - } - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -pub struct DotnetPackage { - pub name: String, - pub hash: String, - pub url: String, -} - -impl Validation for DotnetPackage { - fn validate(&self) -> Result<(), Vec> { - let mut errors = Vec::new(); - - if let Err(err) = validate_not_empty("name", &self.name) { - errors.push(err); - } - - if let Err(err) = validate_not_empty("hash", &self.hash) { - errors.push(err); - } - - if let Err(err) = validate_not_empty("url", &self.url) { - errors.push(err); - } - - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -pub struct DotnetConfig { - pub use_backup_version: bool, - pub dotnet_packages: Vec, - pub deps: Option>, -} - -impl Validation for DotnetConfig { - fn validate(&self) -> Result<(), Vec> { - let errors = Vec::new(); - - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -pub struct NimConfig { - pub nim_version: String, - pub nim_binary_url: String, - pub nim_version_checksum: String, -} - -impl Validation for NimConfig { - fn validate(&self) -> Result<(), Vec> { - let mut errors = Vec::new(); - - if let Err(err) = validate_not_empty("nim_version", &self.nim_version) { - errors.push(err); - } - if let Err(err) = validate_not_empty("nim_binary_url", &self.nim_binary_url) { - errors.push(err); - } - if let Err(err) = validate_not_empty("nim_version_checksum", &self.nim_version_checksum) { - errors.push(err); - } - - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -#[serde(tag = "language_env", rename_all = "lowercase")] -pub enum LanguageEnv { - Rust(RustConfig), - Go(GoConfig), - JavaScript(JavascriptConfig), - Java(JavaConfig), - Dotnet(DotnetConfig), - TypeScript(JavascriptConfig), - Nim(NimConfig), - #[default] - C, - Python, -} - -impl Validation for LanguageEnv { - fn validate(&self) -> Result<(), Vec> { - match self { - LanguageEnv::Rust(config) => config.validate(), - LanguageEnv::Go(config) => config.validate(), - LanguageEnv::JavaScript(config) => config.validate(), - LanguageEnv::Java(config) => config.validate(), - LanguageEnv::Dotnet(config) => config.validate(), - LanguageEnv::TypeScript(config) => config.validate(), - LanguageEnv::Nim(config) => config.validate(), - LanguageEnv::C => Ok(()), - LanguageEnv::Python => Ok(()), - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -pub struct DefaultPackageTypeConfig { - pub tarball_url: String, - pub tarball_hash: Option, - pub language_env: LanguageEnv, -} - -impl Validation for DefaultPackageTypeConfig { - fn validate(&self) -> Result<(), Vec> { - let mut errors = Vec::new(); - - if let Err(err) = validate_not_empty("tarball_url", &self.tarball_url) { - errors.push(err); - } - if let Some(value) = &self.tarball_hash { - if let Err(err) = validate_not_empty("tarball_hash", value) { - errors.push(err); - } - } - let language_errors = self.language_env.validate(); - - if let Err(mut language_errors) = language_errors { - errors.append(&mut language_errors); - } - - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -pub struct SubModule { - pub commit: String, - pub path: String, -} - -impl Validation for SubModule { - fn validate(&self) -> Result<(), Vec> { - let mut errors = Vec::new(); - - if let Err(err) = validate_not_empty("commit", &self.commit) { - errors.push(err); - } - - if let Err(err) = validate_not_empty("path", &self.path) { - errors.push(err); - } - - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -pub struct GitPackageTypeConfig { - pub git_tag: String, - pub git_url: String, - pub submodules: Vec, - pub language_env: LanguageEnv, -} - -impl Validation for GitPackageTypeConfig { - fn validate(&self) -> Result<(), Vec> { - let mut errors = Vec::new(); - - if let Err(err) = validate_not_empty("git_tag", &self.git_tag) { - errors.push(err); - } - - if let Err(err) = validate_not_empty("git_url", &self.git_url) { - errors.push(err); - } - - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -#[serde(tag = "package_type", rename_all = "lowercase")] -pub enum PackageType { - Default(DefaultPackageTypeConfig), - Git(GitPackageTypeConfig), - #[default] - Virtual, -} - -impl Validation for PackageType { - fn validate(&self) -> Result<(), Vec> { - match self { - PackageType::Default(config) => config.validate(), - PackageType::Git(config) => config.validate(), - PackageType::Virtual => Ok(()), - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Default, Clone)] -pub struct PackageFields { - pub spec_file: String, - pub package_name: String, - pub version_number: String, - pub revision_number: String, - pub homepage: String, -} - -impl Validation for PackageFields { - fn validate(&self) -> Result<(), Vec> { - let mut errors = Vec::new(); - - if let Err(err) = validate_not_empty("spec_file", &self.spec_file) { - errors.push(err); - } - if let Err(err) = validate_not_empty("package_name", &self.package_name) { - errors.push(err); - } - if let Err(err) = validate_not_empty("version_number", &self.version_number) { - errors.push(err); - } - if let Err(err) = validate_not_empty("revision_number", &self.revision_number) { - errors.push(err); - } - if let Err(err) = validate_not_empty("homepage", &self.homepage) { - errors.push(err); - } - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Default, Clone)] -pub struct BuildEnv { - pub codename: String, - pub arch: String, - pub pkg_builder_version: String, - pub debcrafter_version: String, - pub sbuild_cache_dir: Option, - pub docker: Option, - pub run_lintian: Option, - pub run_piuparts: Option, - pub run_autopkgtest: Option, - pub lintian_version: String, - pub piuparts_version: String, - pub autopkgtest_version: String, - pub sbuild_version: String, - #[serde(deserialize_with = "deserialize_option_empty_string")] - pub workdir: Option, -} - -impl Validation for BuildEnv { - fn validate(&self) -> Result<(), Vec> { - let mut errors = Vec::new(); - - if let Err(err) = validate_not_empty("codename", &self.codename) { - errors.push(err); - } - if let Err(err) = validate_not_empty("arch", &self.arch) { - errors.push(err); - } - if let Err(err) = validate_not_empty("pkg_builder_version", &self.pkg_builder_version) { - errors.push(err); - } - - if let Err(err) = validate_not_empty("debcrafter_version", &self.debcrafter_version) { - errors.push(err); - } - if let Err(err) = validate_not_empty("lintian_version", &self.lintian_version) { - errors.push(err); - } - if let Err(err) = validate_not_empty("piuparts_version", &self.piuparts_version) { - errors.push(err); - } - if let Err(err) = validate_not_empty("autopkgtest_version", &self.autopkgtest_version) { - errors.push(err); - } - if let Err(err) = validate_not_empty("sbuild_version", &self.sbuild_version) { - errors.push(err); - } - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -pub struct PkgConfig { - pub package_fields: PackageFields, - pub package_type: PackageType, - pub build_env: BuildEnv, -} - -impl Validation for PkgConfig { - fn validate(&self) -> Result<(), Vec> { - let mut errors = Vec::new(); - let package_field_errors = self.package_fields.validate(); - let package_type_errors = self.package_type.validate(); - let build_env_errors = self.build_env.validate(); - if let Err(mut package_field_errors) = package_field_errors { - errors.append(&mut package_field_errors); - } - - if let Err(mut package_type_errors) = package_type_errors { - errors.append(&mut package_type_errors); - } - - if let Err(mut build_env_errors) = build_env_errors { - errors.append(&mut build_env_errors); - } - - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - -pub fn parse(config_str: &str) -> Result - where - T: Validation + DeserializeOwned, -{ - let configuration = toml::from_str::(config_str)?; - configuration - .validate() - .map_err(|errors| eyre!("Validation failed: {:?}", errors))?; - Ok(configuration) -} - -pub fn read_config(path: &Path) -> Result - where - T: Validation + DeserializeOwned, -{ - let toml_content = fs::read_to_string(path)?; - - let config: T = - parse(&toml_content)?; - - - Ok(config) -} - -pub fn get_config(config_file: String) -> Result - where - T: Validation + DeserializeOwned, -{ - let path = Path::new(&config_file); - read_config(path) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_full_config() { - let config_str = r#" -[package_fields] -spec_file = "hello-world.sss" -package_name = "hello-world" -version_number = "1.0.0" -revision_number = "1" -homepage="https://github.com/eth-pkg/pkg-builder#examples" - -[package_type] -package_type="default" -tarball_url = "hello-world-1.0.0.tar.gz" -git_source = "" -git_commit="" - -[package_type.language_env] -language_env = "rust" -rust_version = "1.22" -rust_binary_url = "http:://example.com" -rust_binary_gpg_asc = "binary_key" -go_version = "1.22" - - -[build_env] -codename="bookworm" -arch = "amd64" -pkg_builder_version="0.2.11" -debcrafter_version = "8189263" -run_lintian=false -run_piuparts=false -run_autopkgtest=false -lintian_version="2.116.3" -piuparts_version="1.1.7" -autopkgtest_version="5.28" -sbuild_version="0.85.6" -workdir="~/.pkg-builder/packages/jammy" -"#; - let config = PkgConfig { - package_fields: PackageFields { - spec_file: "hello-world.sss".to_string(), - package_name: "hello-world".to_string(), - version_number: "1.0.0".to_string(), - revision_number: "1".to_string(), - homepage: "https://github.com/eth-pkg/pkg-builder#examples".to_string(), - }, - package_type: PackageType::Default(DefaultPackageTypeConfig { - tarball_url: "hello-world-1.0.0.tar.gz".to_string(), - tarball_hash: None, - language_env: LanguageEnv::Rust(RustConfig { - rust_version: "1.22".to_string(), - rust_binary_url: "http:://example.com".to_string(), - rust_binary_gpg_asc: "binary_key".to_string(), - }), - }), - build_env: BuildEnv { - codename: "bookworm".to_string(), - arch: "amd64".to_string(), - pkg_builder_version: "0.2.11".to_string(), - debcrafter_version: "8189263".to_string(), - sbuild_cache_dir: None, - docker: None, - run_lintian: Some(false), - run_piuparts: Some(false), - run_autopkgtest: Some(false), - lintian_version: "2.116.3".to_string(), - piuparts_version: "1.1.7".to_string(), - autopkgtest_version: "5.28".to_string(), - sbuild_version: "0.85.6".to_string(), - workdir: Some("~/.pkg-builder/packages/jammy".to_string()), - }, - }; - assert_eq!(parse::(config_str).unwrap(), config); - } - - #[test] - fn test_empty_strings_are_error_rust_config() { - let config = RustConfig::default(); - match config.validate() { - Err(validation_errors) => { - let expected_errors = [ - "field: rust_version cannot be empty", - "field: rust_binary_url cannot be empty", - "field: rust_binary_gpg_asc cannot be empty", - ]; - assert_eq!( - validation_errors.len(), - expected_errors.len(), - "Number of errors is different" - ); - for (actual, expected) in validation_errors.iter().zip(expected_errors.iter()) { - assert_eq!(actual.to_string(), *expected); - } - } - Ok(_) => panic!("Validation should have failed."), - } - } - - #[test] - fn test_empty_strings_are_error_go_config() { - let config = GoConfig::default(); - match config.validate() { - Err(validation_errors) => { - let expected_errors = [ - "field: go_version cannot be empty", - "field: go_binary_url cannot be empty", - "field: go_binary_checksum cannot be empty", - ]; - assert_eq!( - validation_errors.len(), - expected_errors.len(), - "Number of errors is different" - ); - for (actual, expected) in validation_errors.iter().zip(expected_errors.iter()) { - assert_eq!(actual.to_string(), *expected); - } - } - Ok(_) => panic!("Validation should have failed."), - } - } - - #[test] - fn test_empty_strings_are_error_javascript_config() { - let config = JavascriptConfig::default(); - match config.validate() { - Err(validation_errors) => { - let expected_errors = [ - "field: node_version cannot be empty", - "field: node_binary_url cannot be empty", - "field: node_binary_checksum cannot be empty", - ]; - assert_eq!( - validation_errors.len(), - expected_errors.len(), - "Number of errors is different" - ); - for (actual, expected) in validation_errors.iter().zip(expected_errors.iter()) { - assert_eq!(actual.to_string(), *expected); - } - } - Ok(_) => panic!("Validation should have failed."), - } - } - - #[test] - fn test_empty_strings_are_error_java_config() { - let config = JavaConfig::default(); - match config.validate() { - Err(validation_errors) => { - let expected_errors = [ - "field: jdk_version cannot be empty", - "field: jdk_binary_url cannot be empty", - "field: jdk_binary_checksum cannot be empty", - ]; - assert_eq!( - validation_errors.len(), - expected_errors.len(), - "Number of errors is different" - ); - for (actual, expected) in validation_errors.iter().zip(expected_errors.iter()) { - assert_eq!(actual.to_string(), *expected); - } - } - Ok(_) => panic!("Validation should have failed."), - } - } - - // #[test] - // fn test_empty_strings_are_error_dotnet_config() { - // let config = DotnetConfig::default(); - // match config.validate() { - // Err(validation_errors) => { - // let expected_errors: Vec= vec![]; - // assert_eq!( - // validation_errors.len(), - // expected_errors.len(), - // "Number of errors is different" - // ); - // for (actual, expected) in validation_errors.iter().zip(expected_errors.iter()) { - // assert_eq!(actual.to_string(), *expected); - // } - // } - // Ok(_) => panic!("Validation should have failed."), - // } - // } - - // #[test] - // fn test_empty_strings_are_error_typescript_config() { - // let config = TypescriptConfig { - // node_version: "".to_string(), - // yarn_version: "".to_string(), - // }; - // match config.validate() { - // Err(validation_errors) => { - // let expected_errors = [ - // "field: node_version cannot be empty", - // "field: yarn_version cannot be empty", - // ]; - // assert_eq!( - // validation_errors.len(), - // expected_errors.len(), - // "Number of errors is different" - // ); - // for (actual, expected) in validation_errors.iter().zip(expected_errors.iter()) { - // assert_eq!(actual.to_string(), *expected); - // } - // } - // Ok(_) => panic!("Validation should have failed."), - // } - // } - - #[test] - fn test_empty_strings_are_error_nim_config() { - let config = NimConfig::default(); - match config.validate() { - Err(validation_errors) => { - let expected_errors = [ - "field: nim_version cannot be empty", - "field: nim_binary_url cannot be empty", - "field: nim_version_checksum cannot be empty", - ]; - assert_eq!( - validation_errors.len(), - expected_errors.len(), - "Number of errors is different" - ); - for (actual, expected) in validation_errors.iter().zip(expected_errors.iter()) { - assert_eq!(actual.to_string(), *expected); - } - } - Ok(_) => panic!("Validation should have failed."), - } - } - - #[test] - fn test_empty_strings_are_error_default_package_type_config() { - let config = DefaultPackageTypeConfig::default(); - match config.validate() { - Err(validation_errors) => { - let expected_errors = [ - "field: tarball_url cannot be empty", - ]; - assert_eq!( - validation_errors.len(), - expected_errors.len(), - "Number of errors is different" - ); - for (actual, expected) in validation_errors.iter().zip(expected_errors.iter()) { - assert_eq!(actual.to_string(), *expected); - } - } - Ok(_) => panic!("Validation should have failed."), - } - } - - #[test] - fn test_empty_strings_are_error_git_package_type_config() { - let config = GitPackageTypeConfig::default(); - match config.validate() { - Err(validation_errors) => { - let expected_errors = [ - "field: git_tag cannot be empty", - "field: git_url cannot be empty", - ]; - assert_eq!( - validation_errors.len(), - expected_errors.len(), - "Number of errors is different" - ); - for (actual, expected) in validation_errors.iter().zip(expected_errors.iter()) { - assert_eq!(actual.to_string(), *expected); - } - } - Ok(_) => panic!("Validation should have failed."), - } - } - - #[test] - fn test_empty_strings_are_error_gradle_config() { - let config = GradleConfig::default(); - match config.validate() { - Err(validation_errors) => { - let expected_errors = [ - "field: gradle_version cannot be empty", - "field: gradle_binary_url cannot be empty", - "field: gradle_binary_checksum cannot be empty", - ]; - assert_eq!( - validation_errors.len(), - expected_errors.len(), - "Number of errors is different" - ); - for (actual, expected) in validation_errors.iter().zip(expected_errors.iter()) { - assert_eq!(actual.to_string(), *expected); - } - } - Ok(_) => panic!("Validation should have failed."), - } - } - - #[test] - fn test_empty_strings_are_error_package_fields() { - let config = PackageFields::default(); - match config.validate() { - Err(validation_errors) => { - let expected_errors = [ - "field: spec_file cannot be empty", - "field: package_name cannot be empty", - "field: version_number cannot be empty", - "field: revision_number cannot be empty", - "field: homepage cannot be empty", - ]; - assert_eq!( - validation_errors.len(), - expected_errors.len(), - "Number of errors is different" - ); - for (actual, expected) in validation_errors.iter().zip(expected_errors.iter()) { - assert_eq!(actual.to_string(), *expected); - } - } - Ok(_) => panic!("Validation should have failed."), - } - } - - #[test] - fn test_empty_strings_are_error_build_env() { - let config = BuildEnv::default(); - match config.validate() { - Err(validation_errors) => { - let expected_errors = [ - "field: codename cannot be empty", - "field: arch cannot be empty", - "field: pkg_builder_version cannot be empty", - "field: debcrafter_version cannot be empty", - "field: lintian_version cannot be empty", - "field: piuparts_version cannot be empty", - "field: autopkgtest_version cannot be empty", - "field: sbuild_version cannot be empty", - ]; - assert_eq!( - validation_errors.len(), - expected_errors.len(), - "Number of errors is different" - ); - for (actual, expected) in validation_errors.iter().zip(expected_errors.iter()) { - assert_eq!(actual.to_string(), *expected); - } - } - Ok(_) => panic!("Validation should have failed."), - } - } - - #[test] - fn test_validate_with_all_empty_values_pkg_config() { - let config = PkgConfig::default(); - match config.validate() { - Err(validation_errors) => { - let expected_errors = [ - "field: spec_file cannot be empty", - "field: package_name cannot be empty", - "field: version_number cannot be empty", - "field: revision_number cannot be empty", - "field: homepage cannot be empty", - "field: codename cannot be empty", - "field: arch cannot be empty", - "field: pkg_builder_version cannot be empty", - "field: debcrafter_version cannot be empty", - "field: lintian_version cannot be empty", - "field: piuparts_version cannot be empty", - "field: autopkgtest_version cannot be empty", - "field: sbuild_version cannot be empty", - ]; - assert_eq!( - validation_errors.len(), - expected_errors.len(), - "Number of errors is different" - ); - for (actual, expected) in validation_errors.iter().zip(expected_errors.iter()) { - assert_eq!(actual.to_string(), *expected); - } - } - Ok(_) => panic!("Validation should have failed."), - } - } -} diff --git a/src/v1/pkg_config_verify.rs b/src/v1/pkg_config_verify.rs deleted file mode 100644 index 7a525b5c..00000000 --- a/src/v1/pkg_config_verify.rs +++ /dev/null @@ -1,68 +0,0 @@ -use eyre::{eyre, Report}; -use serde::Deserialize; -use crate::v1::pkg_config::{validate_not_empty, Validation}; - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -pub struct PackageHash { - pub name: String, - pub hash: String, -} - -impl Validation for PackageHash { - fn validate(&self) -> eyre::Result<(), Vec> { - let mut errors = Vec::new(); - - if let Err(err) = validate_not_empty("name", &self.name) { - errors.push(err); - } - - if let Err(err) = validate_not_empty("hash", &self.hash) { - errors.push(err); - } - - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -pub struct VerifyConfig { - pub package_hash: Vec, -} - -impl Validation for VerifyConfig { - fn validate(&self) -> eyre::Result<(), Vec> { - if self.package_hash.is_empty() { - let err = vec![eyre!("package_hash cannot be empty")]; - Err(err) - } else { - let mut errors = Vec::new(); - for packagehash in self.package_hash.iter() { - if let Err(mut err) = packagehash.validate() { - if !err.is_empty() { - errors.append(&mut err); - } - } - } - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Clone, Default)] -pub struct PkgVerifyConfig { - pub verify: VerifyConfig, -} - -impl Validation for PkgVerifyConfig { - fn validate(&self) -> eyre::Result<(), Vec> { - return self.verify.validate(); - } -} \ No newline at end of file diff --git a/workspace/cli/Cargo.toml b/workspace/cli/Cargo.toml new file mode 100644 index 00000000..56e259d0 --- /dev/null +++ b/workspace/cli/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "cli" +version = "0.3.0" +edition = "2024" + +[lib] +name = "cli" +path = "src/mod.rs" + +[dependencies] +types = { workspace = true} +packager_deb = { workspace = true} +clap = { workspace = true} +toml = { workspace = true} +serde = { workspace = true} +env_logger ={ workspace = true} +log = { workspace = true} +cargo_metadata = { workspace = true} +regex = { workspace = true} +thiserror = { workspace = true} +tempfile = { workspace = true } diff --git a/src/v1/args.rs b/workspace/cli/src/args.rs similarity index 99% rename from src/v1/args.rs rename to workspace/cli/src/args.rs index 1f21082c..f9d25b08 100644 --- a/src/v1/args.rs +++ b/workspace/cli/src/args.rs @@ -23,7 +23,7 @@ pub enum ActionType { /// Verify package against hashes, it also rebuilds the package Verify(VerifyConfig), // pkg-builder version - Version + Version, } #[derive(Debug, Args)] diff --git a/workspace/cli/src/cli.rs b/workspace/cli/src/cli.rs new file mode 100644 index 00000000..7eb3c9ef --- /dev/null +++ b/workspace/cli/src/cli.rs @@ -0,0 +1,85 @@ +use super::args::{ActionType, BuildEnvSubCommand, PkgBuilderArgs}; +use clap::Parser; +use env_logger::Env; +use log::error; +use packager_deb::handler::PackageError; +use packager_deb::handler::dispatch_package_operation; +use std::env; +use thiserror::Error; +use types::config::Config; +use types::config::ConfigError; +use types::config::ConfigFile; +use types::debian::DebCommandPayload; + +#[derive(Error, Debug)] +pub enum PkgBuilderError { + #[error(transparent)] + PackageError(#[from] PackageError), + + #[error(transparent)] + ConfigError(#[from] ConfigError), +} + +type Result = std::result::Result; + +pub fn run_cli() -> Result<()> { + env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + + let args = PkgBuilderArgs::parse(); + let program_version: &str = env!("CARGO_PKG_VERSION"); + + if let ActionType::Version = &args.action { + let program_name: &str = env!("CARGO_PKG_NAME"); + + println!("{} version: {}", program_name, program_version); + return Ok(()); + } + + let config_path = match &args.action { + ActionType::Verify(command) => command.config.clone(), + ActionType::Package(command) => command.config.clone(), + ActionType::Env(command) => match &command.build_env_sub_command { + BuildEnvSubCommand::Create(sub_command) => sub_command.config.clone(), + BuildEnvSubCommand::Clean(sub_command) => sub_command.config.clone(), + }, + ActionType::Piuparts(command) => command.config.clone(), + ActionType::Autopkgtest(command) => command.config.clone(), + ActionType::Lintian(command) => command.config.clone(), + ActionType::Version => None, // Special case already handled above + }; + let config_file = ConfigFile::::load(config_path)?; + let build_env = config_file + .clone() + .parse()? + .build_env + .validate_and_apply_defaults(program_version)?; + + let cmd_payload = match args.action { + ActionType::Verify(command) => Ok(DebCommandPayload::Verify { + verify_config: command.verify_config, + no_package: command.no_package, + }), + ActionType::Lintian(_) => Ok(DebCommandPayload::Lintian), + ActionType::Piuparts(_) => Ok(DebCommandPayload::Piuparts), + ActionType::Autopkgtest(_) => Ok(DebCommandPayload::Autopkgtest), + ActionType::Package(cmd) => Ok(DebCommandPayload::Package { + run_autopkgtest: cmd.run_autopkgtest, + run_piuparts: cmd.run_piuparts, + run_lintian: cmd.run_lintian, + }), + ActionType::Env(build_env_action) => match build_env_action.build_env_sub_command { + BuildEnvSubCommand::Create(_) => Ok(DebCommandPayload::EnvCreate), + BuildEnvSubCommand::Clean(_) => Ok(DebCommandPayload::EnvClean), + }, + ActionType::Version => Err("Version has no payload."), + }; + + match build_env.codename { + types::distribution::Distribution::Debian(_) + | types::distribution::Distribution::Ubuntu(_) => { + dispatch_package_operation(config_file, cmd_payload.unwrap())? + } + }; + + Ok(()) +} diff --git a/workspace/cli/src/mod.rs b/workspace/cli/src/mod.rs new file mode 100644 index 00000000..1029203e --- /dev/null +++ b/workspace/cli/src/mod.rs @@ -0,0 +1,2 @@ +mod args; +pub mod cli; diff --git a/workspace/debian/Cargo.lock b/workspace/debian/Cargo.lock new file mode 100644 index 00000000..d9d9997c --- /dev/null +++ b/workspace/debian/Cargo.lock @@ -0,0 +1,253 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "debian" +version = "0.1.0" +dependencies = [ + "eyre", + "log", + "shellexpand", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "once_cell" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "shellexpand" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b" +dependencies = [ + "dirs", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/workspace/debian/Cargo.toml b/workspace/debian/Cargo.toml new file mode 100644 index 00000000..f767728d --- /dev/null +++ b/workspace/debian/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "debian" +version = "0.1.0" +edition = "2021" + +[lib] +name = "debian" +path = "src/mod.rs" + +[dependencies] +types = { workspace = true } +log = { workspace = true } +shellexpand = { workspace = true } +thiserror = { workspace = true } +tempfile = { workspace = true } +serde = { workspace = true } +toml = { workspace = true } diff --git a/workspace/debian/src/autopkgtest.rs b/workspace/debian/src/autopkgtest.rs new file mode 100644 index 00000000..18ee9829 --- /dev/null +++ b/workspace/debian/src/autopkgtest.rs @@ -0,0 +1,359 @@ +use log::info; +use std::path::PathBuf; +use thiserror::Error; + +use super::execute::{execute_command, Execute, ExecuteError}; + +/// `Autopkgtest` provides a builder interface for autopkgtest commands. +/// +/// This struct implements the builder pattern to configure and execute +/// autopkgtest commands, which are used for testing Debian packages. +/// It allows configuring various options such as changes files, test setup, +/// virtualization options, and more. +/// +/// # Examples +/// +/// ``` +/// use debian::autopkgtest::Autopkgtest; +/// use crate::debian::execute::Execute; +/// use std::path::PathBuf; +/// +/// let result = Autopkgtest::new() +/// .changes_file("package.changes") +/// .apt_upgrade() +/// .setup_commands("apt-get install dependency") +/// .qemu(PathBuf::from("/path/to/image.img")) +/// .working_dir(&PathBuf::from("/tmp/working_dir")) +/// .execute(); +/// ``` +#[derive(Debug, Clone, Default)] +pub struct Autopkgtest { + changes_file: Option, + no_built_binaries: bool, + apt_upgrade: bool, + setup_commands: Vec, + qemu_image: Option, + dir: Option, +} + +/// Custom error type for autopkgtest operations +#[derive(Error, Debug)] +pub enum AutopkgtestError { + #[error("Failed to execute command: {0}")] + CommandExecutionError(#[from] ExecuteError), +} + +type Result = std::result::Result; + +/// Builder for Autopkgtest commands +impl Autopkgtest { + /// Creates a new instance of `Autopkgtest` with default configuration. + /// + /// All options are initialized to their default values: + /// - No changes file + /// - No built binaries flag disabled + /// - Apt upgrade flag disabled + /// - No setup commands + /// - No QEMU image + /// - No working directory + /// + /// # Returns + /// + /// A new `Autopkgtest` instance with default configuration. + pub fn new() -> Self { + Self::default() + } + + /// Specifies a changes file to be processed by autopkgtest. + /// + /// This sets the path to a .changes file that will be used by autopkgtest. + /// The .changes file contains information about the package to be tested. + /// + /// # Arguments + /// + /// * `file` - Path to the .changes file + /// + /// # Returns + /// + /// The updated builder instance. + pub fn changes_file(mut self, file: &str) -> Self { + self.changes_file = Some(file.to_string()); + self + } + + /// Adds the `--no-built-binaries` flag to the command. + /// + /// When this flag is set, autopkgtest will ignore any built binaries + /// that might be available and build everything from source. + /// + /// # Returns + /// + /// The updated builder instance. + pub fn no_built_binaries(mut self) -> Self { + self.no_built_binaries = true; + self + } + + /// Adds the `--apt-upgrade` flag to the command. + /// + /// When this flag is set, autopkgtest will run apt-get upgrade + /// before starting the tests. + /// + /// # Returns + /// + /// The updated builder instance. + pub fn apt_upgrade(mut self) -> Self { + self.apt_upgrade = true; + self + } + + /// Adds a setup command to the autopkgtest. + /// + /// This method allows specifying shell commands that will be run + /// in the test environment before the tests are executed. + /// + /// # Arguments + /// + /// * `command` - A shell command to run in the test environment + /// + /// # Returns + /// + /// The updated builder instance. + pub fn setup_commands(mut self, command: &str) -> Self { + self.setup_commands.push(command.to_string()); + self + } + + /// Specifies test dependencies that are not in Debian repositories. + /// + /// This is a convenience method that adds each dependency as a separate setup command. + /// It can be used to install dependencies that are not available in standard repositories. + /// + /// # Arguments + /// + /// * `deps` - A slice of strings representing the dependencies + /// + /// # Returns + /// + /// The updated builder instance. + pub fn test_deps_not_in_debian(mut self, deps: &[String]) -> Self { + for dep in deps { + self.setup_commands.push(dep.clone()); + } + self + } + + /// Configures the command to use QEMU with the specified image. + /// + /// This adds the necessary arguments to run the tests in a QEMU virtual machine + /// using the specified disk image. + /// + /// # Arguments + /// + /// * `image_path` - Path to the QEMU disk image + /// + /// # Returns + /// + /// The updated builder instance. + pub fn qemu(mut self, image_path: PathBuf) -> Self { + self.qemu_image = Some(image_path); + self + } + + /// Sets the working directory for the command execution. + /// + /// This specifies the directory from which the autopkgtest command + /// will be executed. + /// + /// # Arguments + /// + /// * `dir` - Path to the working directory + /// + /// # Returns + /// + /// The updated builder instance. + pub fn working_dir(mut self, dir: &PathBuf) -> Self { + self.dir = Some(dir.clone()); + self + } + + /// Builds the command arguments based on the configured options. + /// + /// This method constructs a vector of strings representing the + /// command-line arguments that will be passed to the autopkgtest command. + /// + /// # Returns + /// + /// A vector of strings containing the command arguments. + fn build_args(&self) -> Vec { + let mut args = Vec::new(); + + // Add changes file if provided + if let Some(file) = &self.changes_file { + args.push(file.clone()); + } + + // Add flags based on boolean options + if self.no_built_binaries { + args.push("--no-built-binaries".to_string()); + } + + if self.apt_upgrade { + args.push("--apt-upgrade".to_string()); + } + + // Add setup commands + for cmd in &self.setup_commands { + args.push(format!("--setup-commands={}", cmd)); + } + + // Add QEMU configuration if provided + if let Some(image) = &self.qemu_image { + args.push("--".to_string()); + args.push("qemu".to_string()); + args.push(image.display().to_string()); + } + + args + } +} + +/// Implementation of the `Execute` trait for `Autopkgtest`. +/// +/// This allows an `Autopkgtest` instance to be executed using the `execute()` method. +impl Execute for Autopkgtest { + type Error = AutopkgtestError; + /// Executes the autopkgtest command with the configured options. + /// + /// This method builds the command arguments and runs the autopkgtest command. + /// It logs the command being executed and returns an error if the execution fails. + /// + /// # Returns + /// + /// A `Result` indicating success or failure. If successful, the result is `Ok(())`. + /// If there's an error, it will be wrapped with additional context information. + fn execute(&self) -> Result<()> { + let args = self.build_args(); + let args_str = args.join(" "); + info!("Running: autopkgtest {}", args_str); + + execute_command("autopkgtest", &args, self.dir.as_deref())?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + #[test] + fn test_new() { + let autopkgtest = Autopkgtest::new(); + assert!(autopkgtest.changes_file.is_none()); + assert!(!autopkgtest.no_built_binaries); + assert!(!autopkgtest.apt_upgrade); + assert!(autopkgtest.setup_commands.is_empty()); + assert!(autopkgtest.qemu_image.is_none()); + assert!(autopkgtest.dir.is_none()); + } + + #[test] + fn test_changes_file() { + let autopkgtest = Autopkgtest::new().changes_file("test.changes"); + assert_eq!(autopkgtest.changes_file, Some("test.changes".to_string())); + } + + #[test] + fn test_no_built_binaries() { + let autopkgtest = Autopkgtest::new().no_built_binaries(); + assert!(autopkgtest.no_built_binaries); + } + + #[test] + fn test_apt_upgrade() { + let autopkgtest = Autopkgtest::new().apt_upgrade(); + assert!(autopkgtest.apt_upgrade); + } + + #[test] + fn test_setup_command() { + let autopkgtest = Autopkgtest::new() + .setup_commands("apt-get install foo") + .setup_commands("echo 'test'"); + + assert_eq!( + autopkgtest.setup_commands, + vec!["apt-get install foo".to_string(), "echo 'test'".to_string()] + ); + } + + #[test] + fn test_test_deps_not_in_debian() { + let deps = vec!["dep1".to_string(), "dep2".to_string()]; + let autopkgtest = Autopkgtest::new().test_deps_not_in_debian(&deps); + assert_eq!(autopkgtest.setup_commands, deps); + } + + #[test] + fn test_qemu() { + let autopkgtest = Autopkgtest::new().qemu(PathBuf::from("/path/to/image.img")); + assert_eq!( + autopkgtest.qemu_image, + Some(PathBuf::from("/path/to/image.img")) + ); + } + + #[test] + fn test_working_dir() { + let dir = PathBuf::from("/tmp/test"); + let autopkgtest = Autopkgtest::new().working_dir(&dir); + assert_eq!(autopkgtest.dir, Some(dir)); + } + + #[test] + fn test_build_args() { + let autopkgtest = Autopkgtest::new() + .changes_file("test.changes") + .no_built_binaries() + .apt_upgrade() + .setup_commands("apt-get install pkg1") + .setup_commands("apt-get install pkg2") + .qemu("/path/to/image.img".into()); + + let args = autopkgtest.build_args(); + + assert_eq!( + args, + vec![ + "test.changes", + "--no-built-binaries", + "--apt-upgrade", + "--setup-commands=apt-get install pkg1", + "--setup-commands=apt-get install pkg2", + "--", + "qemu", + "/path/to/image.img" + ] + ); + } + + #[test] + fn test_multiple_setup_commands() { + let autopkgtest = Autopkgtest::new() + .setup_commands("cmd1") + .setup_commands("cmd2") + .setup_commands("cmd3"); + + let args = autopkgtest.build_args(); + + assert_eq!( + args, + vec![ + "--setup-commands=cmd1", + "--setup-commands=cmd2", + "--setup-commands=cmd3" + ] + ); + } +} diff --git a/workspace/debian/src/autopkgtest_image.rs b/workspace/debian/src/autopkgtest_image.rs new file mode 100644 index 00000000..2c3f1fc1 --- /dev/null +++ b/workspace/debian/src/autopkgtest_image.rs @@ -0,0 +1,285 @@ +use crate::execute::ExecuteError; + +use super::execute::{execute_command_with_sudo, Execute}; +/// Provides functionality for building and managing Autopkgtest VM images +/// +/// This module contains structures and implementations for creating +/// virtual machine images compatible with autopkgtest for different +/// Linux distributions. +use log::info; +use std::path::{Path, PathBuf}; +use thiserror::Error; +use types::{config::Architecture, distribution::Distribution}; + +/// Custom error type for autopkgtest image building operations +#[derive(Error, Debug)] +pub enum AutopkgtestImageError { + #[error("Distribution not specified")] + MissingDistribution, + + #[error("Image path not specified")] + MissingImagePath, + + #[error("Unsupported or invalid distribution codename: {0}")] + UnsupportedDistribution(String), + + #[error("Failed to execute command: {0}")] + CommandExecutionError(#[from] ExecuteError), + + #[error("Work directory path error: {0}")] + PathError(String), +} + +// Type alias for Result with our custom error type +type Result = std::result::Result; + +trait BuildCommandProvider { + fn get_command(&self) -> &'static str; + fn get_formatted_codename(&self) -> String; +} +impl BuildCommandProvider for Distribution { + /// Returns the appropriate command for building an image for this distribution + /// + /// # Returns + /// * `&'static str` - The command to use for image creation + fn get_command(&self) -> &'static str { + match self { + Distribution::Debian(_) => "autopkgtest-build-qemu", + Distribution::Ubuntu(_) => "autopkgtest-buildvm-ubuntu-cloud", + } + } + + /// Returns the codename formatted as an argument for the build command + /// + /// # Returns + /// * `String` - The formatted codename argument + fn get_formatted_codename(&self) -> String { + match self { + Distribution::Debian(_) => self.as_short().to_string(), + Distribution::Ubuntu(_) => format!("--release={}", self.as_short()), + } + } +} + +/// Builder for creating Autopkgtest VM images +/// +/// Provides a fluent interface for configuring and building VM images +/// for different distributions, architectures, and repositories. +#[derive(Debug, Clone, Default)] +pub struct AutopkgtestImageBuilder { + /// The target Linux distribution + distribution: Option, + /// Path where the image will be created + image_path: Option, + /// Directory to use for temporary files during build + work_dir: Option, + /// Repository mirror URL to use for package installation + mirror: Option, + /// Target architecture for the VM image + arch: Option, +} + +impl AutopkgtestImageBuilder { + /// Creates a new empty builder instance + /// + /// # Returns + /// * `Self` - A new AutopkgtestImageBuilder + pub fn new() -> Self { + Self::default() + } + + /// Sets the distribution codename for the image + /// + /// # Arguments + /// * `codename` - The distribution codename (e.g., "bookworm", "noble") + /// + /// # Returns + /// * `Result` - Modified builder or error if codename is unsupported + pub fn codename(mut self, codename: &Distribution) -> Result { + // Assuming Distribution::from_codename has been modified to return our Result type + // or we're mapping the error here + self.distribution = Some(codename.clone()); + Ok(self) + } + + /// Sets the image output path based on cache directory, codename and architecture + /// + /// # Arguments + /// * `cache_dir` - Directory where the image will be stored + /// * `codename` - Distribution codename + /// * `arch` - Target architecture + /// + /// # Returns + /// * `Self` - Modified builder + pub fn image_path(mut self, cache_dir: &str, codename: &Distribution, arch: &Architecture) -> Self { + let image_name = format!("autopkgtest-{}-{}.img", codename.as_short(), arch); + let cache_dir = shellexpand::tilde(cache_dir).to_string(); + let image_path = Path::new(&cache_dir).join(&image_name); + self.image_path = Some(image_path.clone()); + self.work_dir = Some(image_path.parent().unwrap_or(Path::new("")).to_path_buf()); + self + } + + /// Sets the package repository mirror URL + /// + /// # Arguments + /// * `repo_url` - The URL of the package repository mirror + /// + /// # Returns + /// * `Self` - Modified builder + pub fn mirror(mut self, repo_url: &str) -> Self { + self.mirror = Some(repo_url.to_string()); + self + } + + /// Sets the target architecture for the VM image + /// + /// # Arguments + /// * `arch` - Architecture name (e.g., "amd64", "arm64") + /// + /// # Returns + /// * `Self` - Modified builder + pub fn arch(mut self, arch: &Architecture) -> Self { + self.arch = Some(arch.to_string()); + self + } + + /// Returns the configured image path if set + /// + /// # Returns + /// * `Option<&PathBuf>` - The image path or None if not set + pub fn get_image_path(&self) -> Option<&PathBuf> { + self.image_path.as_ref() + } + + /// Builds the command-line arguments for the image creation command + /// + /// # Returns + /// * `Result>` - The list of arguments or an error if configuration is incomplete + fn build_args(&self) -> Result> { + let mut args = Vec::new(); + + if let Some(dist) = &self.distribution { + args.push(dist.get_formatted_codename()); + } else { + return Err(AutopkgtestImageError::MissingDistribution); + } + + if let Some(mirror) = &self.mirror { + args.push(format!("--mirror={}", mirror)); + } + + if let Some(arch) = &self.arch { + args.push(format!("--arch={}", arch)); + } + + if let Some(Distribution::Ubuntu(_)) = &self.distribution { + args.push("-v".to_string()); + } + + if let Some(path) = &self.image_path { + if let Some(Distribution::Debian(_)) = &self.distribution { + args.push(path.to_string_lossy().to_string()); + } else { + args.push(format!("--timeout={}", 3600)); + args.push(format!("--ram-size={}", 2048)); + } + } else { + return Err(AutopkgtestImageError::MissingImagePath); + } + + Ok(args) + } +} + +/// Implementation of the Execute trait for AutopkgtestImageBuilder +/// +/// Allows the builder to be executed to create the VM image. +impl Execute for AutopkgtestImageBuilder { + type Error = AutopkgtestImageError; + /// Executes the VM image creation process + /// + /// # Returns + /// * `Result<()>` - Success or an error if the build fails + fn execute(&self) -> Result<()> { + let cmd = self + .distribution + .as_ref() + .ok_or(AutopkgtestImageError::MissingDistribution)? + .get_command(); + + let args = self.build_args()?; + + info!("Running: sudo -S {} {}", cmd, args.join(" ")); + + let result = execute_command_with_sudo(cmd, args.clone(), self.work_dir.as_deref()); + + // try again with sudo if the first attempt failed + // for some strange reason the first attempt fails, some of the times on ubuntu runners + if result.is_err(){ + execute_command_with_sudo(cmd, args, self.work_dir.as_deref())?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use types::{config::Architecture, distribution::Distribution}; + + /// Tests creation of Distribution from various codenames + #[test] + fn test_distribution_from_codename() { + assert!(matches!( + Distribution::from_codename("bookworm").unwrap(), + Distribution::Debian(_) + )); + + assert!(matches!( + Distribution::from_codename("noble").unwrap(), + Distribution::Ubuntu(_) + )); + + assert!(Distribution::from_codename("unsupported").is_err()); + } + + /// Tests generation of command-line arguments + #[test] + fn test_build_args() { + let builder = AutopkgtestImageBuilder::new() + .codename(&Distribution::bookworm()) + .unwrap() + .image_path("/tmp", &Distribution::bookworm(), &Architecture::Amd64) + .arch(&Architecture::Amd64) + .mirror("http://example.com/debian"); + + let args = builder.build_args().unwrap(); + assert!(args.contains(&"bookworm".to_string())); + assert!(args + .iter() + .any(|arg| arg.contains("/tmp/autopkgtest-bookworm-amd64.img"))); + assert!(args.contains(&"--arch=amd64".to_string())); + assert!(args.contains(&"--mirror=http://example.com/debian".to_string())); + } + + #[test] + fn test_noble_build_args() { + let builder = AutopkgtestImageBuilder::new() + .codename(&Distribution::noble()) + .unwrap() + .image_path("/tmp", &Distribution::noble(), &Architecture::Amd64) + .arch(&Architecture::Amd64) + .mirror("http://example.com/ubuntu"); + + let args = builder.build_args().unwrap(); + assert!(args.contains(&"--release=noble".to_string())); + assert!(!args.contains(&"--release=noble numbat".to_string())); + assert!(args + .iter() + .all(|arg| !arg.contains("/tmp/autopkgtest-noble-amd64.img"))); + assert!(args.contains(&"--arch=amd64".to_string())); + assert!(args.contains(&"--mirror=http://example.com/ubuntu".to_string())); + } +} diff --git a/workspace/debian/src/debcrafter.rs b/workspace/debian/src/debcrafter.rs new file mode 100644 index 00000000..c24c8240 --- /dev/null +++ b/workspace/debian/src/debcrafter.rs @@ -0,0 +1,314 @@ +use log::info; +use std::fs; +use std::io; +use std::path::Path; +use std::path::PathBuf; +use std::process::Command; +use tempfile::tempdir; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum DebcrafterCmdError { + #[error("Command not found: {0}")] + CommandNotFound(#[from] io::Error), + + #[error("Failed to execute command: {0}")] + CommandFailed(CommandError), + + #[error("File not found: {0}")] + FileNotFound(String), +} + +#[derive(Debug, Error)] +pub enum CommandError { + #[error("{0}")] + StringError(String), + #[error("{0}")] + IOError(#[from] io::Error), +} + +impl PartialEq for CommandError { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::StringError(a), Self::StringError(b)) => a == b, + (Self::IOError(_), Self::IOError(_)) => false, // IO errors aren't comparable + _ => false, + } + } +} + +impl From for CommandError { + fn from(err: String) -> Self { + CommandError::StringError(err) + } +} + +pub struct DebcrafterCmd { + version: String, +} + +impl DebcrafterCmd { + /// Creates a new DebcrafterCmd with the specified version + pub fn new(version: &str) -> Self { + Self { + version: version.to_string(), + } + } + + /// Checks if dpkg-parsechangelog is installed on the system + pub fn check_if_dpkg_parsechangelog_installed(&self) -> Result<(), DebcrafterCmdError> { + let mut cmd = Command::new("which"); + cmd.arg("dpkg-parsechangelog"); + + self.handle_command_execution( + &mut cmd, + "dpkg-parsechangelog is not installed, please install it.".to_string(), + ) + } + + /// Checks if the specified version of debcrafter is installed + pub fn check_if_installed(&self) -> Result<(), DebcrafterCmdError> { + let mut cmd = Command::new("which"); + cmd.arg(format!("debcrafter_{}", self.version)); + + self.handle_command_execution( + &mut cmd, + format!("debcrafter_{} is not installed", self.version), + ) + } + + /// Creates a debian directory using the specified specification file + pub fn create_debian_dir( + &self, + specification_file: &PathBuf, + target_dir: &PathBuf, + ) -> Result<(), DebcrafterCmdError> { + let debcrafter_dir = + tempdir().map_err(|e| DebcrafterCmdError::CommandFailed(e.to_string().into()))?; + + let spec_file_path = fs::canonicalize(specification_file).map_err(|_| { + DebcrafterCmdError::FileNotFound(format!( + "{:?} spec_file doesn't exist", + specification_file + )) + })?; + + if !spec_file_path.exists() { + return Err(DebcrafterCmdError::FileNotFound(format!( + "{:?} spec_file doesn't exist", + specification_file + ))); + } + + let spec_dir = spec_file_path.parent().ok_or_else(|| { + DebcrafterCmdError::CommandFailed("Invalid specification file path".to_string().into()) + })?; + + let spec_file_name = spec_file_path.file_name().ok_or_else(|| { + DebcrafterCmdError::CommandFailed("Invalid specification file name".to_string().into()) + })?; + + info!( + "Spec directory: {:?}", + spec_dir.to_str().unwrap_or_default() + ); + info!("Spec file: {:?}", spec_file_name); + info!("Debcrafter directory: {:?}", debcrafter_dir.path()); + + let mut cmd = Command::new(format!("debcrafter_{}", self.version)); + cmd.arg(spec_file_name) + .current_dir(spec_dir) + .arg(debcrafter_dir.path()); + + self.handle_command_execution(&mut cmd, "Debcrafter execution failed".to_string())?; + + if let Some(first_directory) = self.get_first_directory(debcrafter_dir.path()) { + let tmp_debian_dir = first_directory.join("debian"); + let dest_dir = Path::new(target_dir).join("debian"); + self.copy_dir_contents_recursive(&tmp_debian_dir, &dest_dir) + .map_err(|err| DebcrafterCmdError::CommandFailed(err.to_string().into()))?; + } else { + return Err(DebcrafterCmdError::CommandFailed( + "Unable to create debian dir: no output directory found" + .to_string() + .into(), + )); + } + + Ok(()) + } + + /// Recursively copies the contents of a directory to another location + fn copy_dir_contents_recursive(&self, src_dir: &Path, dest_dir: &Path) -> io::Result<()> { + info!( + "Copying directory: {:?} to {:?}", + src_dir.display(), + dest_dir.display() + ); + + if !src_dir.is_dir() { + return Err(io::Error::new( + io::ErrorKind::NotFound, + format!("Source path is not a directory: {}", src_dir.display()), + )); + } + + if !dest_dir.exists() { + fs::create_dir_all(dest_dir)?; + } + + for entry in fs::read_dir(src_dir)? { + let entry = entry?; + let src_path = entry.path(); + let dest_path = dest_dir.join(entry.file_name()); + + if src_path.is_dir() { + self.copy_dir_contents_recursive(&src_path, &dest_path)?; + } else { + fs::copy(&src_path, &dest_path)?; + } + } + + Ok(()) + } + + /// Handles command execution and processes errors + fn handle_command_execution( + &self, + cmd: &mut Command, + error_message: String, + ) -> Result<(), DebcrafterCmdError> { + let output = cmd + .output() + .map_err(|e| DebcrafterCmdError::CommandNotFound(e))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); + let detailed_error = if stderr.is_empty() { + error_message + } else { + format!("{}: {}", error_message, stderr) + }; + + return Err(DebcrafterCmdError::CommandFailed(detailed_error.into())); + } + + Ok(()) + } + + /// Gets the first directory in a given path + fn get_first_directory(&self, dir: &Path) -> Option { + if !dir.is_dir() { + return None; + } + + fs::read_dir(dir) + .ok()? + .filter_map(Result::ok) + .find(|entry| entry.path().is_dir()) + .map(|entry| entry.path()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::{NamedTempFile, TempDir}; + + #[test] + fn test_command_error_from_string() { + let error = CommandError::from("test error".to_string()); + assert_eq!(error, CommandError::StringError("test error".to_string())); + } + + #[test] + fn test_command_error_from_io_error() { + let io_error = io::Error::new(io::ErrorKind::NotFound, "io error"); + let error_kind = io_error.kind(); + let error = CommandError::from(io::Error::new(error_kind, "io error")); + assert!(matches!(error, CommandError::IOError(_))); + } + + #[test] + fn test_get_first_directory_none() { + // Create a temp file (not a directory) + let temp_file = NamedTempFile::new().unwrap(); + let path = temp_file.path(); + let cmd = DebcrafterCmd::new("test"); + + assert_eq!(cmd.get_first_directory(path), None); + } + + #[test] + fn test_get_first_directory_empty() { + // Create an empty temp directory + let temp_dir = TempDir::new().unwrap(); + let path = temp_dir.path(); + let cmd = DebcrafterCmd::new("test"); + + assert_eq!(cmd.get_first_directory(path), None); + } + + #[test] + fn test_get_first_directory_with_subdirectory() { + // Create a temp directory with a subdirectory + let temp_dir = TempDir::new().unwrap(); + let sub_dir_path = temp_dir.path().join("subdir"); + fs::create_dir(&sub_dir_path).unwrap(); + let cmd = DebcrafterCmd::new("test"); + + let result = cmd.get_first_directory(temp_dir.path()); + assert!(result.is_some()); + assert_eq!(result.unwrap(), sub_dir_path); + } + + #[test] + fn test_copy_dir_contents_recursive() { + // Create source directory structure + let src_dir = TempDir::new().unwrap(); + let src_file1_path = src_dir.path().join("file1.txt"); + let src_subdir_path = src_dir.path().join("subdir"); + let src_file2_path = src_subdir_path.join("file2.txt"); + + fs::create_dir(&src_subdir_path).unwrap(); + fs::write(&src_file1_path, b"test content 1").unwrap(); + fs::write(&src_file2_path, b"test content 2").unwrap(); + + // Create destination directory + let dest_dir = TempDir::new().unwrap(); + let cmd = DebcrafterCmd::new("test"); + + // Copy the directory contents + let result = cmd.copy_dir_contents_recursive(src_dir.path(), dest_dir.path()); + assert!(result.is_ok()); + + // Verify the destination has the same structure and content + let dest_file1_path = dest_dir.path().join("file1.txt"); + let dest_subdir_path = dest_dir.path().join("subdir"); + let dest_file2_path = dest_subdir_path.join("file2.txt"); + + assert!(dest_file1_path.exists()); + assert!(dest_subdir_path.exists()); + assert!(dest_file2_path.exists()); + + assert_eq!( + fs::read_to_string(&dest_file1_path).unwrap(), + "test content 1" + ); + assert_eq!( + fs::read_to_string(&dest_file2_path).unwrap(), + "test content 2" + ); + } + + #[test] + fn test_copy_dir_contents_recursive_source_not_dir() { + // Create a temp file (not a directory) + let temp_file = NamedTempFile::new().unwrap(); + let dest_dir = TempDir::new().unwrap(); + let cmd = DebcrafterCmd::new("test"); + + let result = cmd.copy_dir_contents_recursive(temp_file.path(), dest_dir.path()); + assert!(result.is_err()); + } +} diff --git a/workspace/debian/src/execute.rs b/workspace/debian/src/execute.rs new file mode 100644 index 00000000..c153bbb1 --- /dev/null +++ b/workspace/debian/src/execute.rs @@ -0,0 +1,87 @@ +use log::info; +use std::{ + ffi::OsStr, + io::{BufRead, BufReader}, + path::Path, + process::{Command, Stdio}, +}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ExecuteError { + #[error("Command execution failed: {0}")] + CommandFailed(#[from] std::io::Error), + + #[error("Failed to change working directory: {0}")] + WorkingDirectoryError(String), + + #[error("Command '{0}' failed with status: {1}")] + CommandStatusError(String, i32), +} + +pub trait Execute { + type Error; + fn execute(&self) -> Result<(), Self::Error>; +} + +pub fn execute_command(cmd: &str, args: I, dir: Option<&Path>) -> Result<(), ExecuteError> +where + I: IntoIterator, + S: AsRef, +{ + let mut command = Command::new(cmd); + command + .args(args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + + if let Some(dir) = dir { + command.current_dir(dir); + } + + run_command(&mut command, cmd) +} + +pub fn execute_command_with_sudo( + cmd: &str, + args: I, + dir: Option<&Path>, +) -> Result<(), ExecuteError> +where + I: IntoIterator, + S: AsRef, +{ + let mut command = Command::new("sudo"); + command + .arg("-S") + .arg(cmd) + .args(args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + + if let Some(dir) = dir { + command.current_dir(dir); + } + + run_command(&mut command, &format!("sudo -S {}", cmd)) +} + +fn run_command(command: &mut Command, cmd_name: &str) -> Result<(), ExecuteError> { + let mut child = command.spawn()?; + + if let Some(stdout) = child.stdout.take() { + let reader = BufReader::new(stdout); + for line in reader.lines() { + info!("{}", line?); + } + } + + let status = child.wait()?; + + if status.success() { + Ok(()) + } else { + let code = status.code().unwrap_or(-1); + Err(ExecuteError::CommandStatusError(cmd_name.to_string(), code)) + } +} diff --git a/workspace/debian/src/lintian.rs b/workspace/debian/src/lintian.rs new file mode 100644 index 00000000..b478bbb6 --- /dev/null +++ b/workspace/debian/src/lintian.rs @@ -0,0 +1,395 @@ +use super::execute::{execute_command, Execute, ExecuteError}; +use log::info; +use std::path::Path; +use thiserror::Error; +use types::distribution::Distribution; + +/// Represents a wrapper for the Lintian Debian package checker tool. +/// +/// Lintian is used to check Debian packages for compliance with the Debian policy +/// and other quality assurance checks. This struct provides a builder pattern +/// interface to configure and execute Lintian commands. +/// +/// # Examples +/// +/// ``` +/// use debian::lintian::Lintian; +/// use debian::execute::Execute; +/// let result = Lintian::new() +/// .info() +/// .suppress_tag("malformed-deb-archive") +/// .changes_file("package.changes") +/// .execute(); +/// ``` +#[derive(Debug, Clone, Default)] +pub struct Lintian { + /// Tags to suppress during linting + suppress_tags: Vec, + /// Whether to show informational tags + show_info: bool, + /// Whether to show extended information + extended_info: bool, + /// Path to the changes file to check + changes_file_path: Option, + /// Maximum number of tags to display + tag_display_limit: Option, + /// Severity levels to fail on (warning, error) + fail_on: Vec, + /// Ubuntu/Debian codename for version-specific checks + codename: Option, +} + +/// Custom error type for lintian operations +#[derive(Error, Debug)] +pub enum LintianError { + #[error("Failed to execute command: {0}")] + CommandExecutionError(#[from] ExecuteError), +} + +type Result = std::result::Result; + +impl Lintian { + /// Creates a new Lintian instance with default configuration. + /// + /// This method initializes a Lintian object with empty or default values, + /// ready to be configured using the builder pattern. + /// + /// # Returns + /// + /// A new instance of `Lintian`. + pub fn new() -> Self { + Self::default() + } + + /// Suppresses the specified lintian tag. + /// + /// This will add the given tag to the list of tags that Lintian + /// should not report. + /// + /// # Arguments + /// + /// * `tag` - The tag to suppress. + /// + /// # Returns + /// + /// Self with the tag added to the suppress list. + pub fn suppress_tag>(mut self, tag: S) -> Self { + self.suppress_tags.push(tag.as_ref().to_string()); + self + } + + /// Enables display of informational tags. + /// + /// Corresponds to the `-i` flag in the Lintian command. + /// + /// # Returns + /// + /// Self with the info flag enabled. + pub fn info(mut self) -> Self { + self.show_info = true; + self + } + + /// Enables display of extended information. + /// + /// Corresponds to the `-I` flag in the Lintian command. + /// + /// # Returns + /// + /// Self with the extended info flag enabled. + pub fn extended_info(mut self) -> Self { + self.extended_info = true; + self + } + + /// Specifies a changes file to check. + /// + /// # Arguments + /// + /// * `file` - Path to the changes file. + /// + /// # Returns + /// + /// Self with the changes file path set. + pub fn changes_file>(mut self, file: P) -> Self { + self.changes_file_path = Some(format!("{:?}", file.as_ref())); + self + } + + /// Sets the maximum number of tags to display. + /// + /// # Arguments + /// + /// * `limit` - Maximum number of tags to display. + /// + /// # Returns + /// + /// Self with the tag display limit set. + pub fn tag_display_limit(mut self, limit: u32) -> Self { + self.tag_display_limit = Some(limit); + self + } + + /// Configures Lintian to fail on warnings. + /// + /// Can be combined with `fail_on_error()`. + /// + /// # Returns + /// + /// Self with fail-on-warning configured. + pub fn fail_on_warning(mut self) -> Self { + self.fail_on.push("warning".to_string()); + self + } + + /// Configures Lintian to fail on errors. + /// + /// Can be combined with `fail_on_warning()`. + /// + /// # Returns + /// + /// Self with fail-on-error configured. + pub fn fail_on_error(mut self) -> Self { + self.fail_on.push("error".to_string()); + self + } + + /// Configures Lintian for a specific Ubuntu/Debian codename. + /// + /// For certain codenames (jammy, noble), this automatically + /// suppresses the "malformed-deb-archive" tag. + /// + /// # Arguments + /// + /// * `codename` - Ubuntu/Debian codename (e.g., "jammy", "noble"). + /// + /// # Returns + /// + /// Self with codename-specific configuration. + pub fn with_codename(mut self, codename: &Distribution) -> Self { + self.codename = Some(codename.clone()); + + match codename { + Distribution::Debian(_) => {} + Distribution::Ubuntu(_) => self.suppress_tags.push("malformed-deb-archive".to_string()), + } + self + } + + /// Builds the command arguments from struct fields. + /// + /// This method constructs a vector of command-line arguments + /// based on the current configuration of the Lintian object. + /// + /// # Returns + /// + /// A vector of strings representing the command-line arguments. + fn build_args(&self) -> Vec { + let mut args = Vec::new(); + + // Add suppress tags + for tag in &self.suppress_tags { + args.push("--suppress-tags".to_string()); + args.push(tag.clone()); + } + + // Add info flag + if self.show_info { + args.push("-i".to_string()); + } + + // Add extended info flag + if self.extended_info { + args.push("-I".to_string()); + } + + // Add changes file + if let Some(file) = &self.changes_file_path { + args.push(file.clone()); + } + + // Add tag display limit + if let Some(limit) = self.tag_display_limit { + args.push(format!("--tag-display-limit={}", limit)); + } + + // Add fail-on options + for level in &self.fail_on { + args.push(format!("--fail-on={}", level)); + } + + args + } +} + +impl Execute for Lintian { + type Error = LintianError; + /// Executes the lintian command with the configured options. + /// + /// This method builds the arguments based on the current configuration + /// and executes the lintian command. + /// + /// # Returns + /// + /// A result indicating success or an error. + fn execute(&self) -> Result<()> { + let args = self.build_args(); + info!("Running: lintian {}", args.join(" ")); + execute_command("lintian", &args, None)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + #[test] + fn test_new() { + let lintian = Lintian::new(); + assert!(lintian.suppress_tags.is_empty()); + assert!(!lintian.show_info); + assert!(!lintian.extended_info); + assert!(lintian.changes_file_path.is_none()); + assert!(lintian.tag_display_limit.is_none()); + assert!(lintian.fail_on.is_empty()); + assert!(lintian.codename.is_none()); + } + + #[test] + fn test_suppress_tag() { + let lintian = Lintian::new().suppress_tag("test-tag"); + assert_eq!(lintian.suppress_tags, vec!["test-tag"]); + } + + #[test] + fn test_info() { + let lintian = Lintian::new().info(); + assert!(lintian.show_info); + } + + #[test] + fn test_extended_info() { + let lintian = Lintian::new().extended_info(); + assert!(lintian.extended_info); + } + + #[test] + fn test_changes_file() { + let path = PathBuf::from("/path/to/file.changes"); + let lintian = Lintian::new().changes_file(&path); + assert_eq!(lintian.changes_file_path, Some(format!("{:?}", path))); + } + + #[test] + fn test_tag_display_limit() { + let lintian = Lintian::new().tag_display_limit(10); + assert_eq!(lintian.tag_display_limit, Some(10)); + } + + #[test] + fn test_fail_on_warning() { + let lintian = Lintian::new().fail_on_warning(); + assert_eq!(lintian.fail_on, vec!["warning".to_string()]); + } + + #[test] + fn test_fail_on_error() { + let lintian = Lintian::new().fail_on_error(); + assert_eq!(lintian.fail_on, vec!["error".to_string()]); + } + + #[test] + fn test_fail_on_both() { + let lintian = Lintian::new().fail_on_warning().fail_on_error(); + assert_eq!( + lintian.fail_on, + vec!["warning".to_string(), "error".to_string()] + ); + } + + #[test] + fn test_with_codename_jammy() { + let lintian = Lintian::new().with_codename(&Distribution::jammy()); + assert_eq!(lintian.codename, Some(Distribution::jammy())); + assert!(lintian + .suppress_tags + .contains(&"malformed-deb-archive".to_string())); + } + + #[test] + fn test_with_codename_other() { + let lintian = Lintian::new().with_codename(&Distribution::bookworm()); + assert_eq!(lintian.codename, Some(Distribution::bookworm())); + assert!(!lintian + .suppress_tags + .contains(&"malformed-deb-archive".to_string())); + } + + #[test] + fn test_build_args() { + let lintian = Lintian::new() + .suppress_tag("tag1") + .suppress_tag("tag2") + .info() + .extended_info() + .changes_file("/path/to/file.changes") + .tag_display_limit(5) + .fail_on_warning(); + + let args = lintian.build_args(); + + assert!(args.contains(&"--suppress-tags".to_string())); + assert!(args.contains(&"tag1".to_string())); + assert!(args.contains(&"tag2".to_string())); + assert!(args.contains(&"-i".to_string())); + assert!(args.contains(&"-I".to_string())); + assert!(args.contains(&format!("{:?}", PathBuf::from("/path/to/file.changes")))); + assert!(args.contains(&"--tag-display-limit=5".to_string())); + assert!(args.contains(&"--fail-on=warning".to_string())); + } + + // #[test] + // fn test_execute() { + // // Setup mock + // let mut mock = MockExecuteCommand::new(); + // mock.expect_call() + // .with( + // eq("lintian"), + // eq(vec!["-i".to_string()]), + // eq(None) + // ) + // .times(1) + // .returning(|_, _, _| Ok(())); + + // // We'd normally replace the actual execute_command with our mock + // // For this test, we're just verifying the arguments + + // let lintian = Lintian::new().info(); + // let args = lintian.build_args(); + + // assert_eq!(args, vec!["-i".to_string()]); + // } + + #[test] + fn test_chaining() { + let lintian = Lintian::new() + .info() + .extended_info() + .suppress_tag("tag1") + .changes_file("file.changes") + .fail_on_error() + .with_codename(&Distribution::noble()); + + assert!(lintian.show_info); + assert!(lintian.extended_info); + assert_eq!(lintian.suppress_tags, vec!["tag1", "malformed-deb-archive"]); + assert_eq!( + lintian.changes_file_path, + Some(format!("{:?}", PathBuf::from("file.changes"))) + ); + assert_eq!(lintian.fail_on, vec!["error".to_string()]); + assert_eq!(lintian.codename, Some(Distribution::noble())); + } +} diff --git a/workspace/debian/src/mod.rs b/workspace/debian/src/mod.rs new file mode 100644 index 00000000..ec7fc526 --- /dev/null +++ b/workspace/debian/src/mod.rs @@ -0,0 +1,8 @@ +pub mod autopkgtest; +pub mod autopkgtest_image; +pub mod debcrafter; +pub mod execute; +pub mod lintian; +pub mod piuparts; +pub mod sbuild; +pub mod sbuild_create_chroot; diff --git a/workspace/debian/src/piuparts.rs b/workspace/debian/src/piuparts.rs new file mode 100644 index 00000000..7bef8295 --- /dev/null +++ b/workspace/debian/src/piuparts.rs @@ -0,0 +1,393 @@ +use super::execute::{execute_command_with_sudo, Execute, ExecuteError}; +use log::info; +use std::path::Path; +use thiserror::Error; +use types::distribution::{Distribution, UbuntuCodename}; + +/// A builder for the piuparts command, which tests Debian package installation, +/// upgrading, and removal processes. +/// +/// Piuparts (Package Installation, UPgrading And Removal Testing Suite) helps validate +/// Debian packages by testing their installation, upgrade paths, and purging in a clean +/// chroot environment. This struct implements a builder pattern to configure and execute +/// piuparts with various options. +/// +/// # Examples +/// +/// ```rust +/// use std::path::Path; +/// use debian::piuparts::Piuparts; +/// use crate::debian::execute::Execute; +/// use types::distribution::Distribution; +/// +/// // Basic usage +/// let deb_file = Path::new("/path/to/package.deb"); +/// let result = Piuparts::new() +/// .distribution(&Distribution::bookworm()) +/// .mirror("http://deb.debian.org/debian") +/// .verbose() +/// .deb_file(deb_file) +/// .execute(); +/// +/// // With .NET environment setup +/// let result = Piuparts::new() +/// .distribution(&Distribution::bookworm()) +/// .with_dotnet_env(true, &Distribution::bookworm()) +/// .deb_file(deb_file) +/// .execute(); +/// ``` +/// +/// # Note +/// +/// This command requires sudo privileges to run piuparts, as it operates +/// on system package management and creates isolated environments. +pub struct Piuparts<'a> { + /// Distribution codename (e.g., "bookworm", "jammy") + distribution: Option, + /// Mirror URL for package repository + mirror: Option, + /// Paths to bind mount into the chroot + bindmounts: Vec, + /// Path to keyring file for package verification + keyring: Option, + /// Whether to enable verbose output + verbose: bool, + /// Additional repositories to use + extra_repos: Vec, + /// Whether to verify package signatures + verify_signatures: bool, + /// Path to the .deb file to test + deb_file: Option<&'a Path>, + /// Directory containing the .deb file + deb_path: Option<&'a Path>, +} +/// Custom error type for piuparts operations +#[derive(Error, Debug)] +pub enum PiupartsError { + #[error("Deb file not specified")] + MissingDebFile, + + #[error("Failed to execute command: {0}")] + CommandExecutionError(#[from] ExecuteError), +} + +type Result = std::result::Result; + +impl<'a> Piuparts<'a> { + /// Creates a new Piuparts builder with default settings. + /// + /// Default values: + /// - No distribution specified + /// - No mirror specified + /// - No bind mounts + /// - No keyring specified + /// - Verbose mode disabled + /// - No extra repositories + /// - Package signature verification enabled + /// - No .deb file or path specified + pub fn new() -> Self { + Self { + distribution: None, + mirror: None, + bindmounts: Vec::new(), + keyring: None, + verbose: false, + extra_repos: Vec::new(), + verify_signatures: true, + deb_file: None, + deb_path: None, + } + } + + /// Sets the distribution codename (e.g., "bookworm", "jammy"). + /// + /// This corresponds to the `-d` option in piuparts and specifies the + /// Debian/Ubuntu distribution to use for testing. The distribution must + /// be available in the specified mirror. + /// + /// # Arguments + /// + /// * `codename` - The distribution codename (e.g., "bookworm", "jammy", "bullseye") + pub fn distribution(mut self, codename: &Distribution) -> Self { + self.distribution = Some(codename.clone()); + self + } + + /// Sets the mirror URL for the package repository. + /// + /// This corresponds to the `-m` option in piuparts and specifies the + /// package repository mirror to use for downloading packages. + /// + /// # Arguments + /// + /// * `url` - The URL of the repository mirror (e.g., "http://deb.debian.org/debian") + pub fn mirror(mut self, url: &str) -> Self { + self.mirror = Some(url.to_string()); + self + } + + /// Adds /dev to the list of directories to bind mount into the chroot. + /// + /// This corresponds to the `--bindmount=/dev` option in piuparts and allows + /// the chroot environment to access the host's /dev directory. This can be + /// necessary for certain packages that need access to device files. + /// + /// # Note + /// + /// Binding /dev can introduce security risks by giving the chroot + /// environment access to the host's devices. + pub fn bindmount_dev(mut self) -> Self { + self.bindmounts.push("/dev".to_string()); + self + } + + /// Sets the keyring file to use for package verification. + /// + /// This corresponds to the `--keyring` option in piuparts and specifies the + /// GPG keyring file to use for package signature verification. + /// + /// # Arguments + /// + /// * `keyring` - The path to the keyring file + pub fn keyring(mut self, keyring: &str) -> Self { + self.keyring = Some(keyring.to_string()); + self + } + + /// Enables verbose output. + /// + /// This corresponds to the `--verbose` option in piuparts and causes + /// piuparts to output more detailed information during testing, which + /// can be helpful for debugging. + pub fn verbose(mut self) -> Self { + self.verbose = true; + self + } + + /// Adds an additional repository to use. + /// + /// This corresponds to the `--extra-repo` option in piuparts and allows + /// specifying additional package repositories beyond the main mirror. + /// + /// # Arguments + /// + /// * `repo` - The repository definition in sources.list format + /// (e.g., "deb http://security.debian.org/debian-security bookworm-security main") + pub fn extra_repo(mut self, repo: &str) -> Self { + self.extra_repos.push(repo.to_string()); + self + } + + /// Disables package signature verification. + /// + /// This corresponds to the `--do-not-verify-signatures` option in piuparts + /// and disables GPG signature verification for packages. This can be necessary + /// when using unofficial repositories that don't provide properly signed packages. + /// + /// # Security Note + /// + /// Disabling signature verification reduces security by allowing potentially + /// tampered packages to be installed. + pub fn no_verify_signatures(mut self) -> Self { + self.verify_signatures = false; + self + } + + /// Configures the environment for .NET packages if needed. + /// + /// If `is_dotnet` is true and the distribution is either "bookworm" or "jammy jellyfish", + /// adds the Microsoft repository and disables signature verification. + pub fn with_dotnet_env(self, is_dotnet: bool, codename: &Distribution) -> Self { + if !is_dotnet { + return self; + } + + match codename { + Distribution::Debian(debian) => { + let repo = format!( + "deb https://packages.microsoft.com/debian/12/prod {} main", + debian + ); + return self.extra_repo(&repo).no_verify_signatures(); + } + Distribution::Ubuntu(ubuntu_distro) => { + if let UbuntuCodename::Jammy = ubuntu_distro { + let repo = format!( + "deb https://packages.microsoft.com/debian/12/prod {} main", + "jammy" + ); + return self.extra_repo(&repo).no_verify_signatures(); + } + } + } + + self + } + + /// Sets the .deb file to test. + /// + /// This specifies the Debian package file that piuparts will test. + /// This is a required parameter for executing piuparts. + /// + /// # Arguments + /// + /// * `deb_file` - Path to the .deb file to test + pub fn deb_file(mut self, deb_file: &'a Path) -> Self { + self.deb_file = Some(deb_file); + self + } + + /// Sets the directory containing the .deb file. + /// + /// This specifies the directory where the .deb file is located. + /// It can be useful when the execution needs to know the context directory. + /// + /// # Arguments + /// + /// * `deb_path` - Path to the directory containing the .deb file + pub fn deb_path(mut self, deb_path: &'a Path) -> Self { + self.deb_path = Some(deb_path); + self + } + + /// Builds the command-line arguments for piuparts based on the configured options. + /// + /// This method converts the builder's state into a vector of string arguments + /// that can be passed to the piuparts command. It's called internally by the + /// `execute()` method. + /// + /// # Returns + /// + /// A vector of strings representing the command-line arguments + fn build_args(&self) -> Vec { + let mut args = Vec::new(); + + if let Some(dist) = &self.distribution { + args.push("-d".to_string()); + args.push(dist.as_short().into()); + } + + if let Some(mirror_url) = &self.mirror { + args.push("-m".to_string()); + args.push(mirror_url.clone()); + } + + for bindmount in &self.bindmounts { + args.push(format!("--bindmount={}", bindmount)); + } + + if let Some(keyring_path) = &self.keyring { + args.push(format!("--keyring={}", keyring_path)); + } + + if self.verbose { + args.push("--verbose".to_string()); + } + + for repo in &self.extra_repos { + args.push(format!("--extra-repo={}", repo)); + } + + if !self.verify_signatures { + args.push("--do-not-verify-signatures".to_string()); + } + + if let Some(deb_file) = &self.deb_file { + args.push(deb_file.display().to_string()); + } + + args + } +} + +impl<'a> Execute for Piuparts<'a> { + type Error = PiupartsError; + /// Executes the piuparts command with the configured options. + /// + /// Returns an error if the .deb file is not set or if the command fails. + fn execute(&self) -> Result<()> { + let args = self.build_args(); + + + info!( + "Running: sudo -S piuparts {} {:?}", + args.join(" "), + self.deb_file.unwrap() + ); + + execute_command_with_sudo("piuparts", args, self.deb_path)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_default_piuparts() { + let piuparts = Piuparts::new(); + assert_eq!(piuparts.build_args(), Vec::::new()); + } + + #[test] + fn test_distribution_option() { + let piuparts = Piuparts::new().distribution(&Distribution::bookworm()); + assert_eq!(piuparts.build_args(), vec!["-d", "bookworm"]); + } + + #[test] + fn test_mirror_option() { + let piuparts = Piuparts::new().mirror("http://deb.debian.org/debian"); + assert_eq!( + piuparts.build_args(), + vec!["-m", "http://deb.debian.org/debian"] + ); + } + + #[test] + fn test_multiple_options() { + let piuparts = Piuparts::new() + .distribution(&Distribution::jammy()) + .mirror("http://archive.ubuntu.com/ubuntu") + .verbose() + .bindmount_dev(); + + let args = piuparts.build_args(); + assert!(args.contains(&"-d".to_string())); + assert!(args.contains(&"jammy".to_string())); + assert!(args.contains(&"-m".to_string())); + assert!(args.contains(&"http://archive.ubuntu.com/ubuntu".to_string())); + assert!(args.contains(&"--verbose".to_string())); + assert!(args.contains(&"--bindmount=/dev".to_string())); + } + + #[test] + fn test_dotnet_env_bookworm() { + let piuparts = Piuparts::new().with_dotnet_env(true, &Distribution::bookworm()); + let args = piuparts.build_args(); + + assert!(args.contains( + &"--extra-repo=deb https://packages.microsoft.com/debian/12/prod bookworm main" + .to_string() + )); + assert!(args.contains(&"--do-not-verify-signatures".to_string())); + } + + #[test] + fn test_dotnet_env_no_effect() { + let piuparts = Piuparts::new().with_dotnet_env(true, &Distribution::noble()); + assert_eq!(piuparts.build_args(), Vec::::new()); + + let piuparts = Piuparts::new().with_dotnet_env(false, &Distribution::bookworm()); + assert_eq!(piuparts.build_args(), Vec::::new()); + } + + + #[test] + fn test_deb_file_path() { + let deb_path = Path::new("/tmp/package.deb"); + let piuparts = Piuparts::new().deb_file(&deb_path); + assert_eq!(piuparts.deb_file, Some(deb_path)); + } +} diff --git a/workspace/debian/src/sbuild.rs b/workspace/debian/src/sbuild.rs new file mode 100644 index 00000000..6ccbd32a --- /dev/null +++ b/workspace/debian/src/sbuild.rs @@ -0,0 +1,401 @@ +use super::execute::{execute_command, Execute, ExecuteError}; +use log::info; +use std::path::{Path, PathBuf}; +use thiserror::Error; +use types::distribution::Distribution; + +/// A builder for configuring and executing the sbuild command. +/// +/// This struct allows for fluid configuration of sbuild parameters using +/// the builder pattern. Once configured, the command can be executed +/// via the `Execute` trait implementation. +/// +/// # Example +/// +/// ``` +/// use debian::sbuild::SbuildBuilder; +/// use debian::execute::Execute; +/// use types::distribution::Distribution; +/// +/// let result = SbuildBuilder::new() +/// .distribution(&Distribution::bookworm()) +/// .build_arch_all() +/// .verbose() +/// .execute(); +/// ``` + +#[derive(Default, Debug, Clone)] +pub struct SbuildBuilder { + /// Debian distribution codename (e.g., "bullseye", "bookworm") + distribution: Option, + /// Whether to build architecture-independent packages + build_arch_all: bool, + /// Whether to build source packages only + build_source: bool, + /// Path to the sbuild cache file + cache_file: Option, + /// Whether to enable verbose output + verbose: bool, + /// Whether to use unshare chroot mode + chroot_mode_unshare: bool, + /// Additional setup commands to pass to sbuild + setup_commands: Vec, + /// Whether to run piuparts after building + run_piuparts: bool, + /// Whether to perform apt upgrades before building + apt_upgrades: bool, + /// Whether to run lintian after building (None = use sbuild default) + run_lintian: Option, + /// Whether to run autopkgtest after building + run_autopkgtest: bool, + /// Working directory for the sbuild command + dir: Option, +} + +#[derive(Error, Debug)] +pub enum SbuildCmdError { + #[error("Failed to execute command: {0}")] + CommandExecutionError(#[from] ExecuteError), +} + +type Result = std::result::Result; + +impl SbuildBuilder { + /// Creates a new SbuildBuilder with default settings. + /// + /// Default settings: + /// - No distribution specified + /// - Architecture-independent packages not built + /// - Source packages not built + /// - No cache file specified + /// - Normal (non-verbose) output + /// - Default chroot mode + /// - No additional setup commands + /// - piuparts enabled + /// - apt upgrades enabled + /// - lintian uses sbuild default + /// - autopkgtest enabled + /// - No working directory specified + pub fn new() -> Self { + Self { + distribution: None, + build_arch_all: false, + build_source: false, + cache_file: None, + verbose: false, + chroot_mode_unshare: false, + setup_commands: Vec::new(), + run_piuparts: true, + apt_upgrades: true, + run_lintian: None, + run_autopkgtest: true, + dir: None, + } + } + + /// Sets the target distribution for the build. + /// + /// This corresponds to the `-d` flag in sbuild. + /// + /// # Arguments + /// + /// * `codename` - The Debian distribution codename (e.g., "bullseye", "bookworm") + pub fn distribution(mut self, codename: &Distribution) -> Self { + self.distribution = Some(codename.clone()); + self + } + + /// Enables building of architecture-independent packages. + /// + /// This corresponds to the `-A` flag in sbuild. + pub fn build_arch_all(mut self) -> Self { + self.build_arch_all = true; + self + } + + /// Enables building of source packages only. + /// + /// This corresponds to the `-s` and `--source-only-changes` flags in sbuild. + pub fn build_source(mut self) -> Self { + self.build_source = true; + self + } + + /// Sets the cache file to use for the build. + /// + /// This corresponds to the `-c` flag in sbuild. + /// + /// # Arguments + /// + /// * `cache_file` - Path to the cache file + pub fn cache_file(mut self, cache_file: PathBuf) -> Self { + self.cache_file = Some(cache_file); + self + } + + /// Enables verbose output. + /// + /// This corresponds to the `-v` flag in sbuild. + pub fn verbose(mut self) -> Self { + self.verbose = true; + self + } + + /// Sets the chroot mode to unshare. + /// + /// This corresponds to the `--chroot-mode=unshare` flag in sbuild. + pub fn chroot_mode_unshare(mut self) -> Self { + self.chroot_mode_unshare = true; + self + } + + /// Adds custom setup commands to pass to sbuild. + /// + /// # Arguments + /// + /// * `commands` - List of additional commands to pass to sbuild + pub fn setup_commands(mut self, commands: &[String]) -> Self { + self.setup_commands + .extend(commands.iter().map(|s| s.to_string())); + self + } + + /// Disables running piuparts after building. + /// + /// This corresponds to the `--no-run-piuparts` flag in sbuild. + pub fn no_run_piuparts(mut self) -> Self { + self.run_piuparts = false; + self + } + + /// Disables apt upgrades before building. + /// + /// This corresponds to the `--no-apt-upgrade` and `--no-apt-distupgrade` flags in sbuild. + pub fn no_apt_upgrades(mut self) -> Self { + self.apt_upgrades = false; + self + } + + /// Sets whether to run lintian after building. + /// + /// When enabled, adds several lintian-related flags with common options. + /// When disabled, adds the `--no-run-lintian` flag. + /// + /// # Arguments + /// + /// * `enabled` - Whether to enable or disable lintian + pub fn run_lintian(mut self, enabled: bool) -> Self { + self.run_lintian = Some(enabled); + self + } + + /// Disables running autopkgtest after building. + /// + /// This corresponds to the `--no-run-autopkgtest` flag in sbuild. + pub fn no_run_autopkgtest(mut self) -> Self { + self.run_autopkgtest = false; + self + } + + /// Sets the working directory for the sbuild command. + /// + /// # Arguments + /// + /// * `dir` - Path to the working directory + pub fn working_dir(mut self, dir: &Path) -> Self { + self.dir = Some(dir.to_path_buf()); + self + } + + /// Converts the builder configuration into command-line arguments. + /// + /// This method translates the builder's state into a vector of strings + /// that can be passed to the sbuild command. + /// + /// # Returns + /// + /// A vector of strings representing the sbuild command-line arguments. + fn build_args(&self) -> Vec { + let mut args = Vec::new(); + + if let Some(dist) = &self.distribution { + args.push("-d".to_string()); + args.push(dist.as_short().into()); + } + + if self.build_arch_all { + args.push("-A".to_string()); + } + + if self.build_source { + args.push("-s".to_string()); + args.push("--source-only-changes".to_string()); + } + + if let Some(cache) = &self.cache_file { + args.push("-c".to_string()); + args.push(cache.display().to_string()); + } + + if self.verbose { + args.push("-v".to_string()); + } + + if self.chroot_mode_unshare { + args.push("--chroot-mode=unshare".to_string()); + } + + args.extend(self.setup_commands.clone()); + + if !self.run_piuparts { + args.push("--no-run-piuparts".to_string()); + } + + if !self.apt_upgrades { + args.push("--no-apt-upgrade".to_string()); + args.push("--no-apt-distupgrade".to_string()); + } + + if let Some(enabled) = self.run_lintian { + if enabled { + args.extend([ + "--run-lintian".to_string(), + "--lintian-opt=-i".to_string(), + "--lintian-opt=--I".to_string(), + "--lintian-opt=--suppress-tags".to_string(), + "--lintian-opt=bad-distribution-in-changes-file".to_string(), + "--lintian-opt=--suppress-tags".to_string(), + "--lintian-opt=debug-file-with-no-debug-symbols".to_string(), + "--lintian-opt=--tag-display-limit=0".to_string(), + "--lintian-opts=--fail-on=error".to_string(), + "--lintian-opts=--fail-on=warning".to_string(), + ]); + } else { + args.push("--no-run-lintian".to_string()); + } + } + + if !self.run_autopkgtest { + args.push("--no-run-autopkgtest".to_string()); + } + + args + } +} + +/// Implementation of the Execute trait for SbuildBuilder. +/// +/// This allows the builder to be executed directly after configuration. +impl Execute for SbuildBuilder { + type Error = SbuildCmdError; + /// Executes the sbuild command with the configured options. + /// + /// # Returns + /// + /// Ok(()) if the command executed successfully, or an error if it failed. + /// + /// # Errors + /// + /// Returns an error if the sbuild command fails to execute or returns a non-zero exit code. + fn execute(&self) -> Result<()> { + let args = self.build_args(); + info!("Running: sbuild {}", &args.join(" ")); + execute_command("sbuild", &args, self.dir.as_deref())?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + #[test] + fn test_new_builder() { + let builder = SbuildBuilder::new(); + assert!(builder.distribution.is_none()); + assert!(!builder.build_arch_all); + assert!(!builder.build_source); + assert!(builder.cache_file.is_none()); + assert!(!builder.verbose); + assert!(!builder.chroot_mode_unshare); + assert!(builder.setup_commands.is_empty()); + assert!(builder.run_piuparts); + assert!(builder.apt_upgrades); + assert!(builder.run_lintian.is_none()); + assert!(builder.run_autopkgtest); + assert!(builder.dir.is_none()); + } + + #[test] + fn test_builder_methods() { + let path = PathBuf::from("/tmp/test"); + let builder = SbuildBuilder::new() + .distribution(&Distribution::bookworm()) + .build_arch_all() + .build_source() + .cache_file("cache.txt".into()) + .verbose() + .chroot_mode_unshare() + .setup_commands(&["--foo".to_string(), "--bar".to_string()]) + .no_run_piuparts() + .no_apt_upgrades() + .run_lintian(true) + .no_run_autopkgtest() + .working_dir(&path); + + assert_eq!(builder.distribution, Some(Distribution::bookworm())); + assert!(builder.build_arch_all); + assert!(builder.build_source); + assert_eq!(builder.cache_file, Some("cache.txt".into())); + assert!(builder.verbose); + assert!(builder.chroot_mode_unshare); + assert_eq!( + builder.setup_commands, + vec!["--foo".to_string(), "--bar".to_string()] + ); + assert!(!builder.run_piuparts); + assert!(!builder.apt_upgrades); + assert_eq!(builder.run_lintian, Some(true)); + assert!(!builder.run_autopkgtest); + assert_eq!(builder.dir, Some(path)); + } + + #[test] + fn test_build_args() { + let builder = SbuildBuilder::new() + .distribution(&Distribution::bookworm()) + .build_arch_all() + .verbose() + .run_lintian(false); + + let args = builder.build_args(); + assert!(args.contains(&"-d".to_string())); + assert!(args.contains(&"bookworm".to_string())); + assert!(args.contains(&"-A".to_string())); + assert!(args.contains(&"-v".to_string())); + assert!(args.contains(&"--no-run-lintian".to_string())); + + // Test with lintian enabled + let builder = SbuildBuilder::new().run_lintian(true); + let args = builder.build_args(); + assert!(args.contains(&"--run-lintian".to_string())); + assert!(args.contains(&"--lintian-opt=-i".to_string())); + } + + #[test] + fn test_custom_commands() { + let commands = vec![ + "--foo=bar".to_string(), + "--baz".to_string(), + "--qux=quux".to_string(), + ]; + + let builder = SbuildBuilder::new().setup_commands(&commands); + let args = builder.build_args(); + + for cmd in commands { + assert!(args.contains(&cmd)); + } + } +} diff --git a/workspace/debian/src/sbuild_create_chroot.rs b/workspace/debian/src/sbuild_create_chroot.rs new file mode 100644 index 00000000..d1e2e17e --- /dev/null +++ b/workspace/debian/src/sbuild_create_chroot.rs @@ -0,0 +1,294 @@ +use super::execute::{execute_command, Execute, ExecuteError}; +use log::info; +use std::path::{Path, PathBuf}; +use thiserror::Error; +use types::distribution::Distribution; + +/// Represents options for creating an sbuild chroot environment +/// +/// This struct provides a builder pattern to configure and execute +/// the `sbuild-createchroot` command with various options. +#[derive(Debug, Default)] +pub struct SbuildCreateChroot { + /// The chroot mode (e.g., "schroot", "unshare") + chroot_mode: Option, + /// Whether to create a tarball + make_tarball: bool, + /// Path to the cache file + cache_file: Option, + /// Codename of the distribution (e.g., "bullseye") + codename: Option, + /// Directory to use for temporary files + temp_dir: Option, + /// URL of the repository to use + repo_url: Option, +} + +/// Custom error type for sbuild-createchroot operations +#[derive(Error, Debug)] +pub enum SbuildCreateChrootError { + #[error("Failed to execute command: {0}")] + CommandExecutionError(#[from] ExecuteError), +} + +type Result = std::result::Result; + +impl SbuildCreateChroot { + /// Creates a new instance with default options + /// + /// # Examples + /// + /// ``` + /// use debian::sbuild_create_chroot::SbuildCreateChroot; + /// let chroot = SbuildCreateChroot::new(); + /// ``` + pub fn new() -> Self { + Self::default() + } + + /// Sets the chroot mode + /// + /// # Arguments + /// + /// * `mode` - The chroot mode to use (e.g., "schroot", "unshare") + /// + /// # Examples + /// + /// ``` + /// use debian::sbuild_create_chroot::SbuildCreateChroot; + /// let chroot = SbuildCreateChroot::new().chroot_mode("unshare"); + /// ``` + pub fn chroot_mode(mut self, mode: &str) -> Self { + self.chroot_mode = Some(mode.to_string()); + self + } + + /// Enables creation of a tarball + /// + /// # Examples + /// + /// ``` + /// use debian::sbuild_create_chroot::SbuildCreateChroot; + /// let chroot = SbuildCreateChroot::new().make_tarball(); + /// ``` + pub fn make_tarball(mut self) -> Self { + self.make_tarball = true; + self + } + + /// Sets the cache file path + /// + /// # Arguments + /// + /// * `path` - Path to the cache file + /// + /// # Examples + /// + /// ``` + /// use debian::sbuild_create_chroot::SbuildCreateChroot; + /// let chroot = SbuildCreateChroot::new().cache_file(&"/path/to/cache".into()); + /// ``` + pub fn cache_file(mut self, path: &PathBuf) -> Self { + self.cache_file = Some(path.clone()); + self + } + + /// Sets the distribution codename + /// + /// # Arguments + /// + /// * `name` - Codename of the distribution (e.g., "bullseye") + /// + /// # Examples + /// + /// ``` + /// use debian::sbuild_create_chroot::SbuildCreateChroot; + /// use types::distribution::Distribution; + /// let chroot = SbuildCreateChroot::new().codename(&Distribution::bookworm()); + /// ``` + pub fn codename(mut self, name: &Distribution) -> Self { + self.codename = Some(name.clone()); + self + } + + /// Sets the temporary directory + /// + /// # Arguments + /// + /// * `dir` - Path to the temporary directory + /// + /// # Examples + /// + /// ``` + /// use std::path::PathBuf; + /// use debian::sbuild_create_chroot::SbuildCreateChroot; + /// let temp = PathBuf::from("/tmp/sbuild"); + /// let chroot = SbuildCreateChroot::new().temp_dir(&temp); + /// ``` + pub fn temp_dir(mut self, dir: &Path) -> Self { + self.temp_dir = Some(dir.to_string_lossy().to_string()); + self + } + + /// Sets the repository URL + /// + /// # Arguments + /// + /// * `url` - URL of the repository to use + /// + /// # Examples + /// + /// ``` + /// use debian::sbuild_create_chroot::SbuildCreateChroot; + /// let chroot = SbuildCreateChroot::new().repo_url("http://deb.debian.org/debian"); + /// ``` + pub fn repo_url(mut self, url: &str) -> Self { + self.repo_url = Some(url.to_string()); + self + } + + /// Builds the command arguments based on the configured options + fn build_args(&self) -> Vec { + let mut args = Vec::new(); + + if let Some(mode) = &self.chroot_mode { + args.push(format!("--chroot-mode={}", mode)); + } + + if self.make_tarball { + args.push("--make-sbuild-tarball".to_string()); + } + + if let Some(cache) = &self.cache_file { + args.push(cache.display().to_string()); + } + + if let Some(name) = &self.codename { + args.push(name.as_short().into()); + } + + if let Some(dir) = &self.temp_dir { + args.push(dir.clone()); + } + + if let Some(url) = &self.repo_url { + args.push(url.clone()); + } + + args + } +} + +impl Execute for SbuildCreateChroot { + type Error = SbuildCreateChrootError; + /// Executes the sbuild-createchroot command with the configured options + /// + /// # Returns + /// + /// A `Result` indicating success or containing an error + /// + /// # Examples + /// + /// ``` + /// + /// # use debian::sbuild_create_chroot::SbuildCreateChroot; + /// # use debian::sbuild_create_chroot::SbuildCreateChrootError; + /// # use debian::execute::Execute; + /// # use types::distribution::Distribution; + /// # fn run() -> Result<(), SbuildCreateChrootError> { + /// let chroot = SbuildCreateChroot::new() + /// .chroot_mode("unshare") + /// .make_tarball() + /// .codename(&Distribution::bookworm()); + /// + /// chroot.execute()?; + /// # Ok(()) + /// # } + /// ``` + fn execute(&self) -> Result<()> { + let args = self.build_args(); + info!("Running: sbuild-createchroot {}", args.join(" ")); + execute_command("sbuild-createchroot", &args, None)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + // use mockall::predicate::*; + // use mockall::mock; + + // // Mock the execute_command function + // mock! { + // pub fn ExecuteCommand {} + // impl ExecuteCommand { + // pub fn execute_command(cmd: &str, args: &[String], env: Option<&[(String, String)]>) -> Result<()>; + // } + // } + + #[test] + fn test_default_new() { + let chroot = SbuildCreateChroot::new(); + assert!(chroot.chroot_mode.is_none()); + assert!(!chroot.make_tarball); + assert!(chroot.cache_file.is_none()); + assert!(chroot.codename.is_none()); + assert!(chroot.temp_dir.is_none()); + assert!(chroot.repo_url.is_none()); + } + + #[test] + fn test_build_args_empty() { + let chroot = SbuildCreateChroot::new(); + let args = chroot.build_args(); + assert!(args.is_empty()); + } + + #[test] + fn test_build_args_with_options() { + let temp_path = PathBuf::from("/tmp/sbuild"); + let chroot = SbuildCreateChroot::new() + .chroot_mode("unshare") + .make_tarball() + .cache_file(&"/var/cache/sbuild.tar.gz".into()) + .codename(&Distribution::bookworm()) + .temp_dir(&temp_path) + .repo_url("http://deb.debian.org/debian"); + + let args = chroot.build_args(); + + assert_eq!(args.len(), 6); + assert_eq!(args[0], "--chroot-mode=unshare"); + assert_eq!(args[1], "--make-sbuild-tarball"); + assert_eq!(args[2], "/var/cache/sbuild.tar.gz"); + assert_eq!(args[3], "bookworm"); + assert_eq!(args[4], "/tmp/sbuild"); + assert_eq!(args[5], "http://deb.debian.org/debian"); + } + + // #[test] + // fn test_execute() { + // let mut mock = MockExecuteCommand::new(); + + // // Set up expectations + // mock.expect_execute_command() + // .with( + // eq("sbuild-createchroot"), + // eq(vec!["--chroot-mode=unshare".to_string(), "bullseye".to_string()]), + // eq(None) + // ) + // .times(1) + // .returning(|_, _, _| Ok(())); + + // // Create and execute the command + // let chroot = SbuildCreateChroot::new() + // .chroot_mode("unshare") + // .codename("bullseye"); + + // // This would fail without mocking the actual command execution + // // Here we'd need a way to inject our mock into the execution + // // For a real test, you might use dependency injection or function pointers + // } +} diff --git a/workspace/packager_deb/Cargo.toml b/workspace/packager_deb/Cargo.toml new file mode 100644 index 00000000..1597f373 --- /dev/null +++ b/workspace/packager_deb/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "packager_deb" +version = "0.1.0" +edition = "2021" + +[lib] +name = "packager_deb" +path = "src/mod.rs" + + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +debian = { workspace = true } +types = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +tempfile = { workspace = true } +log = { workspace = true } +dirs = { workspace = true } +rand = { workspace = true } +shellexpand = { workspace = true } +sha2 = { workspace = true } +sha1 = { workspace = true } +filetime = { workspace = true } +cargo_metadata = { workspace = true } +toml = { workspace = true } + + +[dev-dependencies] +toml = { workspace = true } +httpmock = { workspace = true } +env_logger = { workspace = true } diff --git a/src/v1/build/.sbuildrc b/workspace/packager_deb/src/.sbuildrc similarity index 100% rename from src/v1/build/.sbuildrc rename to workspace/packager_deb/src/.sbuildrc diff --git a/workspace/packager_deb/src/configs/autopkgtest_version.rs b/workspace/packager_deb/src/configs/autopkgtest_version.rs new file mode 100644 index 00000000..903ca841 --- /dev/null +++ b/workspace/packager_deb/src/configs/autopkgtest_version.rs @@ -0,0 +1,121 @@ +use cargo_metadata::semver::Version as OriginalVersion; +use serde::{de, Deserialize, Deserializer, Serialize}; +use std::borrow::Cow; +use std::fmt; +use std::ops::{Deref, DerefMut}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct AutopkgtestVersion { + inner: OriginalVersion, + original_string: Cow<'static, str>, +} + +impl fmt::Display for AutopkgtestVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.original_string) + } +} + +impl AutopkgtestVersion { + pub fn as_str(&self) -> &str { + &self.original_string + } + + // Helper to normalize version strings like "2.5" to "0.2.5" + fn normalize_version(s: &str) -> String { + if s.matches('.').count() == 1 { + format!("0.{}", s) + } else { + s.to_string() + } + } + + // Helper to denormalize version strings like "0.2.5" back to "2.5" + fn denormalize_version(s: &str) -> String { + if s.starts_with("0.") { + s[2..].to_string() + } else { + s.to_string() + } + } +} + +impl Serialize for AutopkgtestVersion { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + // Remove the leading "0." if it was added during deserialization + let output = AutopkgtestVersion::denormalize_version(&self.original_string); + serializer.serialize_str(&output) + } +} + +impl Deref for AutopkgtestVersion { + type Target = OriginalVersion; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for AutopkgtestVersion { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl From for AutopkgtestVersion { + fn from(version: OriginalVersion) -> Self { + let original_string = Cow::Owned(version.to_string()); + AutopkgtestVersion { + inner: version, + original_string, + } + } +} + +impl<'a> TryFrom<&'a str> for AutopkgtestVersion { + type Error = cargo_metadata::semver::Error; + fn try_from(s: &'a str) -> Result { + let normalized = AutopkgtestVersion::normalize_version(s); + let inner = OriginalVersion::parse(&normalized)?; + Ok(AutopkgtestVersion { + inner, + original_string: Cow::Owned(s.to_string()), // Keep the original format + }) + } +} + +impl TryFrom for AutopkgtestVersion { + type Error = cargo_metadata::semver::Error; + fn try_from(s: String) -> Result { + >::try_from(&s) + } +} + +impl<'de> Deserialize<'de> for AutopkgtestVersion { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct AutopkgVersionVisitor; + impl<'de> de::Visitor<'de> for AutopkgVersionVisitor { + type Value = AutopkgtestVersion; + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string containing a valid version (e.g., 2.5 or 1.2.3)") + } + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + let normalized = AutopkgtestVersion::normalize_version(value); + let inner = OriginalVersion::parse(&normalized).map_err(de::Error::custom)?; + Ok(AutopkgtestVersion { + inner, + original_string: Cow::Owned(value.to_string()), // Keep original format + }) + } + } + deserializer.deserialize_string(AutopkgVersionVisitor) + } +} diff --git a/workspace/packager_deb/src/configs/mod.rs b/workspace/packager_deb/src/configs/mod.rs new file mode 100644 index 00000000..7477dd78 --- /dev/null +++ b/workspace/packager_deb/src/configs/mod.rs @@ -0,0 +1,4 @@ +pub mod autopkgtest_version; +pub mod pkg_config; +pub mod pkg_config_verify; +pub mod sbuild_version; diff --git a/workspace/packager_deb/src/configs/pkg_config.rs b/workspace/packager_deb/src/configs/pkg_config.rs new file mode 100644 index 00000000..5feb8875 --- /dev/null +++ b/workspace/packager_deb/src/configs/pkg_config.rs @@ -0,0 +1,181 @@ +use std::path::PathBuf; + +use serde::Deserialize; +use types::{config::Architecture, defaults::WORKDIR_ROOT, distribution::Distribution, url::Url, version::Version}; + + +use crate::misc::utils::expand_path; + +use super::{autopkgtest_version::AutopkgtestVersion, sbuild_version::SbuildVersion}; + +#[derive(Debug, Deserialize, PartialEq, Clone)] +pub struct RustConfig { + pub rust_version: Version, + pub rust_binary_url: Url, + pub rust_binary_gpg_asc: String, +} + +#[derive(Debug, Deserialize, PartialEq, Clone)] +pub struct GoConfig { + pub go_version: Version, + pub go_binary_url: Url, + pub go_binary_checksum: String, +} + +#[derive(Debug, Deserialize, PartialEq, Clone)] +pub struct JavascriptConfig { + pub node_version: Version, + pub node_binary_url: Url, + pub node_binary_checksum: String, + pub yarn_version: Option, +} + +#[derive(Debug, Deserialize, PartialEq, Clone)] +pub struct GradleConfig { + pub gradle_version: String, + pub gradle_binary_url: Url, + pub gradle_binary_checksum: String, +} + +#[derive(Debug, Deserialize, PartialEq, Clone)] +pub struct JavaConfig { + pub is_oracle: bool, + pub jdk_version: String, + pub jdk_binary_url: Url, + pub jdk_binary_checksum: String, + pub gradle: Option, +} + +#[derive(Debug, Deserialize, PartialEq, Clone)] +pub struct DotnetPackage { + pub name: String, + pub hash: String, + pub url: Url, +} + +#[derive(Debug, Deserialize, PartialEq, Clone, Default)] +pub struct DotnetConfig { + pub use_backup_version: bool, + pub dotnet_packages: Vec, + pub deps: Option>, +} + +#[derive(Debug, Deserialize, PartialEq, Clone)] +pub struct NimConfig { + pub nim_version: Version, + pub nim_binary_url: String, + pub nim_version_checksum: String, +} + +#[derive(Debug, Deserialize, PartialEq, Clone, Default)] +#[serde(tag = "language_env", rename_all = "lowercase")] +pub enum LanguageEnv { + Rust(RustConfig), + Go(GoConfig), + JavaScript(JavascriptConfig), + Java(JavaConfig), + Dotnet(DotnetConfig), + TypeScript(JavascriptConfig), + Nim(NimConfig), + #[default] + C, + Python, +} + +#[derive(Debug, Deserialize, PartialEq, Clone)] +pub struct DefaultPackageTypeConfig { + pub tarball_url: String, + pub tarball_hash: Option, + pub language_env: LanguageEnv, +} + +#[derive(Debug, Deserialize, PartialEq, Clone)] +pub struct SubModule { + pub commit: String, + pub path: String, +} + +#[derive(Debug, Deserialize, PartialEq, Clone)] +pub struct GitPackageTypeConfig { + pub git_tag: String, + pub git_url: Url, + pub submodules: Vec, + pub language_env: LanguageEnv, +} + +#[derive(Debug, Deserialize, PartialEq, Clone, Default)] +#[serde(tag = "package_type", rename_all = "lowercase")] +pub enum PackageType { + Default(DefaultPackageTypeConfig), + Git(GitPackageTypeConfig), + #[default] + Virtual, +} + +impl PackageType { + pub fn get_language_env(&self) -> Option<&LanguageEnv> { + match self { + PackageType::Default(config) => Some(&config.language_env), + PackageType::Git(config) => Some(&config.language_env), + PackageType::Virtual => None, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Clone)] +pub struct PackageFields { + pub spec_file: PathBuf, + pub package_name: String, + pub version_number: Version, + pub revision_number: String, + pub homepage: String, +} + + + +#[derive(Debug, Deserialize, PartialEq, Clone)] +pub struct BuildEnv { + pub codename: Distribution, + pub arch: Architecture, + pub pkg_builder_version: Version, + pub debcrafter_version: String, + pub sbuild_cache_dir: Option, + pub docker: Option, + pub run_lintian: Option, + pub run_piuparts: Option, + pub run_autopkgtest: Option, + pub lintian_version: Version, + pub piuparts_version: Version, + pub autopkgtest_version: AutopkgtestVersion, + pub sbuild_version: SbuildVersion, + pub workdir: PathBuf, +} + +#[derive(Debug, Deserialize, PartialEq, Clone)] +pub struct PkgConfig { + pub package_fields: PackageFields, + pub package_type: PackageType, + pub build_env: BuildEnv, + pub _config_root: Option, +} + +impl PkgConfig { + pub fn resolve_paths(mut self, config_root: PathBuf) -> Self { + self._config_root = Some(config_root.clone()); + // Set workdir to default if empty + let mut default_work_dir = PathBuf::from(WORKDIR_ROOT); + default_work_dir.push(self.build_env.codename.as_ref()); + + if self.build_env.workdir.as_os_str().is_empty() { + self.build_env.workdir = default_work_dir; + } + + // Expand workdir path + self.build_env.workdir = expand_path(&self.build_env.workdir, None); + + // Update spec file path to canonical form + self.package_fields.spec_file = config_root.join(&self.package_fields.spec_file); + + self + } +} diff --git a/workspace/packager_deb/src/configs/pkg_config_verify.rs b/workspace/packager_deb/src/configs/pkg_config_verify.rs new file mode 100644 index 00000000..8bc570af --- /dev/null +++ b/workspace/packager_deb/src/configs/pkg_config_verify.rs @@ -0,0 +1,17 @@ +use serde::Deserialize; + +#[derive(Debug, Deserialize, PartialEq, Clone, Default)] +pub struct PackageHash { + pub name: String, + pub hash: String, +} + +#[derive(Debug, Deserialize, PartialEq, Clone, Default)] +pub struct VerifyConfig { + pub package_hash: Vec, +} + +#[derive(Debug, Deserialize, PartialEq, Clone, Default)] +pub struct PkgVerifyConfig { + pub verify: VerifyConfig, +} diff --git a/workspace/packager_deb/src/configs/sbuild_version.rs b/workspace/packager_deb/src/configs/sbuild_version.rs new file mode 100644 index 00000000..10aac60a --- /dev/null +++ b/workspace/packager_deb/src/configs/sbuild_version.rs @@ -0,0 +1,134 @@ +use cargo_metadata::semver::Version as OriginalVersion; +use serde::{de, Deserialize, Deserializer, Serialize}; +use std::borrow::Cow; +use std::fmt; +use std::ops::{Deref, DerefMut}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct SbuildVersion { + inner: OriginalVersion, + original_string: Cow<'static, str>, +} + +impl fmt::Display for SbuildVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.original_string) + } +} + +impl SbuildVersion { + pub fn as_str(&self) -> &str { + &self.original_string + } + + // Extract version from the first line of a string + fn extract_version(s: &str) -> Option { + let first_line = s.lines().next()?; + + if let Some(start_idx) = first_line.find("sbuild (Debian sbuild) ") { + let start_pos = start_idx + "sbuild (Debian sbuild) ".len(); + if let Some(end_idx) = first_line[start_pos..].find(' ') { + return Some(first_line[start_pos..(start_pos + end_idx)].to_string()); + } else { + return Some(first_line[start_pos..].trim().to_string()); + } + } + None + } +} + +impl Serialize for SbuildVersion { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.original_string) + } +} + +impl Deref for SbuildVersion { + type Target = OriginalVersion; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for SbuildVersion { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl From for SbuildVersion { + fn from(version: OriginalVersion) -> Self { + let original_string = Cow::Owned(version.to_string()); + SbuildVersion { + inner: version, + original_string, + } + } +} + +impl<'a> TryFrom<&'a str> for SbuildVersion { + type Error = cargo_metadata::semver::Error; + fn try_from(s: &'a str) -> Result { + let version_str = if s.contains("sbuild") { + SbuildVersion::extract_version(s) + .unwrap_or_else(|| s.lines().next().unwrap_or(s).to_string()) + } else { + s.to_string() + }; + + let inner = OriginalVersion::parse(&version_str)?; + Ok(SbuildVersion { + inner, + original_string: Cow::Owned(version_str), + }) + } +} + +impl TryFrom for SbuildVersion { + type Error = cargo_metadata::semver::Error; + fn try_from(s: String) -> Result { + >::try_from(&s) + } +} + +impl<'de> Deserialize<'de> for SbuildVersion { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct SbuildVersionVisitor; + impl<'de> de::Visitor<'de> for SbuildVersionVisitor { + type Value = SbuildVersion; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str( + "a string containing a valid version, possibly in a multiline string", + ) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + let version_str = if value.contains("sbuild") { + SbuildVersion::extract_version(value).ok_or_else(|| { + de::Error::custom("Could not extract version from the first line") + })? + } else { + value.to_string() + }; + + let inner = OriginalVersion::parse(&version_str).map_err(de::Error::custom)?; + Ok(SbuildVersion { + inner, + original_string: Cow::Owned(version_str), + }) + } + } + + deserializer.deserialize_string(SbuildVersionVisitor) + } +} diff --git a/workspace/packager_deb/src/handler.rs b/workspace/packager_deb/src/handler.rs new file mode 100644 index 00000000..90ee2d9c --- /dev/null +++ b/workspace/packager_deb/src/handler.rs @@ -0,0 +1,90 @@ +use thiserror::Error; + +use types::{ + config::{Config, ConfigError, ConfigFile, ConfigType}, + debian::DebCommandPayload, + defaults::{CONFIG_FILE_NAME, VERIFY_CONFIG_FILE_NAME}, +}; + +use crate::{ + configs::{pkg_config::PkgConfig, pkg_config_verify::PkgVerifyConfig}, + misc::{build_pipeline::BuildError, validation::ValidationError}, + sbuild::{Sbuild, SbuildError}, +}; + +impl ConfigType for PkgVerifyConfig { + fn default_config_path() -> &'static str { + VERIFY_CONFIG_FILE_NAME + } +} + +impl ConfigType for PkgConfig { + fn default_config_path() -> &'static str { + CONFIG_FILE_NAME + } +} + +#[derive(Debug, Error)] +pub enum PackageError { + #[error(transparent)] + BuildError(#[from] BuildError), + #[error(transparent)] + SbuildError(#[from] SbuildError), + #[error(transparent)] + ConfigError(#[from] ConfigError), + #[error(transparent)] + ValidationError(#[from] ValidationError), +} + +pub fn dispatch_package_operation( + config: ConfigFile, + cmd_payload: DebCommandPayload, +) -> Result<(), PackageError> { + // ReParse config first + let mut pkg_config = + ConfigFile::::load_and_parse(Some(config.path.display().to_string()))? + .resolve_paths(config.path.parent().unwrap().to_path_buf()); + + // Apply CLI overrides if needed + if let DebCommandPayload::Package { + run_autopkgtest, + run_lintian, + run_piuparts, + } = &cmd_payload + { + if let Some(run_piuparts_value) = run_piuparts { + pkg_config.build_env.run_piuparts = Some(*run_piuparts_value); + } + if let Some(run_autopkgtest_value) = run_autopkgtest { + pkg_config.build_env.run_autopkgtest = Some(*run_autopkgtest_value); + } + if let Some(run_lintian_value) = run_lintian { + pkg_config.build_env.run_lintian = Some(*run_lintian_value); + } + } + + // do not run piuparts or autopkgtest on verify + if let DebCommandPayload::Verify { .. } = &cmd_payload { + pkg_config.build_env.run_piuparts = Some(false); + pkg_config.build_env.run_autopkgtest = Some(false); + } + + let packager: Sbuild = pkg_config.try_into()?; + match cmd_payload { + DebCommandPayload::Verify { + verify_config, + no_package, + } => { + let pkg_verify_config_file = + ConfigFile::::load_and_parse(verify_config)?; + packager.run_verify(pkg_verify_config_file, no_package.unwrap_or_default()) + } + DebCommandPayload::Lintian => packager.run_lintian(), + DebCommandPayload::Piuparts => packager.run_piuparts(), + DebCommandPayload::Autopkgtest => packager.run_autopkgtests(), + DebCommandPayload::Package { .. } => packager.run_package(), + DebCommandPayload::EnvCreate => packager.run_env_create(), + DebCommandPayload::EnvClean => packager.run_env_clean(), + }?; + Ok(()) +} diff --git a/workspace/packager_deb/src/installers/command_builder.rs b/workspace/packager_deb/src/installers/command_builder.rs new file mode 100644 index 00000000..92c7fb5d --- /dev/null +++ b/workspace/packager_deb/src/installers/command_builder.rs @@ -0,0 +1,39 @@ +pub struct CommandBuilder { + commands: Vec, +} + +impl CommandBuilder { + pub fn new() -> Self { + Self { + commands: Vec::new(), + } + } + + pub fn add(&mut self, cmd: impl Into) -> &mut Self { + self.commands.push(cmd.into()); + self + } + + pub fn add_with(&mut self, template: &str, replacement: &str) -> &mut Self { + let cmd = template.replace("{}", replacement); + self.commands.push(cmd); + self + } + + pub fn add_with_args(&mut self, template: &str, args: &[&str]) -> &mut Self { + // Format the string with positional arguments + let mut result = template.to_string(); + for arg in args { + // Replace the first occurrence of {} with the argument + if let Some(pos) = result.find("{}") { + result.replace_range(pos..pos + 2, arg); + } + } + self.commands.push(result); + self + } + + pub fn build(self) -> Vec { + self.commands + } +} diff --git a/workspace/packager_deb/src/installers/dotnet_installer.rs b/workspace/packager_deb/src/installers/dotnet_installer.rs new file mode 100644 index 00000000..1f4e1777 --- /dev/null +++ b/workspace/packager_deb/src/installers/dotnet_installer.rs @@ -0,0 +1,129 @@ +use std::{borrow::Cow, collections::HashMap}; + +use types::{config::Architecture, distribution::{Distribution, UbuntuCodename}}; + +use crate::configs::pkg_config::DotnetConfig; + +use super::{command_builder::CommandBuilder, language_installer::LanguageInstaller}; + +pub struct DotnetInstaller(pub(crate) DotnetConfig); + +impl LanguageInstaller for DotnetInstaller { + fn recipe(&self) -> Cow<'static, str> { + let recipe = include_str!("../recipes/dotnet_installer.sh"); + Cow::Borrowed(&recipe) + } + + fn substitutions(&self) -> HashMap<&str, &str> { + let subs = HashMap::new(); + subs + } + fn get_build_deps(&self, arch: &Architecture, codename: &Distribution) -> Vec { + let dotnet_packages = &self.0.dotnet_packages; + let deps = self.0.deps.clone().unwrap_or_default(); + let mut builder = CommandBuilder::new(); + + if self.0.use_backup_version { + builder + .add("apt install -y wget") + .add("apt install -y libicu-dev"); + + for package in deps { + builder.add_with("apt install -y {}", &package); + } + + for package in dotnet_packages { + builder + .add_with("cd /tmp && wget -q {}", &package.url.as_ref()) + .add_with("cd /tmp && ls && dpkg -i {}.deb", &package.name) + .add_with("cd /tmp && ls && sha1sum {}.deb", &package.name) + .add_with_args( + "cd /tmp && echo {} {}.deb > hash_file.txt && cat hash_file.txt", + &[&package.hash, &package.name], + ) + .add("cd /tmp && sha1sum -c hash_file.txt"); + } + + builder.add("dotnet --version").add("apt remove -y wget"); + } else { + match codename { + Distribution::Debian(_) | Distribution::Ubuntu(UbuntuCodename::Jammy) => { + builder + .add("apt install -y wget") + .add("cd /tmp && wget -q https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb -O packages-microsoft-prod.deb") + .add("cd /tmp && dpkg -i packages-microsoft-prod.deb") + .add("apt update -y"); + + for package in dotnet_packages { + let pkg = transform_name(&package.name, arch); + builder + .add_with("cd /tmp && wget -q {}", &package.url.as_ref()) + .add_with("cd /tmp && apt install -y --allow-downgrades {}", &pkg) + .add_with("cd /tmp && apt download -y {}", &pkg) + .add_with("cd /tmp && ls && sha1sum {}.deb", &package.name) + .add_with_args( + "cd /tmp && echo {} {}.deb >> hash_file.txt && cat hash_file.txt", + &[&package.hash, &package.name], + ) + .add("cd /tmp && sha1sum -c hash_file.txt"); + } + + builder.add("dotnet --version").add("apt remove -y wget"); + } + Distribution::Ubuntu(UbuntuCodename::Noble) => { + builder + .add("apt-get install software-properties-common -y") + .add("add-apt-repository ppa:dotnet/backports") + .add("apt-get update -y") + .add("apt install -y wget"); + + for package in dotnet_packages { + let pkg = transform_name(&package.name, arch); + builder + .add_with("cd /tmp && wget -q {}", &package.url.as_ref()) + .add_with("cd /tmp && apt install -y {}", &pkg) + .add_with("cd /tmp && apt download -y {}", &pkg) + .add_with("cd /tmp && ls && sha1sum {}.deb", &package.name) + .add_with_args( + "cd /tmp && echo {} {}.deb >> hash_file.txt && cat hash_file.txt", + &[&package.hash, &package.name], + ) + .add("cd /tmp && sha1sum -c hash_file.txt"); + } + + builder.add("dotnet --version").add("apt remove -y wget"); + } + } + } + + builder.build() + } + fn get_test_deps(&self, codename: &Distribution) -> Vec { + let mut builder = CommandBuilder::new(); + + match codename { + Distribution::Debian(_) | Distribution::Ubuntu(UbuntuCodename::Jammy) => { + builder + .add("apt install -y wget") + .add("cd /tmp && wget https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb -O packages-microsoft-prod.deb") + .add("cd /tmp && dpkg -i packages-microsoft-prod.deb") + .add("apt-get update -y") + .add("apt remove -y wget"); + + builder.build() + } + Distribution::Ubuntu(UbuntuCodename::Noble) => { + vec![] + } + } + } +} + +fn transform_name(input: &str, arch: &Architecture) -> String { + if let Some(pos) = input.find(format!("_{}", arch).as_str()) { + let trimmed = &input[..pos]; + trimmed.replace('_', "=") + } else { + input.replace('_', "=") + } +} diff --git a/workspace/packager_deb/src/installers/empty_installer.rs b/workspace/packager_deb/src/installers/empty_installer.rs new file mode 100644 index 00000000..6552bc6d --- /dev/null +++ b/workspace/packager_deb/src/installers/empty_installer.rs @@ -0,0 +1,21 @@ +use std::{borrow::Cow, collections::HashMap}; + +use types::distribution::Distribution; + +use super::language_installer::LanguageInstaller; + +pub struct EmptyInstaller; + +impl LanguageInstaller for EmptyInstaller { + fn get_test_deps(&self, _codename: &Distribution) -> Vec { + vec![] + } + + fn recipe(&self) -> Cow<'static, str> { + Cow::Borrowed("") + } + + fn substitutions(&self) -> HashMap<&str, &str> { + HashMap::new() + } +} diff --git a/workspace/packager_deb/src/installers/go_installer.rs b/workspace/packager_deb/src/installers/go_installer.rs new file mode 100644 index 00000000..1eaaf108 --- /dev/null +++ b/workspace/packager_deb/src/installers/go_installer.rs @@ -0,0 +1,26 @@ +use std::{borrow::Cow, collections::HashMap}; + +use types::distribution::Distribution; + +use crate::configs::pkg_config::GoConfig; + +use super::language_installer::LanguageInstaller; + +pub struct GoInstaller(pub(crate) GoConfig); + +impl LanguageInstaller for GoInstaller { + fn recipe(&self) -> Cow<'static, str> { + let recipe = include_str!("../recipes/go_installer.sh"); + Cow::Borrowed(recipe) + } + + fn substitutions(&self) -> HashMap<&str, &str> { + let mut subs = HashMap::new(); + subs.insert("${go_binary_url}", self.0.go_binary_url.as_str()); + subs.insert("${go_binary_checksum}", self.0.go_binary_checksum.as_str()); + subs + } + fn get_test_deps(&self, _codename: &Distribution) -> Vec { + vec![] + } +} diff --git a/workspace/packager_deb/src/installers/java_installer.rs b/workspace/packager_deb/src/installers/java_installer.rs new file mode 100644 index 00000000..b74875f4 --- /dev/null +++ b/workspace/packager_deb/src/installers/java_installer.rs @@ -0,0 +1,47 @@ +use std::{borrow::Cow, collections::HashMap}; + +use types::distribution::Distribution; + +use crate::configs::pkg_config::JavaConfig; + +use super::language_installer::LanguageInstaller; + +pub struct JavaInstaller(pub(crate) JavaConfig); + +impl LanguageInstaller for JavaInstaller { + fn recipe(&self) -> Cow<'static, str> { + let java_installer = include_str!("../recipes/java_installer.sh"); + if let Some(_) = &self.0.gradle { + let java_gradle_installer = include_str!("../recipes/java_gradle_installer.sh"); + let installer = java_installer.to_string() + java_gradle_installer; + Cow::Owned(installer) + } else { + Cow::Borrowed(java_installer) + } + } + + fn substitutions(&self) -> HashMap<&str, &str> { + let mut subs = HashMap::new(); + subs.insert("${jdk_version}", self.0.jdk_version.as_str()); + subs.insert("${jdk_binary_url}", &self.0.jdk_binary_url.as_str()); + subs.insert( + "${jdk_binary_checksum}", + &self.0.jdk_binary_checksum.as_str(), + ); + if let Some(gradle_config) = &self.0.gradle { + let gradle_version = &gradle_config.gradle_version; + let gradle_binary_url = &gradle_config.gradle_binary_url; + let gradle_binary_checksum = &gradle_config.gradle_binary_checksum; + subs.insert("${gradle_version}", &gradle_version.as_str()); + subs.insert("${gradle_binary_url}", &gradle_binary_url.as_str()); + subs.insert( + "${gradle_binary_checksum}", + &gradle_binary_checksum.as_str(), + ); + } + subs + } + fn get_test_deps(&self, _codename: &Distribution) -> Vec { + vec![] + } +} diff --git a/workspace/packager_deb/src/installers/language_installer.rs b/workspace/packager_deb/src/installers/language_installer.rs new file mode 100644 index 00000000..67c43885 --- /dev/null +++ b/workspace/packager_deb/src/installers/language_installer.rs @@ -0,0 +1,50 @@ +use types::{config::Architecture, distribution::Distribution}; + +use super::{ + command_builder::CommandBuilder, dotnet_installer::DotnetInstaller, + empty_installer::EmptyInstaller, go_installer::GoInstaller, java_installer::JavaInstaller, + nim_installer::NimInstaller, node_installer::NodeInstaller, rust_installer::RustInstaller, +}; +use crate::configs::pkg_config::LanguageEnv; +use std::{borrow::Cow, collections::HashMap}; + +pub trait LanguageInstaller { + fn recipe(&self) -> Cow<'static, str>; + fn substitutions(&self) -> HashMap<&str, &str>; + + fn get_build_deps(&self, _arch: &Architecture, _codename: &Distribution) -> Vec { + let mut builder = CommandBuilder::new(); + let recipe = self.recipe(); + let substitutions = self.substitutions(); + + for line in recipe.lines() { + let command = line.trim(); + let mut processed_command = String::from(command); + + for (placeholder, value) in &substitutions { + processed_command = processed_command.replace(placeholder, value); + } + + builder.add(&processed_command); + } + + builder.build() + } + fn get_test_deps(&self, codename: &Distribution) -> Vec; +} + +impl From for Box { + fn from(lang_env: LanguageEnv) -> Self { + match lang_env { + LanguageEnv::Rust(config) => Box::new(RustInstaller(config.clone())), + LanguageEnv::Go(config) => Box::new(GoInstaller(config.clone())), + LanguageEnv::JavaScript(config) | LanguageEnv::TypeScript(config) => { + Box::new(NodeInstaller(config.clone())) + } + LanguageEnv::Java(config) => Box::new(JavaInstaller(config.clone())), + LanguageEnv::Dotnet(config) => Box::new(DotnetInstaller(config.clone())), + LanguageEnv::Nim(config) => Box::new(NimInstaller(config.clone())), + _ => Box::new(EmptyInstaller), + } + } +} diff --git a/workspace/packager_deb/src/installers/mod.rs b/workspace/packager_deb/src/installers/mod.rs new file mode 100644 index 00000000..f80b6153 --- /dev/null +++ b/workspace/packager_deb/src/installers/mod.rs @@ -0,0 +1,9 @@ +mod command_builder; +mod dotnet_installer; +mod empty_installer; +mod go_installer; +mod java_installer; +pub mod language_installer; +mod nim_installer; +mod node_installer; +mod rust_installer; diff --git a/workspace/packager_deb/src/installers/nim_installer.rs b/workspace/packager_deb/src/installers/nim_installer.rs new file mode 100644 index 00000000..1ea102d4 --- /dev/null +++ b/workspace/packager_deb/src/installers/nim_installer.rs @@ -0,0 +1,30 @@ +use std::{borrow::Cow, collections::HashMap}; + +use types::distribution::Distribution; + +use crate::configs::pkg_config::NimConfig; + +use super::language_installer::LanguageInstaller; + +pub struct NimInstaller(pub(crate) NimConfig); + +impl LanguageInstaller for NimInstaller { + fn recipe(&self) -> Cow<'static, str> { + let recipe = include_str!("../recipes/nim_installer.sh"); + Cow::Borrowed(recipe) + } + + fn substitutions(&self) -> HashMap<&str, &str> { + let mut subs = HashMap::new(); + subs.insert("${nim_binary_url}", self.0.nim_binary_url.as_str()); + subs.insert("${nim_version}", &self.0.nim_version.as_str()); + subs.insert( + "${nim_version_checksum}", + &self.0.nim_version_checksum.as_str(), + ); + subs + } + fn get_test_deps(&self, _codename: &Distribution) -> Vec { + vec![] + } +} diff --git a/workspace/packager_deb/src/installers/node_installer.rs b/workspace/packager_deb/src/installers/node_installer.rs new file mode 100644 index 00000000..3f2b52f7 --- /dev/null +++ b/workspace/packager_deb/src/installers/node_installer.rs @@ -0,0 +1,38 @@ +use std::{borrow::Cow, collections::HashMap}; + +use types::distribution::Distribution; + +use crate::configs::pkg_config::JavascriptConfig; + +use super::language_installer::LanguageInstaller; + +pub struct NodeInstaller(pub(crate) JavascriptConfig); +impl LanguageInstaller for NodeInstaller { + fn recipe(&self) -> Cow<'static, str> { + let recipe = include_str!("../recipes/node_installer.sh"); + if let Some(_) = &self.0.yarn_version { + let yarn_installer = include_str!("../recipes/yarn_installer.sh"); + let installer = recipe.to_string() + yarn_installer; + Cow::Owned(installer) + } else { + Cow::Borrowed(recipe) + } + } + + fn substitutions(&self) -> HashMap<&str, &str> { + let mut subs = HashMap::new(); + subs.insert( + "${node_binary_checksum}", + self.0.node_binary_checksum.as_str(), + ); + subs.insert("${node_binary_url}", &self.0.node_binary_url.as_str()); + subs.insert("${node_version}", &&self.0.node_version.as_str()); + if let Some(yarn_version) = &self.0.yarn_version { + subs.insert("${yarn_version}", &yarn_version.as_str()); + } + subs + } + fn get_test_deps(&self, _codename: &Distribution) -> Vec { + vec![] + } +} diff --git a/workspace/packager_deb/src/installers/rust_installer.rs b/workspace/packager_deb/src/installers/rust_installer.rs new file mode 100644 index 00000000..ce8f3886 --- /dev/null +++ b/workspace/packager_deb/src/installers/rust_installer.rs @@ -0,0 +1,30 @@ +use std::{borrow::Cow, collections::HashMap}; + +use types::distribution::Distribution; + +use crate::configs::pkg_config::RustConfig; + +use super::language_installer::LanguageInstaller; + +pub struct RustInstaller(pub(crate) RustConfig); + +impl LanguageInstaller for RustInstaller { + fn recipe(&self) -> Cow<'static, str> { + let recipe = include_str!("../recipes/rust_installer.sh"); + Cow::Borrowed(recipe) + } + + fn substitutions(&self) -> HashMap<&str, &str> { + let mut subs = HashMap::new(); + subs.insert("${rust_binary_url}", self.0.rust_binary_url.as_str()); + subs.insert( + "${rust_binary_gpg_asc}", + self.0.rust_binary_gpg_asc.as_str(), + ); + subs + } + + fn get_test_deps(&self, _codename: &Distribution) -> Vec { + vec![] // Rust compiles to binary, no test deps needed + } +} diff --git a/workspace/packager_deb/src/misc/build_pipeline.rs b/workspace/packager_deb/src/misc/build_pipeline.rs new file mode 100644 index 00000000..3360e201 --- /dev/null +++ b/workspace/packager_deb/src/misc/build_pipeline.rs @@ -0,0 +1,108 @@ +use std::path::PathBuf; + +use debian::debcrafter::DebcrafterCmdError; +use thiserror::Error; + +use crate::{ + configs::pkg_config::SubModule, + steps::{create_empty_tar::CreateEmptyTarError, dowload_git::DownloadGitError}, +}; + +#[derive(Debug, Default, Clone)] +pub struct BuildContext { + pub tarball_url: String, + pub tarball_hash: String, + pub tarball_path: PathBuf, + pub build_files_dir: PathBuf, + pub debcrafter_version: String, + pub homepage: String, + pub build_artifacts_dir: PathBuf, + pub spec_file: PathBuf, + pub src_dir: PathBuf, + // only for git package + pub package_name: String, + pub git_tag: String, + pub git_url: String, + pub submodules: Vec, +} + +#[derive(Error, Debug)] +pub enum BuildError { + #[error("Command execution failed: {0}")] + CommandFailed(String), + + #[error("Download failed")] + DownloadFailed, + + #[error("File copy failed: {0}")] + FileCopyFailed(String), + + #[error(transparent)] + IoError(#[from] std::io::Error), + + #[error("Failed to open tarball: {0}")] + TarballOpenError(String), + + #[error("Failed to read tarball: {0}")] + TarballReadError(String), + + #[error("Checksum verification failed: hashes do not match")] + HashMismatchError, + + #[error("Extraction error: {0}")] + ExtractionError(String), + + #[error(transparent)] + DebcrafterError(#[from] DebcrafterCmdError), + + #[error("Failed to copy src directory: {0}")] + CopyDirectory(String), + + #[error("Failed to get debian/rules permission")] + RulesPermissionGet, + + #[error("Failed to set debian/rules permission")] + RulesPermissionSet, + + #[error("Home directory not found")] + HomeDirNotFound, + + #[error("Failed to create ~/.sbuildrc: {0}")] + FileCreationError(String), + + #[error("Failed to write to ~/.sbuildrc: {0}")] + FileWriteError(String), + + #[error(transparent)] + DownloadGitStepError(#[from] DownloadGitError), + + #[error(transparent)] + CreateEmptyTarStepError(#[from] CreateEmptyTarError), +} + +pub trait BuildStep { + fn step(&self) -> Result<(), BuildError>; +} + +#[derive(Default)] +pub struct BuildPipeline { + handlers: Vec>, +} + +impl BuildPipeline { + pub fn new() -> Self { + Self::default() + } + + pub fn add_step(&mut self, handler: T) -> &mut Self { + self.handlers.push(Box::new(handler)); + self + } + + pub fn execute(&self) -> Result<(), BuildError> { + for handler in &self.handlers { + handler.step()?; + } + Ok(()) + } +} diff --git a/workspace/packager_deb/src/misc/distribution.rs b/workspace/packager_deb/src/misc/distribution.rs new file mode 100644 index 00000000..c57f3db3 --- /dev/null +++ b/workspace/packager_deb/src/misc/distribution.rs @@ -0,0 +1,22 @@ +use types::distribution::Distribution; + +pub trait DistributionTrait { + fn get_keyring(&self) -> &str; + fn get_repo_url(&self) -> &str; +} + +impl DistributionTrait for Distribution { + fn get_keyring(&self) -> &str { + match &self { + Distribution::Debian(_) => "/usr/share/keyrings/debian-archive-keyring.gpg", + Distribution::Ubuntu(_) => "/usr/share/keyrings/ubuntu-archive-keyring.gpg", + } + } + + fn get_repo_url(&self) -> &str { + match &self { + Distribution::Debian(_) => "http://deb.debian.org/debian", + Distribution::Ubuntu(_) => "http://archive.ubuntu.com/ubuntu", + } + } +} diff --git a/workspace/packager_deb/src/misc/mod.rs b/workspace/packager_deb/src/misc/mod.rs new file mode 100644 index 00000000..db9c9197 --- /dev/null +++ b/workspace/packager_deb/src/misc/mod.rs @@ -0,0 +1,5 @@ +pub mod build_pipeline; +pub mod distribution; +pub mod sbuild_pipelines; +pub mod utils; +pub mod validation; diff --git a/workspace/packager_deb/src/misc/sbuild_pipelines.rs b/workspace/packager_deb/src/misc/sbuild_pipelines.rs new file mode 100644 index 00000000..19d379f9 --- /dev/null +++ b/workspace/packager_deb/src/misc/sbuild_pipelines.rs @@ -0,0 +1,107 @@ +use crate::{ + misc::build_pipeline::{BuildContext, BuildError, BuildPipeline}, + steps::{ + create_debian_dir::CreateDebianDir, create_empty_tar::CreateEmptyTar, + dowload_git::DownloadGit, download_source::DownloadSource, extract_source::ExtractSource, + package_dir_setup::PackageDirSetup, patch_source::PatchSource, setup_sbuild::SetupSbuild, + verify_hash::VerifyHash, + }, +}; + +#[derive(Default)] +pub struct SbuildSourcePipeline { + context: BuildContext, +} + +impl SbuildSourcePipeline { + pub fn new(context: BuildContext) -> Self { + SbuildSourcePipeline { context } + } + + pub fn execute(self) -> Result<(), BuildError> { + let mut pipeline = BuildPipeline::new(); + let package_dir_handle = PackageDirSetup::from(self.context.clone()); + let download_source_handle = DownloadSource::from(self.context.clone()); + let verify_hash_handle = VerifyHash::from(self.context.clone()); + let extract_source_handle = ExtractSource::from(self.context.clone()); + let create_deb_dir: CreateDebianDir = CreateDebianDir::from(self.context.clone()); + let patch_source_handle = PatchSource::from(self.context.clone()); + let setup_sbuild_handle = SetupSbuild::from(self.context.clone()); + + pipeline + .add_step(package_dir_handle) + .add_step(download_source_handle) + .add_step(verify_hash_handle) + .add_step(extract_source_handle) + .add_step(create_deb_dir) + .add_step(patch_source_handle) + .add_step(setup_sbuild_handle); + + pipeline.execute()?; + Ok(()) + } +} + +#[derive(Default)] +pub struct SbuildGitPipeline { + context: BuildContext, +} + +impl SbuildGitPipeline { + pub fn new(context: BuildContext) -> Self { + SbuildGitPipeline { context } + } + + pub fn execute(self) -> Result<(), BuildError> { + let mut pipeline = BuildPipeline::new(); + let package_dir_handle = PackageDirSetup::from(self.context.clone()); + let download_git_step = DownloadGit::from(self.context.clone()); + let extract_source_handle = ExtractSource::from(self.context.clone()); + let create_deb_dir: CreateDebianDir = CreateDebianDir::from(self.context.clone()); + let patch_source_handle = PatchSource::from(self.context.clone()); + let setup_sbuild_handle = SetupSbuild::from(self.context.clone()); + + pipeline + .add_step(package_dir_handle) + .add_step(download_git_step) + .add_step(extract_source_handle) + .add_step(create_deb_dir) + .add_step(patch_source_handle) + .add_step(setup_sbuild_handle); + + pipeline.execute()?; + Ok(()) + } +} + +#[derive(Default)] +pub struct SbuildVirtualPipeline { + context: BuildContext, +} + +impl SbuildVirtualPipeline { + pub fn new(context: BuildContext) -> Self { + SbuildVirtualPipeline { context } + } + + pub fn execute(self) -> Result<(), BuildError> { + let mut pipeline = BuildPipeline::new(); + let package_dir_handle = PackageDirSetup::from(self.context.clone()); + let empty_tar_handle = CreateEmptyTar::from(self.context.clone()); + let extract_source_handle = ExtractSource::from(self.context.clone()); + let create_deb_dir: CreateDebianDir = CreateDebianDir::from(self.context.clone()); + let patch_source_handle = PatchSource::from(self.context.clone()); + let setup_sbuild_handle = SetupSbuild::from(self.context.clone()); + + pipeline + .add_step(package_dir_handle) + .add_step(empty_tar_handle) + .add_step(extract_source_handle) + .add_step(create_deb_dir) + .add_step(patch_source_handle) + .add_step(setup_sbuild_handle); + + pipeline.execute()?; + Ok(()) + } +} diff --git a/workspace/packager_deb/src/misc/utils.rs b/workspace/packager_deb/src/misc/utils.rs new file mode 100644 index 00000000..0ac3892c --- /dev/null +++ b/workspace/packager_deb/src/misc/utils.rs @@ -0,0 +1,91 @@ +use std::{ + env, fs::{self, create_dir_all, remove_dir_all, remove_file}, path::{Path, PathBuf} +}; + +use crate::sbuild::SbuildError; +use sha1::{Digest, Sha1}; + +pub fn calculate_sha1(data: &[u8]) -> Result { + let mut hasher = Sha1::new(); + hasher.update(data); + Ok(hasher + .finalize() + .iter() + .map(|b| format!("{:02x}", b)) + .collect()) +} + +pub fn ensure_parent_dir>(path: T) -> Result<(), SbuildError> { + let parent = Path::new(path.as_ref()) + .parent() + .ok_or(SbuildError::GenericError(format!( + "Invalid path: {:?}", + path.as_ref() + )))?; + create_dir_all(parent)?; + Ok(()) +} +pub fn remove_file_or_directory(path: &PathBuf, is_dir: bool) -> Result<(), SbuildError> { + if Path::new(path).exists() { + if is_dir { + remove_dir_all(path)?; + } else { + remove_file(path)?; + } + } + Ok(()) +} + +pub fn expand_path(dir: &PathBuf, dir_to_expand: Option<&PathBuf>) -> PathBuf { + if dir.to_string_lossy().starts_with('~') { + let dir_str = dir.to_string_lossy(); + PathBuf::from(shellexpand::tilde(&dir_str).to_string()) + } else if dir.is_absolute() { + dir.clone() + } else { + let parent_dir = match dir_to_expand { + None => env::current_dir().unwrap(), + Some(path) => path.clone(), + }; + + let path = parent_dir.join(dir); + fs::canonicalize(path.clone()).unwrap_or(path) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn expand_path_expands_tilde_correctly() { + let tilde = PathBuf::from("~"); + let result = expand_path(&tilde, None); + assert_ne!(result, tilde); + assert!(!result.display().to_string().contains('~')); + } + + #[test] + fn expand_path_handles_absolute_paths() { + let absolute_path = PathBuf::from("/absolute/path"); + let result = expand_path(&absolute_path, None); + assert_eq!(result, absolute_path); + } + + #[test] + fn expand_path_expands_relative_paths_with_parent() { + let file = PathBuf::from("somefile"); + let mut tmp = PathBuf::from("/tmp"); + let result = expand_path(&file, Some(&tmp)); + tmp.push(file); + assert_eq!(result, tmp); + } + + #[test] + fn expand_path_expands_relative_paths_without_parent() { + let file = PathBuf::from("somefile"); + + let result = expand_path(&file, None); + assert!(result.display().to_string().starts_with('/')); + } +} \ No newline at end of file diff --git a/workspace/packager_deb/src/misc/validation.rs b/workspace/packager_deb/src/misc/validation.rs new file mode 100644 index 00000000..26bc18e5 --- /dev/null +++ b/workspace/packager_deb/src/misc/validation.rs @@ -0,0 +1,19 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum ValidationError { + #[error("Field: '{0}' cannot be empty")] + EmptyField(String), + + #[error("Validation failed: {0}")] + Multiple(String), + + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + + #[error("TOML parsing error: {0}")] + TomlParse(#[from] toml::de::Error), + + #[error("Package hash cannot be empty")] + EmptyPackageHash, +} diff --git a/workspace/packager_deb/src/mod.rs b/workspace/packager_deb/src/mod.rs new file mode 100644 index 00000000..aa588967 --- /dev/null +++ b/workspace/packager_deb/src/mod.rs @@ -0,0 +1,8 @@ +mod configs; +pub mod handler; +mod installers; +mod misc; +pub mod sbuild; +mod sbuild_args; +mod steps; +mod tools; diff --git a/workspace/packager_deb/src/recipes/dotnet_installer.sh b/workspace/packager_deb/src/recipes/dotnet_installer.sh new file mode 100644 index 00000000..e69de29b diff --git a/workspace/packager_deb/src/recipes/go_installer.sh b/workspace/packager_deb/src/recipes/go_installer.sh new file mode 100644 index 00000000..6df320fb --- /dev/null +++ b/workspace/packager_deb/src/recipes/go_installer.sh @@ -0,0 +1,9 @@ +apt install -y wget +cd /tmp && wget -q -O go.tar.gz ${go_binary_url} +cd /tmp && echo "${go_binary_checksum} go.tar.gz" >> hash_file.txt && cat hash_file.txt +cd /tmp && sha256sum -c hash_file.txt +cd /tmp && rm -rf /usr/local/go && mkdir /usr/local/go && tar -C /usr/local -xzf go.tar.gz +ln -s /usr/local/go/bin/go /usr/bin/go +go version +chmod -R a+rwx /usr/local/go/pkg +apt remove -y wget diff --git a/workspace/packager_deb/src/recipes/java_gradle_installer.sh b/workspace/packager_deb/src/recipes/java_gradle_installer.sh new file mode 100644 index 00000000..35a84f06 --- /dev/null +++ b/workspace/packager_deb/src/recipes/java_gradle_installer.sh @@ -0,0 +1,9 @@ +apt install -y wget unzip +mkdir -p /opt/lib/gradle-${gradle_version} +cd /tmp && wget -q --output-document gradle.tar.gz ${gradle_binary_url} +cd /tmp && echo "${gradle_binary_checksum} gradle.tar.gz" > hash_file.txt && cat hash_file.txt +cd /tmp && sha256sum -c hash_file.txt +cd /tmp && unzip gradle.tar.gz && mv gradle-${gradle_version} /opt/lib +ln -s /opt/lib/gradle-${gradle_version}/bin/gradle /usr/bin/gradle +gradle -version +apt remove -y wget \ No newline at end of file diff --git a/workspace/packager_deb/src/recipes/java_installer.sh b/workspace/packager_deb/src/recipes/java_installer.sh new file mode 100644 index 00000000..6404d4e3 --- /dev/null +++ b/workspace/packager_deb/src/recipes/java_installer.sh @@ -0,0 +1,10 @@ +apt install -y wget +mkdir -p /opt/lib/jvm/jdk-${jdk_version}-oracle && mkdir -p /usr/lib/jvm +cd /tmp && wget -q --output-document jdk.tar.gz ${jdk_binary_url} +cd /tmp && echo "${jdk_binary_checksum} jdk.tar.gz" >>hash_file.txt && cat hash_file.txt +cd /tmp && sha256sum -c hash_file.txt +cd /tmp && tar -zxf jdk.tar.gz -C /opt/lib/jvm/jdk-${jdk_version}-oracle --strip-components=1 +ln -s /opt/lib/jvm/jdk-${jdk_version}-oracle/bin/java /usr/bin/java +ln -s /opt/lib/jvm/jdk-${jdk_version}-oracle/bin/javac /usr/bin/javac +java -version +apt remove -y wget diff --git a/workspace/packager_deb/src/recipes/nim_installer.sh b/workspace/packager_deb/src/recipes/nim_installer.sh new file mode 100644 index 00000000..60821ac0 --- /dev/null +++ b/workspace/packager_deb/src/recipes/nim_installer.sh @@ -0,0 +1,11 @@ +apt install -y wget +rm -rf /tmp/nim-${nim_version} && rm -rf /usr/lib/nim/nim-${nim_version} && rm -rf /opt/lib/nim/nim-${nim_version} && mkdir /tmp/nim-${nim_version} +mkdir -p /opt/lib/nim && mkdir -p /usr/lib/nim +cd /tmp && wget -q ${nim_binary_url} +cd /tmp && echo ${nim_version_checksum} >> hash_file.txt && cat hash_file.txt +cd /tmp && sha256sum -c hash_file.txt +cd /tmp && tar xJf nim-${nim_version}-linux_x64.tar.xz -C nim-${nim_version} --strip-components=1 +cd /tmp && mv nim-${nim_version} /opt/lib/nim +ln -s /opt/lib/nim/nim-${nim_version}/bin/nim /usr/bin/nim +nim --version +apt remove -y wget diff --git a/workspace/packager_deb/src/recipes/node_installer.sh b/workspace/packager_deb/src/recipes/node_installer.sh new file mode 100644 index 00000000..8932bd56 --- /dev/null +++ b/workspace/packager_deb/src/recipes/node_installer.sh @@ -0,0 +1,13 @@ +apt install -y wget +cd /tmp && wget -q -O node.tar.gz ${node_binary_url} +cd /tmp && echo "${node_binary_checksum} node.tar.gz" >> hash_file.txt && cat hash_file.txt +cd /tmp && sha256sum -c hash_file.txt +cd /tmp && rm -rf /usr/share/node && mkdir /usr/share/node && tar -C /usr/share/node -xzf node.tar.gz --strip-components=1 +ls -l /usr/share/node/bin +ln -s /usr/share/node/bin/node /usr/bin/node +ln -s /usr/share/node/bin/npm /usr/bin/npm +ln -s /usr/share/node/bin/npx /usr/bin/npx +ln -s /usr/share/node/bin/corepack /usr/bin/corepack +apt remove -y wget +node --version +npm --version diff --git a/workspace/packager_deb/src/recipes/rust_installer.sh b/workspace/packager_deb/src/recipes/rust_installer.sh new file mode 100644 index 00000000..ba3b65e1 --- /dev/null +++ b/workspace/packager_deb/src/recipes/rust_installer.sh @@ -0,0 +1,8 @@ +apt install -y wget gpg gpg-agent +cd /tmp && wget -q -O package.tar.xz ${rust_binary_url} +cd /tmp && echo "${rust_binary_gpg_asc}" >> package.tar.xz.asc +wget -qO- https://keybase.io/rust/pgp_keys.asc | gpg --import +cd /tmp && gpg --verify package.tar.xz.asc package.tar.xz +cd /tmp && tar xvJf package.tar.xz -C . --strip-components=1 +cd /tmp && /bin/bash install.sh +apt remove -y wget gpg gpg-agent \ No newline at end of file diff --git a/workspace/packager_deb/src/recipes/yarn_installer.sh b/workspace/packager_deb/src/recipes/yarn_installer.sh new file mode 100644 index 00000000..53fedf01 --- /dev/null +++ b/workspace/packager_deb/src/recipes/yarn_installer.sh @@ -0,0 +1,3 @@ +npm install --global yarn@${yarn_version} +ln -s /usr/share/node/bin/yarn /usr/bin/yarn +yarn --version \ No newline at end of file diff --git a/workspace/packager_deb/src/sbuild.rs b/workspace/packager_deb/src/sbuild.rs new file mode 100644 index 00000000..896eeaa9 --- /dev/null +++ b/workspace/packager_deb/src/sbuild.rs @@ -0,0 +1,310 @@ +use crate::configs::pkg_config::PkgConfig; +use crate::configs::pkg_config_verify::PkgVerifyConfig; +use crate::misc::build_pipeline::BuildError; +use crate::misc::distribution::DistributionTrait; +use crate::misc::utils::{calculate_sha1, ensure_parent_dir, remove_file_or_directory}; +use crate::sbuild_args::SbuildArgs; +use crate::tools::autopkgtest_tool::AutopkgtestTool; +use crate::tools::lintian_tool::LintianTool; +use crate::tools::piuparts_tool::PiupartsTool; +use crate::tools::sbuild_tool::SbuildTool; +use crate::tools::tool_runner::ToolRunner; +use debian::autopkgtest::AutopkgtestError; +use debian::autopkgtest_image::AutopkgtestImageError; +use debian::execute::Execute; +use debian::lintian::LintianError; +use debian::piuparts::PiupartsError; +use debian::sbuild::SbuildCmdError; +use debian::sbuild_create_chroot::{SbuildCreateChroot, SbuildCreateChrootError}; +use log::info; +use rand::random; +use std::path::Path; +use std::{env, fs}; +use thiserror::Error; + +pub struct Sbuild { + args: SbuildArgs, +} + +impl TryFrom for Sbuild { + type Error = SbuildError; + + fn try_from(config: PkgConfig) -> Result { + let args: SbuildArgs = SbuildArgs::try_from(config)?; + Ok(Sbuild { args }) + } +} + +#[derive(Debug, Error)] +pub enum SbuildError { + #[error(transparent)] + IoError(#[from] std::io::Error), + #[error(transparent)] + SbuildCreateChrootError(#[from] SbuildCreateChrootError), + + #[error(transparent)] + AutopkgtestImageError(#[from] AutopkgtestImageError), + + #[error(transparent)] + PiupartsError(#[from] PiupartsError), + + #[error(transparent)] + LintianError(#[from] LintianError), + + #[error(transparent)] + AutopkgtestError(#[from] AutopkgtestError), + + #[error(transparent)] + SbuildCmdError(#[from] SbuildCmdError), + + #[error("Verification error: {0}")] + VerificationError(String), + + #[error("{0}")] + GenericError(String), + + #[error(transparent)] + SemverError(#[from] cargo_metadata::semver::Error), + + #[error(transparent)] + BuildError(#[from] BuildError), +} + +impl Sbuild { + pub fn run_env_clean(&self) -> Result<(), SbuildError> { + let cache_file = self.args.get_cache_file(); + info!("Cleaning cached build: {:?}", cache_file); + remove_file_or_directory(&cache_file, false)?; + Ok(()) + } + + pub fn run_env_create(&self) -> Result<(), SbuildError> { + let temp_dir = env::temp_dir().join(format!("temp_{}", random::())); + fs::create_dir(&temp_dir)?; + + let cache_file = self.args.get_cache_file(); + ensure_parent_dir(&cache_file)?; + + let codename = &self.args.codename(); + + SbuildCreateChroot::new() + .chroot_mode("unshare") + .make_tarball() + .cache_file(&cache_file) + .codename(codename) + .temp_dir(&temp_dir) + .repo_url(codename.get_repo_url()) + .execute()?; + + Ok(()) + } + + pub fn run_package(&self) -> Result<(), SbuildError> { + let args = self.args.get_sbuild_tool_args(); + + let tool = SbuildTool::new(args); + ToolRunner::new().run_tool(tool)?; + if self.args.run_piuparts() { + self.run_piuparts()?; + } + if self.args.run_autopkgtests() { + self.run_autopkgtests()?; + } + Ok(()) + } + + pub fn run_verify( + &self, + verify_config: PkgVerifyConfig, + no_package: bool, + ) -> Result<(), SbuildError> { + if !no_package { + self.run_package()?; + } + let output_dir = + Path::new(self.args.build_files_dir()) + .parent() + .ok_or(SbuildError::GenericError( + "Invalid build files dir".to_string(), + ))?; + + let errors: Vec<_> = verify_config + .verify + .package_hash + .iter() + .filter_map(|output| { + let file_path = output_dir.join(&output.name); + if !file_path.exists() { + return Some(format!("Verification file missing: {}", output.name)); + } + + let buffer = match std::fs::read(&file_path) { + Ok(buf) => buf, + Err(_) => return Some(format!("Failed to read file: {}", output.name)), + }; + + let actual_sha1 = match calculate_sha1(&buffer) { + Ok(hash) => hash, + Err(_) => { + return Some(format!("Failed to calculate hash for: {}", output.name)) + } + }; + + (actual_sha1 != output.hash).then(|| { + format!( + "SHA1 mismatch for {}: expected {}, got {}", + output.name, output.hash, actual_sha1 + ) + }) + }) + .collect(); + + if errors.is_empty() { + info!("Verification successful!"); + Ok(()) + } else { + // Convert the error collection to a proper error + Err(SbuildError::VerificationError(errors.join("; "))) + } + } + + pub fn run_lintian(&self) -> Result<(), SbuildError> { + let args = self.args.get_lintian_tool_args(); + let tool = LintianTool::new(args); + ToolRunner::new().run_tool(tool) + } + + pub fn run_piuparts(&self) -> Result<(), SbuildError> { + let args = self.args.get_piuparts_tool_args(); + let tool = PiupartsTool::new(args); + ToolRunner::new().run_tool(tool) + } + + pub fn run_autopkgtests(&self) -> Result<(), SbuildError> { + let args = self.args.get_autopkg_tool_args(); + let tool = AutopkgtestTool::new(args); + ToolRunner::new().run_tool(tool) + } +} + +#[cfg(test)] +mod tests { + + use crate::configs::autopkgtest_version::AutopkgtestVersion; + use crate::configs::pkg_config::{ + BuildEnv, DefaultPackageTypeConfig, LanguageEnv, PackageFields, PackageType, + }; + use crate::configs::sbuild_version::SbuildVersion; + + use super::*; + use env_logger::Env; + use types::config::Architecture; + use std::fs::{create_dir_all, File}; + use std::path::{Path, PathBuf}; + use std::sync::Once; + use tempfile::tempdir; + use types::distribution::Distribution; + use types::version::Version; + + static INIT: Once = Once::new(); + + // Initialize logger once for all tests + fn setup() { + INIT.call_once(|| { + env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + }); + } + + fn create_base_config() -> PkgConfig { + let sbuild_cache_dir = tempdir().unwrap().path().to_path_buf(); + let config = PkgConfig { + package_fields: PackageFields { + spec_file: "".into(), + package_name: "".into(), + version_number: Version::try_from("1.0.0").unwrap(), + revision_number: "".into(), + homepage: "".into(), + }, + package_type: PackageType::Default(DefaultPackageTypeConfig { + tarball_url: "test.tar.gz".into(), + tarball_hash: Some("".into()), + language_env: LanguageEnv::C, + }), + build_env: BuildEnv { + codename: Distribution::bookworm(), + arch: Architecture::Amd64, + pkg_builder_version: Version::try_from("1.0.0").unwrap(), + debcrafter_version: "1.0.0".into(), + sbuild_cache_dir: Some(sbuild_cache_dir.clone()), + docker: None, + run_lintian: None, + run_piuparts: None, + run_autopkgtest: None, + lintian_version: Version::try_from("1.0.0").unwrap(), + piuparts_version: Version::try_from("1.0.0").unwrap(), + autopkgtest_version: AutopkgtestVersion::try_from("2.5").unwrap(), + sbuild_version: SbuildVersion::try_from( + "sbuild (Debian sbuild) 0.85.6 (26 February 2024)", + ) + .unwrap(), + workdir: PathBuf::from(""), + }, + _config_root: Some(tempdir().unwrap().path().to_path_buf()), + }; + + config + } + + #[test] + fn test_clean_when_file_missing() { + setup(); + let config = create_base_config(); + let args = SbuildArgs::try_from(config).unwrap(); + let build_env = Sbuild { args: args.clone() }; + + let result = build_env.run_env_clean(); + let cache_file = args.get_cache_file(); + + assert!(result.is_ok()); + assert!(!Path::new(&cache_file).exists()); + } + + #[test] + fn test_clean_with_existing_file() { + setup(); + let config = create_base_config(); + let cache_dir = config.build_env.sbuild_cache_dir.clone().unwrap(); + + let args = SbuildArgs::try_from(config).unwrap(); + + let build_env = Sbuild { args: args.clone() }; + let cache_file = args.get_cache_file(); + + create_dir_all(cache_dir).unwrap(); + File::create(&cache_file).unwrap(); + assert!(Path::new(&cache_file).exists()); + + let result = build_env.run_env_clean(); + assert!(result.is_ok()); + assert!(!Path::new(&cache_file).exists()); + } + + #[test] + #[ignore = "Only run on CI"] + fn test_create_environment() { + setup(); + let config = create_base_config(); + + let args = SbuildArgs::try_from(config).unwrap(); + + let build_env = Sbuild { args: args.clone() }; + let cache_file = args.get_cache_file(); + + build_env.run_env_clean().unwrap(); + assert!(!Path::new(&cache_file).exists()); + + let result = build_env.run_env_create(); + assert!(result.is_ok()); + assert!(Path::new(&cache_file).exists()); + } +} diff --git a/workspace/packager_deb/src/sbuild_args.rs b/workspace/packager_deb/src/sbuild_args.rs new file mode 100644 index 00000000..d5f2ffa9 --- /dev/null +++ b/workspace/packager_deb/src/sbuild_args.rs @@ -0,0 +1,522 @@ +use std::{ + convert::TryFrom, + path::{Path, PathBuf}, +}; + +use types::{config::Architecture, defaults::WORKDIR_ROOT, distribution::Distribution, version::Version}; + +use crate::{ + configs::{ + autopkgtest_version::AutopkgtestVersion, + pkg_config::{LanguageEnv, PackageType, PkgConfig}, + sbuild_version::SbuildVersion, + }, + installers::language_installer::LanguageInstaller, + misc::{build_pipeline::BuildContext, utils::expand_path}, + tools::{ + autopkgtest_tool::AutopkgtestToolArgs, lintian_tool::LintianToolArgs, + piuparts_tool::PiupartsToolArgs, sbuild_tool::SbuildToolArgs, + }, +}; + +#[derive(Debug, Clone)] +pub struct SbuildArgs { + cache_dir: PathBuf, + build_files_dir: PathBuf, + codename: Distribution, + run_lintian: bool, + run_autopkgtests: bool, + run_piuparts: bool, + lintian_version: Version, + piuparts_version: Version, + autopkgtest_version: AutopkgtestVersion, + sbuild_version: SbuildVersion, + package_type: PackageType, + arch: Architecture, + context: BuildContext, + deb_dir: PathBuf, + deb_name: PathBuf, + changes_file: PathBuf, + cache_file: PathBuf, + language_env: Option, + test_deps_not_in_debian: Vec, + chroot_setup_commands: Vec, +} + +impl TryFrom for SbuildArgs { + type Error = std::io::Error; + + fn try_from(config: PkgConfig) -> Result { + let config_root = config._config_root.unwrap(); + // Cache directory + let cache_dir = config + .build_env + .sbuild_cache_dir + .clone() + .unwrap_or_else(|| PathBuf::from("~/.cache/sbuild")); + + let cache_dir = expand_path(&cache_dir, None); + + // Build context + let package_fields = &config.package_fields; + let config_root_path = PathBuf::from(config_root.clone()); + let source_to_patch_from_path = config_root_path.join("src").to_str().unwrap().to_string(); + + let mut workdir = config.build_env.workdir.clone(); + let mut default_work_dir = PathBuf::from(WORKDIR_ROOT); + default_work_dir.push(config.build_env.codename.as_ref()); + if workdir.as_os_str().is_empty() { + workdir = default_work_dir; + } + let workdir = expand_path(&workdir, None); + + let build_artifacts_dir = { + let build_artifacts_dir = format!( + "{}/{}-{}-{}", + workdir.display().to_string(), + &package_fields.package_name, + package_fields.version_number.as_str(), + &package_fields.revision_number + ); + PathBuf::from(build_artifacts_dir) + }; + + let debian_orig_tarball_path = { + let tarball_path = format!( + "{}/{}_{}.orig.tar.gz", + &build_artifacts_dir.display().to_string(), + &package_fields.package_name, + &package_fields.version_number + ); + PathBuf::from(tarball_path) + }; + + let build_files_dir = { + let build_files_dir = format!( + "{}/{}-{}", + build_artifacts_dir.display().to_string(), + &package_fields.package_name, + &package_fields.version_number + ); + PathBuf::from(build_files_dir) + }; + + let mut context = BuildContext { + build_artifacts_dir, + build_files_dir: build_files_dir.clone(), + debcrafter_version: config.build_env.debcrafter_version.as_str().to_string(), + homepage: package_fields.homepage.clone(), + spec_file: package_fields.spec_file.clone(), + tarball_hash: String::new(), + tarball_url: String::new(), + src_dir: source_to_patch_from_path.into(), + tarball_path: debian_orig_tarball_path, + package_name: package_fields.package_name.clone(), + git_tag: String::new(), + git_url: String::new(), + submodules: vec![], + }; + + match &config.package_type { + PackageType::Default(default_config) => { + context.tarball_url = { + let tarball_url = default_config.tarball_url.as_str(); + if tarball_url.starts_with("http") { + tarball_url.to_string() + } else { + expand_path( + &PathBuf::from(tarball_url), + Some(&PathBuf::from(&config_root.clone())), + ) + .display() + .to_string() + } + }; + + if let Some(hash) = &default_config.tarball_hash { + context.tarball_hash = hash.clone(); + } + } + PackageType::Git(git_config) => { + context.git_tag = git_config.git_tag.clone(); + context.git_url = git_config.git_url.as_str().to_string(); + context.submodules = git_config.submodules.clone(); + } + PackageType::Virtual => { + // Virtual packages already have the correct default values + } + } + + // Extract other fields from config + let run_lintian = config.build_env.run_lintian.unwrap_or(false); + let run_autopkgtests = config.build_env.run_piuparts.unwrap_or(false); + let run_piuparts = config.build_env.run_piuparts.unwrap_or(false); + let lintian_version = config.build_env.lintian_version.clone(); + let piuparts_version = config.build_env.piuparts_version.clone(); + let autopkgtest_version = config.build_env.autopkgtest_version.clone(); + let sbuild_version = config.build_env.sbuild_version.clone(); + let package_type = config.package_type.clone(); + let arch = config.build_env.arch.clone(); + let package_name = package_fields.package_name.clone(); + let package_version_number = package_fields.version_number.clone(); + let revision_number = package_fields.revision_number.clone(); + + // Generate derived paths + let deb_dir = PathBuf::from(&build_files_dir) + .parent() + .unwrap() + .to_path_buf(); + + let deb_name = { + let filename = format!( + "{}_{}-{}_{}.{}", + &package_name, &package_version_number, &revision_number, &arch, "deb" + ); + deb_dir.join(filename) + }; + + let changes_file = { + let filename = format!( + "{}_{}-{}_{}.{}", + &package_name, &package_version_number, &revision_number, &arch, "changes" + ); + deb_dir.join(filename) + }; + + let cache_file = { + let dir = shellexpand::tilde(&cache_dir.display().to_string()).to_string(); + let codename = &config.build_env.codename.as_short(); + let cache_file_name = format!("{}-{}.tar.gz", codename, &arch); + Path::new(&dir).join(cache_file_name) + }; + + // Get language environment + let language_env = match &package_type { + PackageType::Default(config) => Some(config.language_env.clone()), + PackageType::Git(config) => Some(config.language_env.clone()), + PackageType::Virtual => None, + }; + + // Build deps + let build_deps_not_in_debian = match &language_env { + Some(env) => { + let installer: Box = env.clone().into(); + installer.get_build_deps(&arch, &config.build_env.codename) + } + None => vec![], + }; + + // Test deps + let test_deps_not_in_debian = match &language_env { + Some(env) => { + let installer: Box = env.clone().into(); + installer.get_test_deps(&config.build_env.codename) + } + None => vec![], + }; + + // Chroot setup commands + let mut chroot_setup_commands = build_deps_not_in_debian.clone(); + if config.build_env.codename == Distribution::noble() { + chroot_setup_commands.extend(vec![ + "apt install -y software-properties-common".to_string(), + "add-apt-repository universe".to_string(), + "add-apt-repository restricted".to_string(), + "add-apt-repository multiverse".to_string(), + "apt update".to_string(), + ]); + } + let chroot_setup_commands = chroot_setup_commands + .into_iter() + .map(|dep| format!("--chroot-setup-commands={}", dep)) + .collect(); + + Ok(SbuildArgs { + cache_dir, + build_files_dir, + codename: config.build_env.codename, + run_lintian, + run_autopkgtests, + run_piuparts, + lintian_version, + piuparts_version, + autopkgtest_version, + sbuild_version, + package_type, + arch, + context, + deb_dir, + deb_name, + changes_file, + cache_file, + language_env, + test_deps_not_in_debian, + chroot_setup_commands, + }) + } +} + +impl SbuildArgs { + pub fn get_sbuild_tool_args(&self) -> SbuildToolArgs { + let version = self.sbuild_version.clone(); + let codename = self.codename.clone(); + let cache_file = self.cache_file.clone(); + let build_chroot_setup_commands = self.chroot_setup_commands.clone(); + let build_files_dir = self.build_files_dir.clone(); + let package_type = self.package_type.clone(); + let context = self.context.clone(); + SbuildToolArgs { + version, + codename, + cache_file, + build_chroot_setup_commands, + build_files_dir, + package_type, + context, + run_lintian: self.run_lintian, + } + } + + pub fn get_autopkg_tool_args(&self) -> AutopkgtestToolArgs { + let version = self.autopkgtest_version.clone(); + let changes_file = self.changes_file.clone(); + let codename = self.codename.clone(); + let deb_dir = self.deb_dir.clone(); + let test_deps = self.test_deps_not_in_debian.clone(); + let cache_dir = self.cache_dir.clone(); + let arch = self.arch.clone(); + + AutopkgtestToolArgs { + version, + changes_file, + codename, + deb_dir, + test_deps, + image_path: None, + cache_dir, + arch, + } + } + pub fn get_lintian_tool_args(&self) -> LintianToolArgs { + let version = self.lintian_version.clone(); + let changes_file = self.changes_file.clone(); + let codename = self.codename.clone(); + + LintianToolArgs { + version, + changes_file, + codename, + } + } + pub fn get_piuparts_tool_args(&self) -> PiupartsToolArgs { + let version = self.piuparts_version.clone(); + let codename = self.codename.clone(); + let deb_dir = self.deb_dir.clone(); + let deb_name = self.deb_name.clone(); + let language_env = self.language_env.clone(); + PiupartsToolArgs { + version, + codename, + deb_dir, + language_env, + deb_name, + } + } + + pub fn build_files_dir(&self) -> &PathBuf { + &self.build_files_dir + } + pub fn codename(&self) -> &Distribution { + &self.codename + } + + pub fn run_autopkgtests(&self) -> bool { + self.run_autopkgtests + } + pub fn run_piuparts(&self) -> bool { + self.run_piuparts + } + + pub fn get_cache_file(&self) -> PathBuf { + self.cache_file.clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::configs::autopkgtest_version::AutopkgtestVersion; + use crate::configs::pkg_config::{ + BuildEnv, DefaultPackageTypeConfig, DotnetConfig, LanguageEnv, PackageFields, PackageType, + }; + use crate::configs::sbuild_version::SbuildVersion; + use std::convert::TryFrom; + use std::path::PathBuf; + use types::distribution::Distribution; + use types::version::Version; + + fn create_test_pkg_config() -> PkgConfig { + let config = PkgConfig { + package_fields: PackageFields { + spec_file: "hello-world-dotnet.sss".into(), + package_name: "hello-world".into(), + version_number: Version::try_from("1.0.0").unwrap(), + revision_number: "1".into(), + homepage: "http://example.com".into(), + }, + package_type: PackageType::Default(DefaultPackageTypeConfig { + tarball_url: "test.tar.gz".into(), + tarball_hash: Some("".into()), + language_env: LanguageEnv::Dotnet(DotnetConfig { + dotnet_packages: vec![], + use_backup_version: false, + deps: None, + }), + }), + build_env: BuildEnv { + codename: Distribution::bookworm(), + arch: Architecture::Amd64, + pkg_builder_version: Version::try_from("1.0.0").unwrap(), + debcrafter_version: "1.0.0".into(), + sbuild_cache_dir: None, + docker: None, + run_lintian: None, + run_piuparts: None, + run_autopkgtest: None, + lintian_version: Version::try_from("1.0.0").unwrap(), + piuparts_version: Version::try_from("1.0.0").unwrap(), + autopkgtest_version: AutopkgtestVersion::try_from("2.5").unwrap(), + sbuild_version: SbuildVersion::try_from( + "sbuild (Debian sbuild) 0.85.6 (26 February 2024)", + ) + .unwrap(), + workdir: PathBuf::from("/tmp/workdir/packages"), + }, + _config_root: Some("/test/config/root".into()), + }; + + config + } + + #[test] + fn test_cache_dir_set_to_default() { + let config = create_test_pkg_config(); + let args = SbuildArgs::try_from(config.clone()).unwrap(); + + // Test all fields + assert_eq!( + args.cache_dir.display().to_string(), + shellexpand::tilde("~/.cache/sbuild").to_string() + ); + } + + #[test] + fn test_cache_dir_overriden() { + let mut config = create_test_pkg_config(); + config.build_env.sbuild_cache_dir = Some(PathBuf::from("/test/cache/dir")); + let args = SbuildArgs::try_from(config.clone()).unwrap(); + + // Test all fields + assert_eq!( + args.cache_dir.display().to_string(), + shellexpand::tilde("/test/cache/dir").to_string() + ); + } + + #[test] + fn test_deb_name_construction() { + let config = create_test_pkg_config(); + let args = SbuildArgs::try_from(config.clone()).unwrap(); + + let expected_deb_name = PathBuf::from( + "/tmp/workdir/packages/hello-world-1.0.0-1/hello-world_1.0.0-1_amd64.deb", + ); + assert_eq!( + args.deb_name, + expected_deb_name, + "deb_name should be correctly constructed from package name, version, revision, and arch" + ); + } + + #[test] + fn test_changes_file_construction() { + let config = create_test_pkg_config(); + let args = SbuildArgs::try_from(config.clone()).unwrap(); + + let expected_changes_file = PathBuf::from( + "/tmp/workdir/packages/hello-world-1.0.0-1/hello-world_1.0.0-1_amd64.changes", + ); + assert_eq!( + args.changes_file, + expected_changes_file, + "changes_file should be correctly constructed from package name, version, revision, and arch" + ); + } + + #[test] + fn test_cache_file_construction() { + let config = create_test_pkg_config(); + let args = SbuildArgs::try_from(config.clone()).unwrap(); + + let expected_cache_file = + PathBuf::from(shellexpand::tilde("~/.cache/sbuild/bookworm-amd64.tar.gz").to_string()); + assert_eq!( + args.get_cache_file(), + expected_cache_file, + "cache_file should be constructed from cache_dir, codename, and arch" + ); + } + + #[test] + fn test_deb_dir_construction() { + let config = create_test_pkg_config(); + let args = SbuildArgs::try_from(config.clone()).unwrap(); + + let expected_deb_dir = PathBuf::from("/tmp/workdir/packages/hello-world-1.0.0-1"); + assert_eq!( + args.deb_dir, expected_deb_dir, + "deb_dir should be the parent directory of build_files_dir" + ); + } + + #[test] + fn test_build_files_dir_construction() { + let config = create_test_pkg_config(); + let args = SbuildArgs::try_from(config.clone()).unwrap(); + + let expected_build_files_dir = + PathBuf::from("/tmp/workdir/packages/hello-world-1.0.0-1/hello-world-1.0.0"); + assert_eq!( + args.build_files_dir(), + &expected_build_files_dir, + "build_files_dir should be constructed from workdir, package name, and version" + ); + } + + #[test] + fn test_context_tarball_url_local_path() { + let config = create_test_pkg_config(); + let args = SbuildArgs::try_from(config.clone()).unwrap(); + + let expected_tarball_url = "/test/config/root/test.tar.gz".to_string(); + assert_eq!( + args.context.tarball_url, expected_tarball_url, + "tarball_url should be expanded relative to config_root for local paths" + ); + } + + #[test] + fn test_context_tarball_url_http() { + let mut config = create_test_pkg_config(); + if let PackageType::Default(ref mut default_config) = config.package_type { + default_config.tarball_url = "https://example.com/test.tar.gz".into(); + } + let args = SbuildArgs::try_from(config.clone()).unwrap(); + + let expected_tarball_url = "https://example.com/test.tar.gz".to_string(); + assert_eq!( + args.context.tarball_url, expected_tarball_url, + "tarball_url should remain unchanged for HTTP URLs" + ); + } +} diff --git a/workspace/packager_deb/src/steps/create_debian_dir.rs b/workspace/packager_deb/src/steps/create_debian_dir.rs new file mode 100644 index 00000000..360cf9b8 --- /dev/null +++ b/workspace/packager_deb/src/steps/create_debian_dir.rs @@ -0,0 +1,37 @@ +use std::path::PathBuf; + +use debian::debcrafter::DebcrafterCmd; +use log::info; + +use crate::misc::build_pipeline::{BuildContext, BuildError, BuildStep}; + +#[derive(Default)] +pub struct CreateDebianDir { + build_files_dir: PathBuf, + debcrafter_version: String, + spec_file: PathBuf, +} + +impl From for CreateDebianDir { + fn from(context: BuildContext) -> Self { + CreateDebianDir { + build_files_dir: context.build_files_dir.clone(), + debcrafter_version: context.debcrafter_version.clone(), + spec_file: context.spec_file.clone(), + } + } +} + +impl BuildStep for CreateDebianDir { + fn step(&self) -> Result<(), BuildError> { + let debcrafter = DebcrafterCmd::new(self.debcrafter_version.as_str()); + debcrafter.check_if_dpkg_parsechangelog_installed()?; + debcrafter.check_if_installed()?; + debcrafter.create_debian_dir(&self.spec_file, &self.build_files_dir)?; + info!( + "Created /debian dir under build_files_dir folder: {:?}", + self.build_files_dir + ); + Ok(()) + } +} diff --git a/workspace/packager_deb/src/steps/create_empty_tar.rs b/workspace/packager_deb/src/steps/create_empty_tar.rs new file mode 100644 index 00000000..81781379 --- /dev/null +++ b/workspace/packager_deb/src/steps/create_empty_tar.rs @@ -0,0 +1,76 @@ +use crate::misc::build_pipeline::{BuildContext, BuildError, BuildStep}; +use log::info; +use std::{path::PathBuf, process::Command}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum CreateEmptyTarError { + #[error("Failed to create virtual package tarball")] + TarballCreationFailed, +} +#[derive(Default)] +pub struct CreateEmptyTar { + tarball_path: PathBuf, + build_artifacts_dir: PathBuf, +} + +impl From for CreateEmptyTar { + fn from(context: BuildContext) -> Self { + CreateEmptyTar { + build_artifacts_dir: context.build_artifacts_dir.clone(), + tarball_path: context.tarball_path.clone(), + } + } +} + +impl BuildStep for CreateEmptyTar { + fn step(&self) -> Result<(), BuildError> { + info!("Creating empty .tar.gz for virtual package"); + let output = Command::new("tar") + .args([ + "czvf", + &self.tarball_path.display().to_string(), + "--files-from", + "/dev/null", + ]) + .current_dir(&self.build_artifacts_dir) + .output()?; + + if !output.status.success() { + return Err(CreateEmptyTarError::TarballCreationFailed.into()); + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::tempdir; + + impl BuildContext { + pub fn new() -> Self { + Self::default() + } + } + #[test] + fn test_download_source_virtual_package() { + let temp_dir = tempdir().expect("Failed to create temporary directory"); + + let build_artifacts_dir = temp_dir.path().to_path_buf(); + let tarball_name = "test_package.tar.gz"; + let tarball_path = temp_dir.path().join(tarball_name); + let tarball_path_str = temp_dir.path().join(tarball_name); + + let mut context = BuildContext::new(); + context.tarball_path = tarball_path_str; + context.build_artifacts_dir = build_artifacts_dir; + let step = CreateEmptyTar::from(context); + + let result = step.step(); + + assert!(result.is_ok()); + assert!(tarball_path.exists()); + } +} diff --git a/workspace/packager_deb/src/steps/dowload_git.rs b/workspace/packager_deb/src/steps/dowload_git.rs new file mode 100644 index 00000000..feb8003e --- /dev/null +++ b/workspace/packager_deb/src/steps/dowload_git.rs @@ -0,0 +1,343 @@ +use std::{ + fs, io, + path::{Path, PathBuf}, + process::{Command, Output}, +}; + +use filetime::FileTime; +use log::info; +use thiserror::Error; + +use crate::{ + configs::pkg_config::SubModule, + misc::build_pipeline::{BuildContext, BuildError, BuildStep}, +}; + +#[derive(Error, Debug)] +pub enum DownloadGitError { + #[error("git-lfs is not installed, please install it!")] + GitLfsNotInstalled, + + #[error("Failed to checkout tag {tag}: {reason}")] + CheckoutTagFailed { tag: String, reason: String }, + + #[error("Failed to initialize submodules: {0}")] + SubmoduleInitFailed(String), + + #[error("Failed to checkout submodule: {0}")] + SubmoduleCheckoutFailed(String), + + #[error("Failed to checkout commit {commit} for submodule {path}: {reason}")] + SubmoduleCommitFailed { + commit: String, + path: String, + reason: String, + }, + + #[error("Failed to create tarball: {0}")] + TarballCreationFailed(String), + + #[error("IO error: {0}")] + IoError(#[from] io::Error), +} + +#[derive(Default)] +pub struct DownloadGit { + build_artifacts_dir: PathBuf, + package_name: String, + git_tag: String, + git_url: String, + submodules: Vec, + tarball_path: PathBuf, +} + +impl From for DownloadGit { + fn from(context: BuildContext) -> Self { + Self { + build_artifacts_dir: context.build_artifacts_dir.clone(), + package_name: context.package_name.clone(), + git_tag: context.git_tag.clone(), + git_url: context.git_url.clone(), + submodules: context.submodules.clone(), + tarball_path: context.tarball_path.clone(), + } + } +} + +impl DownloadGit { + fn check_git_lfs() -> Result<(), DownloadGitError> { + Command::new("which") + .arg("git-lfs") + .output() + .map(|_| ()) + .map_err(|_| DownloadGitError::GitLfsNotInstalled) + } + + fn get_error_message(output: &Output) -> String { + String::from_utf8_lossy(&output.stderr).to_string() + } + + fn run_git_command( + args: &[&str], + current_dir: Option<&Path>, + error_type: impl FnOnce(String) -> DownloadGitError, + ) -> Result<(), DownloadGitError> { + let mut cmd = Command::new("git"); + + if let Some(dir) = current_dir { + cmd.current_dir(dir); + } + + let output = cmd.args(args).output().map_err(DownloadGitError::IoError)?; + + if !output.status.success() { + return Err(error_type(Self::get_error_message(&output))); + } + + Ok(()) + } + + fn clone_repo(git_url: &str, tag_version: &str, path: &Path) -> Result<(), DownloadGitError> { + let output = Command::new("git") + .args(&[ + "clone", + "--depth=1", + "--branch", + tag_version, + git_url, + &path.display().to_string(), + ]) + .output() + .map_err(DownloadGitError::IoError)?; + + if !output.status.success() { + return Err(DownloadGitError::CheckoutTagFailed { + tag: tag_version.to_string(), + reason: Self::get_error_message(&output), + }); + } + + Ok(()) + } + + fn init_submodules(path: &Path) -> Result<(), DownloadGitError> { + Self::run_git_command( + &["submodule", "init"], + Some(&path), + DownloadGitError::SubmoduleInitFailed, + )?; + + Self::run_git_command( + &["submodule", "update", "--depth=1", "--recursive"], + Some(path), + DownloadGitError::SubmoduleInitFailed, + ) + } + + fn checkout_submodule_commits( + submodule: &SubModule, + base_path: &Path, + ) -> Result<(), DownloadGitError> { + let submodule_path = Path::new(base_path).join(&submodule.path); + println!( + "Checking out path: {:?} commit:{}", + submodule_path, submodule.commit + ); + + let fetch_commit_output = Command::new("git") + .current_dir(submodule_path.clone()) + .args(&["fetch", "origin", &submodule.commit.trim()]) + .output() + .map_err(|err| DownloadGitError::SubmoduleCheckoutFailed(err.to_string()))?; + + if !fetch_commit_output.status.success() { + return Err(DownloadGitError::SubmoduleCommitFailed { + commit: submodule.commit.clone(), + path: submodule.path.clone(), + reason: Self::get_error_message(&fetch_commit_output), + }); + } + let output = Command::new("git") + .current_dir(submodule_path) + .args(&["checkout", &submodule.commit.trim()]) + .output() + .map_err(|err| DownloadGitError::SubmoduleCheckoutFailed(err.to_string()))?; + + if !output.status.success() { + return Err(DownloadGitError::SubmoduleCommitFailed { + commit: submodule.commit.clone(), + path: submodule.path.clone(), + reason: Self::get_error_message(&output), + }); + } + + Ok(()) + } + + pub fn clone_and_checkout_tag( + git_url: &str, + tag_version: &str, + path: &Path, + git_submodules: &[SubModule], + ) -> Result<(), DownloadGitError> { + Self::check_git_lfs()?; + Self::clone_repo(git_url, tag_version, path)?; + Self::init_submodules(path)?; + Self::update_submodules(git_submodules, path)?; + + Ok(()) + } + + pub fn update_submodules( + git_submodules: &[SubModule], + current_dir: &Path, + ) -> Result<(), DownloadGitError> { + for submodule in git_submodules { + Self::checkout_submodule_commits(submodule, current_dir)?; + } + + Ok(()) + } + + fn create_tarball(&self) -> Result<(), DownloadGitError> { + info!( + "Creating tar from git repo from {}", + self.build_artifacts_dir.join(&self.package_name).display() + ); + + let output = Command::new("tar") + .args(&[ + "--sort=name", + "--owner=0", + "--group=0", + "--numeric-owner", + // does not work + // "--mtime='2019-01-01 00:00'", + "--pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime", + "-czf", + &self.tarball_path.display().to_string(), + &self.package_name, + ]) + .current_dir(&self.build_artifacts_dir) + .output() + .map_err(DownloadGitError::IoError)?; + + if !output.status.success() { + return Err(DownloadGitError::TarballCreationFailed( + Self::get_error_message(&output), + )); + } + + Ok(()) + } + + fn set_creation_time>(dir_path: P, timestamp: FileTime) -> io::Result<()> { + filetime::set_file_mtime(&dir_path, timestamp)?; + filetime::set_file_atime(&dir_path, timestamp)?; + + let mut stack = vec![PathBuf::from(dir_path.as_ref())]; + + while let Some(current) = stack.pop() { + for entry in fs::read_dir(¤t)? { + let entry = entry?; + let file_type = entry.file_type()?; + let file_path = entry.path(); + + match file_type { + _ if file_type.is_dir() => { + stack.push(file_path.clone()); + filetime::set_file_mtime(&file_path, timestamp)?; + filetime::set_file_atime(&file_path, timestamp)?; + } + _ if file_type.is_file() => { + filetime::set_file_mtime(&file_path, timestamp)?; + filetime::set_file_atime(&file_path, timestamp)?; + } + _ if file_type.is_symlink() => { + filetime::set_symlink_file_times(&file_path, timestamp, timestamp)?; + } + _ => (), // Skip other file types + } + } + } + + Ok(()) + } + + fn prepare_build_directory(&self) -> Result { + let path = self.build_artifacts_dir.join(&self.package_name); + + if path.exists() { + fs::remove_dir_all(&path)?; + } + + fs::create_dir_all(&path)?; + + Ok(path) + } +} + +impl BuildStep for DownloadGit { + fn step(&self) -> Result<(), BuildError> { + let path = self.prepare_build_directory()?; + + Self::clone_and_checkout_tag(&self.git_url, &self.git_tag, &path, &self.submodules)?; + + fs::remove_dir_all(path.join(".git"))?; + + // Set consistent file timestamps for reproducibility: January 1, 2022 + let timestamp = FileTime::from_unix_time(1640995200, 0); + Self::set_creation_time(path, timestamp)?; + + self.create_tarball()?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::configs::pkg_config::{PackageType, PkgConfig}; + + use super::*; + use tempfile::tempdir; + + #[test] + #[ignore = "Only run on CI"] + fn test_clone_and_checkout_tag() { + let url = "https://github.com/status-im/nimbus-eth2.git"; + let temp_dir = tempdir().expect("Failed to create temporary directory"); + let repo_path = temp_dir.path(); + let tag_version = "v24.3.0"; + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + let cargo_workspace_dir = Path::new(cargo_manifest_dir) + .parent() + .unwrap() + .parent() + .unwrap() + .to_path_buf(); + let toml_file = + cargo_workspace_dir.join("examples/bookworm/git-package/nimbus/pkg-builder.toml"); + + let str = fs::read_to_string(toml_file).expect("Cannot read example toml"); + let config: PkgConfig = toml::from_str(&str).expect("Cannot parse file."); + match config.package_type { + PackageType::Git(gitconfig) => { + let result = DownloadGit::clone_and_checkout_tag( + url, + tag_version, + repo_path, + &gitconfig.submodules, + ); + assert!( + result.is_ok(), + "Failed to clone and checkout tag: {:?}", + result + ); + } + _ => panic!("Wrong type of file."), + } + + fs::remove_dir_all(temp_dir).unwrap(); + } +} diff --git a/workspace/packager_deb/src/steps/download_source.rs b/workspace/packager_deb/src/steps/download_source.rs new file mode 100644 index 00000000..cba566a0 --- /dev/null +++ b/workspace/packager_deb/src/steps/download_source.rs @@ -0,0 +1,147 @@ +use crate::misc::build_pipeline::{BuildContext, BuildError, BuildStep}; +use log::info; +use std::{fs, path::PathBuf, process::Command}; + +#[derive(Default)] +pub struct DownloadSource { + tarball_url: String, + tarball_path: PathBuf, +} + +impl From for DownloadSource { + fn from(context: BuildContext) -> Self { + DownloadSource { + tarball_url: context.tarball_url.clone(), + tarball_path: context.tarball_path.clone(), + } + } +} +impl BuildStep for DownloadSource { + fn step(&self) -> Result<(), BuildError> { + info!("Downloading source {:?}", self.tarball_path); + let is_web = self.tarball_url.starts_with("http"); + + if is_web { + info!( + "Downloading tar: {} to location: {:?}", + self.tarball_url, self.tarball_path + ); + let status = Command::new("wget") + .arg("-q") + .arg("-O") + .arg(&self.tarball_path) + .arg(&self.tarball_url) + .status() + .map_err(|e| BuildError::CommandFailed(e.to_string()))?; + + if !status.success() { + return Err(BuildError::DownloadFailed); + } + } else { + info!( + "Copying tar: {} to location: {:?}", + self.tarball_url, self.tarball_path + ); + fs::copy(&self.tarball_url, &self.tarball_path) + .map_err(|e| BuildError::FileCopyFailed(e.to_string()))?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::misc::build_pipeline::BuildContext; + + use super::*; + use std::fs::File; + use std::io::Write; + use std::path::Path; + use tempfile::tempdir; + + use httpmock::prelude::*; + + fn setup_mock_server() -> MockServer { + // Start the mock server + let server = MockServer::start(); + + // Mock the endpoint to serve the tarball file + server.mock(|when, then| { + when.method(GET).path("/test_package.tar.gz"); + then.status(200) + .header("Content-Type", "application/octet-stream") + .body_from_file("tests/misc/test_package.tar.gz"); + }); + + server + } + #[test] + fn test_download_source_non_virtual_package() { + let server = setup_mock_server(); + + let temp_dir = tempdir().expect("Failed to create temporary directory"); + + let tarball_name = "test_package.tar.gz"; + let tarball_path = temp_dir.path().join(tarball_name); + let tarball_url = format!("{}/{}", server.base_url(), tarball_name); + + let mut context = BuildContext::default(); + context.tarball_path = tarball_path.clone(); + context.tarball_url = tarball_url; + let handler = DownloadSource::from(context); + + let result = handler.step(); + + assert!(result.is_ok()); + assert!(tarball_path.exists()); + } + + #[test] + fn test_handle_local_copy_success() { + // Create a temporary directory for testing + let dir = tempdir().unwrap(); + let source_path = dir.path().join("source.tar.gz"); + let dest_path = dir.path().join("dest.tar.gz"); + + // Create a dummy source file + { + let mut file = File::create(&source_path).unwrap(); + writeln!(file, "test content").unwrap(); + } + + let mut context = BuildContext::default(); + context.tarball_path = dest_path.clone(); + context.tarball_url = source_path.to_string_lossy().to_string(); + let handler = DownloadSource::from(context); + + let result = handler.step(); + assert!(result.is_ok()); + + // Verify the file was copied + assert!(Path::new(&dest_path).exists()); + } + + #[test] + fn test_handle_local_copy_failure() { + // Create a temporary directory for testing + let dir = tempdir().unwrap(); + let source_path = dir.path().join("nonexistent_source.tar.gz"); + let dest_path = dir.path().join("dest.tar.gz"); + + // No source file exists + + let mut context = BuildContext::default(); + context.tarball_path = dest_path; + context.tarball_url = source_path.to_string_lossy().to_string(); + let handler = DownloadSource::from(context); + + let result = handler.step(); + assert!(result.is_err()); + + match result { + Err(BuildError::FileCopyFailed(_)) => {} + _ => panic!("Expected FileCopyFailed error"), + } + } +} diff --git a/workspace/packager_deb/src/steps/extract_source.rs b/workspace/packager_deb/src/steps/extract_source.rs new file mode 100644 index 00000000..332919e9 --- /dev/null +++ b/workspace/packager_deb/src/steps/extract_source.rs @@ -0,0 +1,217 @@ +use crate::misc::build_pipeline::{BuildContext, BuildError, BuildStep}; +use log::info; +use std::{fs, io, path::PathBuf, process::Command}; + +#[derive(Default)] +pub struct ExtractSource { + build_files_dir: PathBuf, + tarball_path: PathBuf, +} + +impl From for ExtractSource { + fn from(context: BuildContext) -> Self { + ExtractSource { + build_files_dir: context.build_files_dir.clone(), + tarball_path: context.tarball_path.clone(), + // debcrafter_version: context.debcrafter_version.clone(), + // spec_file: context.spec_file.clone(), + } + } +} + +impl ExtractSource { + fn longest_common_prefix(strings: &[&str]) -> String { + if strings.is_empty() { + return String::new(); + } + if strings.len() == 1 { + let mut path_buf = PathBuf::from(strings[0]); + path_buf.pop(); + let common_prefix = path_buf.to_string_lossy().to_string(); + return common_prefix; + } + let first_string = &strings[0]; + let mut prefix = String::new(); + 'outer: for (i, c) in first_string.char_indices() { + for string in &strings[1..] { + if let Some(next_char) = string.chars().nth(i) { + if next_char != c { + break 'outer; + } + } else { + break 'outer; + } + } + prefix.push(c); + } + prefix + } + + fn components_to_strip(&self, tar_gz_file: String) -> Result { + let output = Command::new("tar") + .arg("--list") + .arg("-z") + .arg("-f") + .arg(tar_gz_file) + .output()?; + let output_str = String::from_utf8_lossy(&output.stdout); + let lines: Vec<&str> = output_str.lines().filter(|l| !l.ends_with('/')).collect(); + let common_prefix = Self::longest_common_prefix(&lines); + let components_to_strip = common_prefix.split('/').filter(|&x| !x.is_empty()).count(); + Ok(components_to_strip) + } +} + +impl BuildStep for ExtractSource { + fn step(&self) -> Result<(), BuildError> { + info!("Extracting source {:?}", &self.build_files_dir); + fs::create_dir_all(&self.build_files_dir).map_err(BuildError::IoError)?; + + let mut args = vec![ + "zxvf".to_string(), + self.tarball_path.display().to_string(), + "-C".to_string(), + self.build_files_dir.display().to_string(), + ]; + let numbers_to_strip = self + .components_to_strip(self.tarball_path.display().to_string()) + .map_err(BuildError::IoError)?; + + let strip = format!("--strip-components={}", numbers_to_strip); + if numbers_to_strip > 0 { + args.push(strip); + } + + info!("Stripping components: {} {:?}", numbers_to_strip, args); + let output = Command::new("tar") + .args(args) + .output() + .map_err(BuildError::IoError)?; + + if !output.status.success() { + let error_message = String::from_utf8(output.stderr) + .unwrap_or_else(|_| "Unknown error occurred during extraction".to_string()); + return Err(BuildError::ExtractionError(error_message)); + } + + info!( + "Extracted source to build_files_dir: {:?}", + self.build_files_dir + ); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use fs::File; + use std::path::Path; + use tempfile::tempdir; + + #[test] + fn test_longest_common_prefix_empty() { + let strings: Vec<&str> = vec![]; + let prefix = ExtractSource::longest_common_prefix(&strings); + assert_eq!(prefix, ""); + } + + #[test] + fn test_longest_common_prefix_single() { + let strings = vec!["folder/file.txt"]; + let prefix = ExtractSource::longest_common_prefix(&strings); + assert_eq!(prefix, "folder"); + } + + #[test] + fn test_longest_common_prefix_multiple() { + let strings = vec![ + "project/src/main.rs", + "project/src/lib.rs", + "project/Cargo.toml", + ]; + let prefix = ExtractSource::longest_common_prefix(&strings); + assert_eq!(prefix, "project/"); + } + + #[test] + fn test_longest_common_prefix_no_common() { + let strings = vec!["abc/def", "xyz/uvw"]; + let prefix = ExtractSource::longest_common_prefix(&strings); + assert_eq!(prefix, ""); + } + + #[test] + fn test_handle_success() -> Result<(), Box> { + let temp_dir = tempdir()?; + let build_dir = temp_dir.path().join("build"); + let tarball_path = temp_dir.path().join("source.tar.gz"); + + File::create(&tarball_path)?; + + let mut context = BuildContext::default(); + context.build_files_dir = build_dir.clone(); + context.tarball_path = tarball_path; + + let handler = ExtractSource::from(context); + + if !Path::new(&build_dir).exists() { + let result = handler.step(); + assert!(result.is_err()); + } + + assert!(Path::new(&build_dir).exists()); + + Ok(()) + } + + #[test] + fn test_handle_extraction_error() { + let temp_dir = tempdir().unwrap(); + let build_dir = temp_dir.path().join("build"); + let tarball_path = temp_dir.path().join("nonexistent.tar.gz"); + + let mut context = BuildContext::default(); + context.build_files_dir = build_dir; + context.tarball_path = tarball_path; + let handler = ExtractSource::from(context); + + let result = handler.step(); + assert!(result.is_err()); + + // match result { + // Err(BuildError::IoError(_)) => (), // Expected + // Err(e) => panic!("Unexpected error type: {:?}", e), + // Ok(_) => panic!("Expected an error but got Ok"), + // } + } + + #[test] + fn test_extract_source() { + let package_name = "test_package"; + let temp_dir = tempdir().expect("Failed to create temporary directory"); + let temp_dir = temp_dir.path(); + let tarball_path: PathBuf = PathBuf::from("tests/misc/test_package.tar.gz"); + + let build_files_dir = temp_dir.join(package_name); + + assert!(tarball_path.exists()); + + let mut context = BuildContext::default(); + context.build_files_dir = build_files_dir.clone(); + context.tarball_path = tarball_path.to_str().unwrap().into(); + let handler = ExtractSource::from(context); + + let result = handler.step(); + + assert!(result.is_ok(), "{:?}", result); + assert!(Path::new(&build_files_dir).exists()); + + let test_file_path = PathBuf::from(build_files_dir.clone()).join("empty_file.txt"); + + assert!( + test_file_path.exists(), + "Empty file not found after extraction" + ); + } +} diff --git a/workspace/packager_deb/src/steps/mod.rs b/workspace/packager_deb/src/steps/mod.rs new file mode 100644 index 00000000..df2cc806 --- /dev/null +++ b/workspace/packager_deb/src/steps/mod.rs @@ -0,0 +1,9 @@ +pub mod create_debian_dir; +pub mod create_empty_tar; +pub mod dowload_git; +pub mod download_source; +pub mod extract_source; +pub mod package_dir_setup; +pub mod patch_source; +pub mod setup_sbuild; +pub mod verify_hash; diff --git a/workspace/packager_deb/src/steps/package_dir_setup.rs b/workspace/packager_deb/src/steps/package_dir_setup.rs new file mode 100644 index 00000000..f1924df5 --- /dev/null +++ b/workspace/packager_deb/src/steps/package_dir_setup.rs @@ -0,0 +1,110 @@ +use crate::misc::build_pipeline::{BuildContext, BuildError, BuildStep}; +use log::info; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +#[derive(Default)] +pub struct PackageDirSetup { + build_artifacts_dir: PathBuf, +} + +impl From for PackageDirSetup { + fn from(context: BuildContext) -> Self { + PackageDirSetup { + build_artifacts_dir: context.build_artifacts_dir.clone(), + } + } +} + +impl BuildStep for PackageDirSetup { + fn step(&self) -> Result<(), BuildError> { + let build_artifacts_dir = &self.build_artifacts_dir; + + if Path::new(build_artifacts_dir).exists() { + info!("Removing previous package folder {:?}", build_artifacts_dir); + + fs::remove_dir_all(build_artifacts_dir)?; + } + + info!("Creating package folder {:?}", build_artifacts_dir); + + fs::create_dir_all(build_artifacts_dir)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::misc::build_pipeline::{BuildContext, BuildError}; + use std::path::PathBuf; + use tempfile::TempDir; + + fn setup_test_context() -> (BuildContext, TempDir) { + let temp_dir = TempDir::new().expect("Failed to create temp directory"); + let artifacts_dir = temp_dir.path().join("artifacts"); + + let mut context = BuildContext::new(); + context.build_artifacts_dir = artifacts_dir; + + (context, temp_dir) + } + + #[test] + fn test_handle_creates_new_directory() { + let (context, _temp_dir) = setup_test_context(); + let handler = PackageDirSetup::from(context); + + let result = handler.step(); + + assert!(result.is_ok()); + assert!(Path::new(&handler.build_artifacts_dir).exists()); + assert!(Path::new(&handler.build_artifacts_dir).is_dir()); + } + + #[test] + fn test_handle_removes_existing_directory() { + let (context, _temp_dir) = setup_test_context(); + + fs::create_dir_all(&context.build_artifacts_dir).expect("Failed to create test directory"); + + let test_file = PathBuf::from(&context.build_artifacts_dir).join("test_file.txt"); + fs::write(&test_file, "test content").expect("Failed to write test file"); + + assert!(test_file.exists()); + + let handler = PackageDirSetup::from(context); + let result = handler.step(); + + assert!(result.is_ok()); + assert!(Path::new(&handler.build_artifacts_dir).exists()); + assert!(Path::new(&handler.build_artifacts_dir).is_dir()); + + assert!(!test_file.exists()); + } + + #[test] + fn test_handle_with_permission_error() { + // This test would ideally test the error case when directory operations fail + // But it's hard to simulate in a portable way, so we're just checking the error mapping + // In a real test environment, you might use mock_fs or similar libraries + + let (mut context, _temp_dir) = setup_test_context(); + + context.build_artifacts_dir = "/root/forbidden_dir".into(); + + let handler = PackageDirSetup::from(context); + let result = handler.step(); + + assert!(result.is_err()); + + if let Err(BuildError::IoError(err)) = result { + assert_eq!(err.to_string(), "Permission denied (os error 13)"); + } else { + panic!("Expected IoError, got a different error or success"); + } + } +} diff --git a/workspace/packager_deb/src/steps/patch_source.rs b/workspace/packager_deb/src/steps/patch_source.rs new file mode 100644 index 00000000..74e1e6c7 --- /dev/null +++ b/workspace/packager_deb/src/steps/patch_source.rs @@ -0,0 +1,260 @@ +use std::{ + fs, + io::{BufRead, BufReader, Write}, + os::unix::fs::PermissionsExt, + path::{Path, PathBuf}, +}; + +use log::info; + +use crate::misc::build_pipeline::{BuildContext, BuildError, BuildStep}; + +#[derive(Default)] +pub struct PatchSource { + build_files_dir: PathBuf, + homepage: String, + src_dir: PathBuf, +} + +impl From for PatchSource { + fn from(context: BuildContext) -> Self { + PatchSource { + build_files_dir: context.build_files_dir.clone(), + homepage: context.homepage.clone(), + src_dir: context.src_dir.clone(), + } + } +} + +impl PatchSource { + pub fn patch_quilt(build_files_dir: &PathBuf) -> Result<(), BuildError> { + let debian_source_format_path = build_files_dir.clone().join("debian/source/format"); + + info!( + "Setting up quilt format for patching. Debian source format path: {:?}", + debian_source_format_path + ); + if !debian_source_format_path.parent().unwrap().exists() { + fs::create_dir_all(&debian_source_format_path.parent().unwrap())?; + info!( + "Created debian/source directory at: {:?}", + debian_source_format_path.parent().unwrap() + ); + } + + if !Path::new(&debian_source_format_path).exists() { + fs::write(&debian_source_format_path, "3.0 (quilt)\n")?; + info!( + "Quilt format file created at: {:?}", + debian_source_format_path + ); + } else { + info!( + "Quilt format file already exists at: {:?}", + debian_source_format_path + ); + } + Ok(()) + } + + pub fn patch_pc_dir(build_files_dir: &PathBuf) -> Result<(), BuildError> { + let pc_version_path = build_files_dir.clone().join(".pc/version"); + + info!("Creating necessary directories for patching .pc"); + let pc_parent = pc_version_path.parent().unwrap().join(".pc"); + fs::create_dir_all(pc_parent)?; + let mut pc_version_file = fs::File::create(pc_version_path)?; + writeln!(pc_version_file, "2")?; + Ok(()) + } + + pub fn patch_standards_version( + build_files_dir: &PathBuf, + homepage: &String, + ) -> Result<(), BuildError> { + let debian_control_path: PathBuf = build_files_dir.clone().join("debian/control"); + info!( + "Adding Standards-Version to the control file. Debian control path: {:?}", + debian_control_path + ); + let input_file = fs::File::open(&debian_control_path)?; + let reader = BufReader::new(input_file); + + let original_content: Vec = reader.lines().map(|line| line.unwrap()).collect(); + let has_standards_version = original_content + .iter() + .any(|line| line.starts_with("Standards-Version")); + let standards_version_line = "Standards-Version: 4.5.1"; + let homepage_line = format!("Homepage: {}", homepage); + if !has_standards_version { + let mut insert_index = 0; + for (i, line) in original_content.iter().enumerate() { + if line.starts_with("Priority:") { + insert_index = i + 1; + break; + } + } + + let mut updated_content = original_content.clone(); + updated_content.insert(insert_index, standards_version_line.to_string()); + updated_content.insert(insert_index + 1, homepage_line.to_string()); + + let mut output_file = fs::File::create(&debian_control_path)?; + for line in updated_content { + writeln!(output_file, "{}", line)?; + } + + info!("Standards-Version added to the control file."); + } else { + info!("Standards-Version already exists in the control file. No changes made."); + } + Ok(()) + } + + pub fn copy_src_dir(build_files_dir: &PathBuf, src_dir: &PathBuf) -> Result<(), BuildError> { + let src_dir_path = Path::new(src_dir); + if src_dir_path.exists() { + Self::copy_directory_recursive(Path::new(src_dir), Path::new(&build_files_dir)) + .map_err(|err| BuildError::CopyDirectory(err.to_string()))?; + } + Ok(()) + } + + pub fn patch_rules_permission(build_files_dir: &PathBuf) -> Result<(), BuildError> { + info!( + "Adding executable permission for {:?}/debian/rules", + build_files_dir + ); + let debian_rules: PathBuf = build_files_dir.clone().join("debian/rules"); + + let mut permissions = fs::metadata(debian_rules.clone()) + .map_err(|_| BuildError::RulesPermissionGet)? + .permissions(); + permissions.set_mode(permissions.mode() | 0o111); + fs::set_permissions(debian_rules, permissions) + .map_err(|_| BuildError::RulesPermissionSet)?; + Ok(()) + } + + fn copy_directory_recursive(src_dir: &Path, dest_dir: &Path) -> Result<(), std::io::Error> { + if !src_dir.exists() { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("Source directory {:?} does not exist", src_dir), + )); + } + + if !dest_dir.exists() { + fs::create_dir_all(dest_dir)?; + } + + for entry in fs::read_dir(src_dir)? { + let entry = entry?; + let entry_path = entry.path(); + let file_name = entry.file_name(); + + let dest_path = dest_dir.join(&file_name); + + if entry_path.is_dir() { + Self::copy_directory_recursive(&entry_path, &dest_path)?; + } else { + if let Err(e) = fs::copy(&entry_path, &dest_path) { + eprintln!( + "Failed to copy file from {:?} to {:?}: {}", + entry_path, dest_path, e + ); + return Err(e); + } + } + } + + Ok(()) + } +} + +impl BuildStep for PatchSource { + fn step(&self) -> Result<(), BuildError> { + // Patch quilt + Self::patch_quilt(&self.build_files_dir)?; + + // Patch .pc dir setup + Self::patch_pc_dir(&self.build_files_dir)?; + + // Patch .pc patch version number + Self::patch_standards_version(&self.build_files_dir, &self.homepage)?; + + // Only copy if src dir exists + Self::copy_src_dir(&self.build_files_dir, &self.src_dir)?; + + Self::patch_rules_permission(&self.build_files_dir)?; + + info!("Patching finished successfully!"); + Ok(()) // Added missing return value + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::File; + use tempfile::tempdir; + + #[test] + fn patch_rules_permission_handles_nonexistent_directory() { + let result = PatchSource::patch_rules_permission(&"/nonexistent/dir".into()); + + assert!(result.is_err()); + } + + #[test] + fn patch_quilt_creates_source_dir_and_format_file() -> Result<(), Box> { + let temp_dir = tempdir()?; + let build_files_dir = temp_dir.path().to_path_buf(); + + let result = PatchSource::patch_quilt(&build_files_dir); + assert!(result.is_ok()); + + let debian_source_dir = temp_dir.path().join("debian/source"); + assert!(debian_source_dir.exists()); + + let debian_source_format_path = temp_dir.path().join("debian/source/format"); + let format_content = fs::read_to_string(debian_source_format_path)?; + assert_eq!(format_content, "3.0 (quilt)\n"); + + Ok(()) + } + + #[test] + fn patch_quilt_skips_creation_if_already_exists() -> Result<(), Box> { + let temp_dir = tempdir()?; + let temp_dir = temp_dir.path(); + let build_files_dir = temp_dir.to_path_buf(); + + fs::create_dir_all(temp_dir.join("debian/source")).expect("Failed to create dir for test."); + File::create(temp_dir.join("debian/source/format")).expect("Failed to create file."); + + let result = PatchSource::patch_quilt(&build_files_dir.into()); + assert!(result.is_ok()); + + let entries: Vec<_> = fs::read_dir(temp_dir)?.collect(); + assert_eq!(entries.len(), 1); + + Ok(()) + } + + #[test] + fn patch_rules_permission_adds_exec_permission() -> Result<(), Box> { + let temp_dir = tempdir()?; + let rules_path = temp_dir.path().join("debian/rules"); + fs::create_dir_all(temp_dir.path().join("debian")).expect("Could not create dir"); + File::create(&rules_path)?; + + let result = PatchSource::patch_rules_permission(&temp_dir.path().to_path_buf()); + assert!(result.is_ok()); + + let permissions = fs::metadata(&rules_path)?.permissions(); + assert_ne!(permissions.mode() & 0o111, 0); + + Ok(()) + } +} diff --git a/workspace/packager_deb/src/steps/setup_sbuild.rs b/workspace/packager_deb/src/steps/setup_sbuild.rs new file mode 100644 index 00000000..b375059c --- /dev/null +++ b/workspace/packager_deb/src/steps/setup_sbuild.rs @@ -0,0 +1,30 @@ +use crate::misc::build_pipeline::{BuildContext, BuildError, BuildStep}; +use dirs::home_dir; +use std::fs; +use std::io::Write; + +#[derive(Default)] +pub struct SetupSbuild {} + +impl From for SetupSbuild { + fn from(_context: BuildContext) -> Self { + SetupSbuild {} + } +} +impl BuildStep for SetupSbuild { + fn step(&self) -> Result<(), BuildError> { + let home_dir = home_dir().ok_or(BuildError::HomeDirNotFound)?; + let dest_path = home_dir.join(".sbuildrc"); + let content = include_str!("../.sbuildrc"); + let home_dir = home_dir.to_str().unwrap_or("/home/runner").to_string(); + let replaced_contents = content.replace("", &home_dir); + + let mut file = fs::File::create(dest_path) + .map_err(|err| BuildError::FileCreationError(err.to_string()))?; + + file.write_all(replaced_contents.as_bytes()) + .map_err(|err| BuildError::FileWriteError(err.to_string()))?; + + Ok(()) + } +} diff --git a/workspace/packager_deb/src/steps/verify_hash.rs b/workspace/packager_deb/src/steps/verify_hash.rs new file mode 100644 index 00000000..0c762bfa --- /dev/null +++ b/workspace/packager_deb/src/steps/verify_hash.rs @@ -0,0 +1,262 @@ +use crate::misc::build_pipeline::{BuildContext, BuildError, BuildStep}; +use log::info; +use sha2::{Digest, Sha256, Sha512}; +use std::fs; +use std::io::Read; +use std::path::PathBuf; + +#[derive(Default)] +pub struct VerifyHash { + tarball_path: PathBuf, + tarball_hash: String, +} + +impl From for VerifyHash { + fn from(context: BuildContext) -> Self { + VerifyHash { + tarball_path: context.tarball_path.clone(), + tarball_hash: context.tarball_hash.clone(), + } + } +} + +impl VerifyHash { + fn calculate_sha256(mut reader: R) -> Result { + let mut hasher = Sha256::new(); + std::io::copy(&mut reader, &mut hasher)?; + let digest_bytes = hasher.finalize(); + let hex_digest = digest_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::(); + Ok(hex_digest) + } + + fn calculate_sha512(mut reader: R) -> Result { + let mut hasher = Sha512::new(); + std::io::copy(&mut reader, &mut hasher)?; + let digest_bytes = hasher.finalize(); + let hex_digest = digest_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::(); + Ok(hex_digest) + } + + fn verify_tarball_checksum( + &self, + tarball_path: &PathBuf, + expected_checksum: &str, + ) -> Result { + let mut file = fs::File::open(tarball_path) + .map_err(|err| BuildError::TarballOpenError(err.to_string()))?; + + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer) + .map_err(|err| BuildError::TarballReadError(err.to_string()))?; + + // Try SHA-512 first + let actual_sha512 = Self::calculate_sha512(&*buffer).unwrap_or_default(); + info!("sha512 hash {}", &actual_sha512); + if actual_sha512 == expected_checksum { + return Ok(true); + } + + // If SHA-512 doesn't match, try SHA-256 + let actual_sha256 = Self::calculate_sha256(&*buffer).unwrap_or_default(); + info!("sha256 hash {}", &actual_sha256); + if actual_sha256 == expected_checksum { + return Ok(true); + } + + Err(BuildError::HashMismatchError) + } +} + +impl BuildStep for VerifyHash { + fn step(&self) -> Result<(), BuildError> { + match self.verify_tarball_checksum(&self.tarball_path, &self.tarball_hash) { + Ok(_) => Ok(()), + Err(err) => Err(err), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::File; + use std::io::Write; + use tempfile::tempdir; + + fn create_temp_file(content: &[u8]) -> Result<(tempfile::TempDir, String), std::io::Error> { + let dir = tempdir()?; + let file_path = dir.path().join("test_tarball.tar.gz"); + let mut file = File::create(&file_path)?; + file.write_all(content)?; + Ok((dir, file_path.to_string_lossy().to_string())) + } + + fn read_tarball(tarball_path: &str) -> Result, std::io::Error> { + let mut file = std::fs::File::open(tarball_path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + Ok(buffer) + } + + #[test] + fn test_calculate_sha256() { + let data = b"abc"; + let expected = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"; + + let result = VerifyHash::calculate_sha256(&data[..]).unwrap(); + assert_eq!(result, expected); + } + + #[test] + fn test_calculate_sha512() { + let data = b"abc"; + let expected = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"; + + let result = VerifyHash::calculate_sha512(&data[..]).unwrap(); + assert_eq!(result, expected); + } + + #[test] + fn test_verify_tarball_checksum_sha256_success() { + let content = b"test content"; + let (_dir, file_path) = create_temp_file(content).unwrap(); + let buffer = read_tarball(&file_path).unwrap(); + + let expected_checksum = VerifyHash::calculate_sha256(&*buffer).unwrap(); + + let handler = VerifyHash::from(BuildContext::new()); + + let result = handler.verify_tarball_checksum(&file_path.into(), &expected_checksum); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), true); + } + + #[test] + fn test_verify_tarball_checksum_sha512_success() { + let content = b"test content"; + let (_dir, file_path) = create_temp_file(content).unwrap(); + let buffer = read_tarball(&file_path).unwrap(); + + let expected_checksum = VerifyHash::calculate_sha512(&*buffer).unwrap(); + + let handler = VerifyHash::from(BuildContext::new()); + + let result = handler.verify_tarball_checksum(&file_path.into(), &expected_checksum); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), true); + } + + #[test] + fn test_verify_tarball_checksum_mismatch() { + let content = b"test content"; + let (_dir, file_path) = create_temp_file(content).unwrap(); + + let incorrect_checksum = "0000000000000000000000000000000000000000000000000000000000000000"; + + let handler = VerifyHash::from(BuildContext::new()); + + let result = handler.verify_tarball_checksum(&file_path.into(), &incorrect_checksum); + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), BuildError::HashMismatchError)); + } + + #[test] + fn test_handle_with_checksum() { + let content = b"test content"; + let (_dir, file_path) = create_temp_file(content).unwrap(); + let buffer = read_tarball(&file_path).unwrap(); + + let expected_checksum = VerifyHash::calculate_sha256(&*buffer).unwrap(); + + let mut context = BuildContext::default(); + context.tarball_hash = expected_checksum; + context.tarball_path = file_path.into(); + let handler: VerifyHash = VerifyHash::from(context); + + let result = handler.step(); + assert!(result.is_ok()); + } + + #[test] + fn test_handle_without_checksum() { + let mut context: BuildContext = BuildContext::default(); + context.tarball_path = "some/path".into(); + let handler: VerifyHash = VerifyHash::from(context); + + let result = handler.step(); + assert!(result.is_err()); + } + + #[test] + fn test_verify_hash_valid_checksum_512() { + let tarball_path = "tests/misc/test_package.tar.gz"; + let expected_checksum = "abd0b8e99f983926dbf60bdcbaef13f83ec7b31d56e68f6252ed05981b237c837044ce768038fc34b71f925e2fb19b7dee451897db512bb4a99e0e1bc96d8ab3"; + let mut context: BuildContext = BuildContext::default(); + context.tarball_hash = expected_checksum.to_string(); + context.tarball_path = tarball_path.into(); + let handler: VerifyHash = VerifyHash::from(context); + + let result = handler.step(); + + assert!(result.is_ok()); + } + + #[test] + fn test_verify_hash_invalid_checksum_512() { + let tarball_path = "tests/misc/test_package.tar.gz"; + let expected_checksum = "abd0b8e99f983926dbf60bdcbaef13f83ec7b31d56e68f6252ed05981b237c837044ce768038fc34b71f925e2fb19b7dee451897db512bb4a99e0e1bc96d8ab2"; + + let mut context: BuildContext = BuildContext::default(); + context.tarball_hash = expected_checksum.to_string(); + context.tarball_path = tarball_path.into(); + + let handler = VerifyHash::from(context); + let result = handler.step(); + + assert!(result.is_err()); + assert_eq!( + result.err().unwrap().to_string(), + "Checksum verification failed: hashes do not match" + ); + } + + #[test] + fn test_verify_hash_valid_checksum_256() { + let tarball_path = "tests/misc/test_package.tar.gz"; + let expected_checksum = "b610e83c026d4c465636779240b6ed40a076593a61df5f6b9f9f59f1a929478d"; + + let mut context: BuildContext = BuildContext::default(); + context.tarball_hash = expected_checksum.to_string(); + context.tarball_path = tarball_path.into(); + let handler = VerifyHash::from(context); + + let result = handler.step(); + + assert!(result.is_ok()); + } + + #[test] + fn test_verify_hash_invalid_checksum_256() { + let tarball_path = "tests/misc/test_package.tar.gz"; + let expected_checksum = "b610e83c026d4c465636779240b6ed40a076593a61df5f6b9f9f59f1a929478_"; + + let mut context: BuildContext = BuildContext::default(); + context.tarball_hash = expected_checksum.to_string(); + context.tarball_path = tarball_path.into(); + let handler = VerifyHash::from(context); + + let result = handler.step(); + + assert!(result.is_err()); + assert_eq!( + result.err().unwrap().to_string(), + "Checksum verification failed: hashes do not match" + ); + } +} diff --git a/workspace/packager_deb/src/tools/autopkgtest_tool.rs b/workspace/packager_deb/src/tools/autopkgtest_tool.rs new file mode 100644 index 00000000..2dad942f --- /dev/null +++ b/workspace/packager_deb/src/tools/autopkgtest_tool.rs @@ -0,0 +1,207 @@ +use std::{fs::create_dir_all, path::PathBuf, process::Command}; + +use debian::{ + autopkgtest::Autopkgtest, autopkgtest_image::AutopkgtestImageBuilder, execute::Execute, +}; +use log::{info, warn}; +use types::{config::Architecture, distribution::Distribution}; + +use crate::{ + configs::autopkgtest_version::AutopkgtestVersion, misc::distribution::DistributionTrait, + sbuild::SbuildError, +}; + +use super::tool_runner::{BuildTool, ToolRunner}; + +pub struct AutopkgtestToolArgs { + pub (crate) version: AutopkgtestVersion, + pub (crate) changes_file: PathBuf, + pub (crate) codename: Distribution, + pub (crate) deb_dir: PathBuf, + pub (crate) test_deps: Vec, + pub (crate) image_path: Option, + pub (crate) cache_dir: PathBuf, + pub (crate) arch: Architecture, +} + +pub struct AutopkgtestTool { + args: AutopkgtestToolArgs, +} + +impl AutopkgtestTool { + pub fn new(args: AutopkgtestToolArgs) -> Self { + AutopkgtestTool { args } + } +} + +impl BuildTool for AutopkgtestTool { + fn name(&self) -> &str { + "autopkgtest" + } + fn check_tool_version(&self) -> Result<(), SbuildError> { + let output = Command::new("apt") + .args(vec!["list", "--installed", "autopkgtest"]) + .output()?; + + if !output.status.success() { + return Err(SbuildError::GenericError(format!( + "Failed to check {} version", + self.name() + ))); + } + + let stdout_str = String::from_utf8_lossy(&output.stdout).to_string(); + let actual_version = extract_version_from_apt_output(&stdout_str)?; + + match self.args.version.cmp(&actual_version) { + std::cmp::Ordering::Less => warn!( + "Using newer {} version ({}) than expected ({})", + self.name(), + actual_version, + self.args.version + ), + std::cmp::Ordering::Greater => warn!( + "Using older {} version ({}) than expected ({})", + self.name(), + actual_version, + self.args.version + ), + std::cmp::Ordering::Equal => info!("{} versions match ({})", self.name(), self.args.version), + } + + Ok(()) + } + + fn configure(&mut self, _runner: &mut ToolRunner) -> Result<(), SbuildError> { + info!("Running prepare_autopkgtest_image"); + let builder = AutopkgtestImageBuilder::new() + .codename(&self.args.codename)? + .image_path( + &self.args.cache_dir.display().to_string(), + &self.args.codename, + &self.args.arch, + ) + .mirror(self.args.codename.get_repo_url()) + .arch(&self.args.arch); + let image_path = builder.get_image_path().unwrap(); + let image_path_parent = image_path.parent().unwrap(); + if !image_path.exists() { + create_dir_all(image_path_parent)?; + + builder.execute()?; + } + + self.args.image_path = Some(image_path.clone()); + Ok(()) + } + fn execute(&self) -> Result<(), SbuildError> { + Autopkgtest::new() + .changes_file(self.args.changes_file.to_str().ok_or(SbuildError::GenericError( + "Invalid changes file path".to_string(), + ))?) + .no_built_binaries() + .apt_upgrade() + .test_deps_not_in_debian(&&self.args.test_deps) + .qemu(self.args.image_path.clone().unwrap()) + .working_dir(&self.args.deb_dir) + .execute()?; + Ok(()) + } +} + +fn extract_version_from_apt_output(output: &str) -> Result { + let version = output + .lines() + .filter(|line| line.trim_start().starts_with("autopkgtest")) + .flat_map(|line| line.split_whitespace()) + .find(|&word| { + let has_digits = word.chars().any(|c| c.is_digit(10)); + let has_dot = word.contains('.'); + has_digits && has_dot + }) + .ok_or_else(|| SbuildError::GenericError("Could not find version string".to_string()))?; + + // For Ubuntu-style versions (e.g., "5.38ubuntu1~24.04.1"), take only major.minor + // For Debian-style versions (e.g., "5.20.3-1"), take the full version + let cleaned_version = if version.contains("ubuntu") || version.contains('~') { + // Ubuntu-style: take only major.minor + version + .split(|c: char| !c.is_digit(10) && c != '.') + .next() + .unwrap_or(version) + .trim() + } else { + // Debian-style: take the full version + version.trim() + }; + + let version = AutopkgtestVersion::try_from(cleaned_version)?; + Ok(version) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::configs::autopkgtest_version::AutopkgtestVersion; + + #[test] + fn test_extract_version_from_apt_output_debian() { + // Standard apt list output + let output = "Listing... Done\nautopkgtest/stable,now 5.20 amd64 [installed]"; + let extracted = extract_version_from_apt_output(output); + assert!(extracted.is_ok()); + let expected_version = AutopkgtestVersion::try_from("5.20").unwrap(); + assert_eq!(extracted.unwrap(), expected_version); + + // Multiple lines with version in the middle + let output = "Listing... Done\npkg1/stable 1.0\nautopkgtest/stable,now 5.20.3-1 amd64 [installed]\npkg2/stable 2.0"; + let extracted = extract_version_from_apt_output(output); + assert!(extracted.is_ok()); + let expected_version = AutopkgtestVersion::try_from("5.20.3-1").unwrap(); + assert_eq!(extracted.unwrap(), expected_version); + + // No version digit + let output = "Listing... Done\nautopkgtest not installed"; + let extracted = extract_version_from_apt_output(output); + assert!(extracted.is_err()); + + // Empty output + let output = ""; + let extracted = extract_version_from_apt_output(output); + assert!(extracted.is_err()); + + // Version with multiple dots + let output = "autopkgtest/stable,now 5.20.3.2 amd64 [installed]"; + let extracted = extract_version_from_apt_output(output); + + assert!(extracted.is_err()); + } + + #[test] + fn test_extract_version_from_apt_output_ubuntu() { + // Standard apt list output + let output = + "Listing...\nautopkgtest/noble-updates,now 5.38ubuntu1~24.04.1 all [installed]"; + let extracted = extract_version_from_apt_output(output); + assert!(extracted.is_ok()); + let expected_version = AutopkgtestVersion::try_from("5.38").unwrap(); + assert_eq!(extracted.unwrap(), expected_version); + + // Multiple lines with version in the middle + let output = "Listing... Done\npkg1/stable 1.0\nListing...\nautopkgtest/noble-updates,now 5.38ubuntu1~24.04.1 all [installed]\npkg2/stable 2.0"; + let extracted = extract_version_from_apt_output(output); + assert!(extracted.is_ok()); + let expected_version = AutopkgtestVersion::try_from("5.38").unwrap(); + assert_eq!(extracted.unwrap(), expected_version); + + // No version digit + let output = "Listing... Done\nautopkgtest not installed"; + let extracted = extract_version_from_apt_output(output); + assert!(extracted.is_err()); + + // Empty output + let output = ""; + let extracted = extract_version_from_apt_output(output); + assert!(extracted.is_err()); + } +} diff --git a/workspace/packager_deb/src/tools/lintian_tool.rs b/workspace/packager_deb/src/tools/lintian_tool.rs new file mode 100644 index 00000000..ccd08a76 --- /dev/null +++ b/workspace/packager_deb/src/tools/lintian_tool.rs @@ -0,0 +1,77 @@ +use std::{path::PathBuf, process::Command}; + +use debian::{execute::Execute, lintian::Lintian}; +use log::{info, warn}; +use types::{distribution::Distribution, version::Version}; + +use crate::sbuild::SbuildError; + +use super::tool_runner::{BuildTool, ToolRunner}; + +pub struct LintianToolArgs { + pub (crate) version: Version, + pub (crate) changes_file: PathBuf, + pub (crate) codename: Distribution, +} + +pub struct LintianTool { + args: LintianToolArgs, +} + +impl LintianTool { + pub fn new(args: LintianToolArgs) -> Self { + LintianTool { args } + } +} + +impl BuildTool for LintianTool { + fn name(&self) -> &str { + "lintian" + } + fn check_tool_version(&self) -> Result<(), SbuildError> { + let output = Command::new(self.name()).arg("--version").output()?; + if !output.status.success() { + return Err(SbuildError::GenericError(format!( + "Failed to check {} version", + self.name() + ))); + } + let stdout_str = String::from_utf8_lossy(&output.stdout).to_string(); + let actual_version = Version::try_from(stdout_str)?; + + match self.args.version.cmp(&actual_version) { + std::cmp::Ordering::Less => warn!( + "Using newer {} version ({}) than expected ({})", + self.name(), + actual_version, + self.args.version + ), + std::cmp::Ordering::Greater => warn!( + "Using older {} version ({}) than expected ({})", + self.name(), + actual_version, + self.args.version + ), + std::cmp::Ordering::Equal => info!("{} versions match ({})", self.name(), self.args.version), + } + Ok(()) + } + fn configure(&mut self, _runner: &mut ToolRunner) -> Result<(), SbuildError> { + // Configure Lintian-specific options + Ok(()) + } + fn execute(&self) -> Result<(), SbuildError> { + Lintian::new() + .suppress_tag("bad-distribution-in-changes-file") + .info() + .extended_info() + .changes_file(&self.args.changes_file) + .tag_display_limit(0) + .fail_on_warning() + .fail_on_error() + .suppress_tag("debug-file-with-no-debug-symbols") + .with_codename(&self.args.codename) + .execute()?; + Ok(()) + } +} diff --git a/workspace/packager_deb/src/tools/mod.rs b/workspace/packager_deb/src/tools/mod.rs new file mode 100644 index 00000000..b171cd1e --- /dev/null +++ b/workspace/packager_deb/src/tools/mod.rs @@ -0,0 +1,5 @@ +pub mod autopkgtest_tool; +pub mod lintian_tool; +pub mod piuparts_tool; +pub mod sbuild_tool; +pub mod tool_runner; diff --git a/workspace/packager_deb/src/tools/piuparts_tool.rs b/workspace/packager_deb/src/tools/piuparts_tool.rs new file mode 100644 index 00000000..8cf8a631 --- /dev/null +++ b/workspace/packager_deb/src/tools/piuparts_tool.rs @@ -0,0 +1,83 @@ +use std::{path::PathBuf, process::Command}; + +use debian::{execute::Execute, piuparts::Piuparts}; +use log::{info, warn}; +use types::{distribution::Distribution, version::Version}; + +use crate::{ + configs::pkg_config::LanguageEnv, misc::distribution::DistributionTrait, sbuild::SbuildError, +}; + +use super::tool_runner::{BuildTool, ToolRunner}; + +pub struct PiupartsToolArgs { + pub(crate) version: Version, + pub(crate) codename: Distribution, + pub(crate) deb_dir: PathBuf, + pub(crate) language_env: Option, + pub(crate) deb_name: PathBuf, +} + +pub struct PiupartsTool { + args: PiupartsToolArgs, +} + +impl PiupartsTool { + pub fn new(args: PiupartsToolArgs) -> Self { + PiupartsTool { args } + } +} + +impl BuildTool for PiupartsTool { + fn name(&self) -> &str { + "piuparts" + } + fn check_tool_version(&self) -> Result<(), SbuildError> { + let output = Command::new(self.name()).arg("--version").output()?; + if !output.status.success() { + return Err(SbuildError::GenericError(format!( + "Failed to check {} version", + self.name() + ))); + } + let stdout_str = String::from_utf8_lossy(&output.stdout).to_string(); + let actual_version = Version::try_from(stdout_str.replace("piuparts ", "").trim())?; + + match self.args.version.cmp(&actual_version) { + std::cmp::Ordering::Less => warn!( + "Using newer {} version ({}) than expected ({})", + self.name(), + actual_version, + self.args.version + ), + std::cmp::Ordering::Greater => warn!( + "Using older {} version ({}) than expected ({})", + self.name(), + actual_version, + self.args.version + ), + std::cmp::Ordering::Equal => info!("{} versions match ({})", self.name(), self.args.version), + } + Ok(()) + } + fn configure(&mut self, _runner: &mut ToolRunner) -> Result<(), SbuildError> { + // Configure piuparts + Ok(()) + } + fn execute(&self) -> Result<(), SbuildError> { + Piuparts::new() + .distribution(&self.args.codename) + .mirror(&self.args.codename.get_repo_url()) + .bindmount_dev() + .keyring(&self.args.codename.get_keyring()) + .verbose() + .with_dotnet_env( + matches!(self.args.language_env, Some(LanguageEnv::Dotnet(_))), + &self.args.codename, + ) + .deb_file(&self.args.deb_name) + .deb_path(&self.args.deb_dir) + .execute()?; + Ok(()) + } +} diff --git a/workspace/packager_deb/src/tools/sbuild_tool.rs b/workspace/packager_deb/src/tools/sbuild_tool.rs new file mode 100644 index 00000000..8e04afe1 --- /dev/null +++ b/workspace/packager_deb/src/tools/sbuild_tool.rs @@ -0,0 +1,107 @@ +use std::{path::PathBuf, process::Command}; + +use debian::{execute::Execute, sbuild::SbuildBuilder}; +use log::{error, info, warn}; +use types::distribution::Distribution; + +use crate::{ + configs::{pkg_config::PackageType, sbuild_version::SbuildVersion}, + misc::{ + build_pipeline::BuildContext, + sbuild_pipelines::{SbuildGitPipeline, SbuildSourcePipeline, SbuildVirtualPipeline}, + }, + sbuild::SbuildError, +}; + +use super::tool_runner::{BuildTool, ToolRunner}; + +pub struct SbuildToolArgs { + pub(crate) version: SbuildVersion, + pub(crate) codename: Distribution, + pub(crate) cache_file: PathBuf, + pub(crate) build_chroot_setup_commands: Vec, + pub(crate) run_lintian: bool, + pub(crate) build_files_dir: PathBuf, + pub(crate) package_type: PackageType, + pub(crate) context: BuildContext, +} +pub struct SbuildTool { + args: SbuildToolArgs, +} + +impl SbuildTool { + pub fn new(args: SbuildToolArgs) -> Self { + SbuildTool { args } + } +} + +impl BuildTool for SbuildTool { + fn name(&self) -> &str { + "sbuild" + } + fn check_tool_version(&self) -> Result<(), SbuildError> { + let output = Command::new(self.name()).arg("--version").output()?; + if !output.status.success() { + return Err(SbuildError::GenericError(format!( + "Failed to check {} version", + self.name() + ))); + } + let stdout_str = String::from_utf8_lossy(&output.stdout).to_string(); + let actual_version = SbuildVersion::try_from(stdout_str)?; + + match self.args.version.cmp(&actual_version) { + std::cmp::Ordering::Less => warn!( + "Using newer {} version ({}) than expected ({})", + self.name(), + actual_version, + self.args.version + ), + std::cmp::Ordering::Greater => error!( + "Using older {} version ({}) than expected ({})", + self.name(), + actual_version, + self.args.version + ), + std::cmp::Ordering::Equal => info!("{} versions match ({})", self.name(), self.args.version), + } + Ok(()) + } + fn configure(&mut self, _runner: &mut ToolRunner) -> Result<(), SbuildError> { + info!("Using build context: {:#?}", self.args.context); + match &self.args.package_type { + PackageType::Default(_) => { + let sbuild_setup = SbuildSourcePipeline::new(self.args.context.clone()); + sbuild_setup.execute()?; + } + PackageType::Git(_) => { + let sbuild_setup = SbuildGitPipeline::new(self.args.context.clone()); + sbuild_setup.execute()?; + } + PackageType::Virtual => { + let sbuild_setup = SbuildVirtualPipeline::new(self.args.context.clone()); + sbuild_setup.execute()?; + } + }; + Ok(()) + } + fn execute(&self) -> Result<(), SbuildError> { + let builder = SbuildBuilder::new() + .distribution(&self.args.codename) + .build_arch_all() + .build_source() + .cache_file(self.args.cache_file.clone()) + .verbose() + .chroot_mode_unshare() + .setup_commands(&self.args.build_chroot_setup_commands) + .no_run_piuparts() + .no_apt_upgrades() + .run_lintian(self.args.run_lintian) + .no_run_autopkgtest() + .working_dir(&self.args.build_files_dir); + + builder.execute()?; + + Ok(()) + } +} diff --git a/workspace/packager_deb/src/tools/tool_runner.rs b/workspace/packager_deb/src/tools/tool_runner.rs new file mode 100644 index 00000000..4b0d8fe5 --- /dev/null +++ b/workspace/packager_deb/src/tools/tool_runner.rs @@ -0,0 +1,27 @@ +use log::info; +// use types::version::Version; + +use crate::sbuild::SbuildError; + +pub trait BuildTool { + fn name(&self) -> &str; + fn check_tool_version(&self) -> Result<(), SbuildError>; + fn configure(&mut self, runner: &mut ToolRunner) -> Result<(), SbuildError>; + fn execute(&self) -> Result<(), SbuildError>; +} + +pub struct ToolRunner {} + +impl ToolRunner { + pub fn new() -> Self { + ToolRunner {} + } + + pub fn run_tool(&mut self, mut tool: T) -> Result<(), SbuildError> { + info!("Running {}...", tool.name()); + tool.check_tool_version()?; + tool.configure(self)?; + tool.execute()?; + Ok(()) + } +} diff --git a/workspace/packager_deb/test_file.txt b/workspace/packager_deb/test_file.txt new file mode 100644 index 00000000..08cf6101 --- /dev/null +++ b/workspace/packager_deb/test_file.txt @@ -0,0 +1 @@ +test content \ No newline at end of file diff --git a/tests/misc/test_package.tar.gz b/workspace/packager_deb/tests/misc/test_package.tar.gz similarity index 100% rename from tests/misc/test_package.tar.gz rename to workspace/packager_deb/tests/misc/test_package.tar.gz diff --git a/workspace/pkg_builder/Cargo.toml b/workspace/pkg_builder/Cargo.toml new file mode 100644 index 00000000..814a05b9 --- /dev/null +++ b/workspace/pkg_builder/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "pkg-builder" +version = "0.3.0" +edition = "2024" +build = "src/build.rs" + +[[bin]] +name = "pkg-builder" +path = "src/main.rs" + + +[build-dependencies] +thiserror = { workspace = true } +serde = { workspace = true } +toml = { workspace = true } + +[dependencies] +cli = { workspace = true } \ No newline at end of file diff --git a/workspace/pkg_builder/bin_dependencies/.crates.toml b/workspace/pkg_builder/bin_dependencies/.crates.toml new file mode 100644 index 00000000..4fb4c9fa --- /dev/null +++ b/workspace/pkg_builder/bin_dependencies/.crates.toml @@ -0,0 +1,2 @@ +[v1] +"debcrafter 0.2.0 (git+https://github.com/Kixunil/debcrafter?rev=8189263#8189263400e69b747dcbf45de0a702993566fe47)" = ["debcrafter"] diff --git a/workspace/pkg_builder/bin_dependencies/.crates2.json b/workspace/pkg_builder/bin_dependencies/.crates2.json new file mode 100644 index 00000000..fa608923 --- /dev/null +++ b/workspace/pkg_builder/bin_dependencies/.crates2.json @@ -0,0 +1 @@ +{"installs":{"debcrafter 0.2.0 (git+https://github.com/Kixunil/debcrafter?rev=8189263#8189263400e69b747dcbf45de0a702993566fe47)":{"version_req":null,"bins":["debcrafter"],"features":[],"all_features":false,"no_default_features":false,"profile":"release","target":"x86_64-unknown-linux-gnu","rustc":"rustc 1.85.0 (4d91de4e4 2025-02-17)\nbinary: rustc\ncommit-hash: 4d91de4e48198da2e33413efdcd9cd2cc0c46688\ncommit-date: 2025-02-17\nhost: x86_64-unknown-linux-gnu\nrelease: 1.85.0\nLLVM version: 19.1.7\n"}}} \ No newline at end of file diff --git a/workspace/pkg_builder/bin_dependencies/debcrafter_2711b53 b/workspace/pkg_builder/bin_dependencies/debcrafter_2711b53 new file mode 100755 index 00000000..683099f3 Binary files /dev/null and b/workspace/pkg_builder/bin_dependencies/debcrafter_2711b53 differ diff --git a/workspace/pkg_builder/bin_dependencies/debcrafter_8189263 b/workspace/pkg_builder/bin_dependencies/debcrafter_8189263 new file mode 100755 index 00000000..63357768 Binary files /dev/null and b/workspace/pkg_builder/bin_dependencies/debcrafter_8189263 differ diff --git a/src/build.rs b/workspace/pkg_builder/src/build.rs similarity index 70% rename from src/build.rs rename to workspace/pkg_builder/src/build.rs index e4a4d736..53153fd3 100644 --- a/src/build.rs +++ b/workspace/pkg_builder/src/build.rs @@ -1,14 +1,13 @@ -use eyre::Context; +use serde::Deserialize; use std::fs; +use std::io; use std::path::Path; use std::process::Command; -use serde::Deserialize; -use toml; #[derive(Debug, Deserialize)] pub struct Dependency { pub url: String, - pub commit_hash: String, + pub commit_hash: String, pub binary_name: String, pub original_binary_name: String, } @@ -26,26 +25,33 @@ pub struct Config { impl Config { pub fn load_config() -> Result { let toml_str = include_str!("dependencies.toml"); - let config = toml::from_str(toml_str).context("Failed to load and parse config.toml")?; + let config = toml::from_str(toml_str).map_err(|e| ConfigError::TomlParseError(e))?; Ok(config) } } #[derive(thiserror::Error, Debug)] pub enum ConfigError { - #[error(transparent)] - UnexpectedError(#[from] eyre::Error), -} - + #[error("Failed to parse TOML: {0}")] + TomlParseError(#[from] toml::de::Error), + #[error("IO error: {0}")] + IoError(#[from] io::Error), + #[error("Other error: {0}")] + Other(String), +} #[derive(thiserror::Error, Debug)] pub enum CargoError { #[error("Non-zero exit status: {0}")] StatusError(String), - #[error(transparent)] - UnexpectedError(#[from] eyre::Error), + + #[error("IO error: {0}")] + IoError(#[from] io::Error), + + #[error("Other error: {0}")] + Other(String), } pub struct Cargo<'a> { @@ -57,6 +63,7 @@ impl<'a> Cargo<'a> { // fn install_from_crates_io>(&self, bin_dir: P) -> Result<(), CargoError> { // todo!() // } + fn install_from_git>(&self, bin_dir: P) -> Result<(), CargoError> { let output = Command::new("cargo") .arg("install") @@ -67,23 +74,19 @@ impl<'a> Cargo<'a> { .arg("--root") .arg(bin_dir.as_ref()) .output() - .context("Failed to build dependency")?; + .map_err(|e| CargoError::IoError(e))?; if output.status.success() { Ok(()) } else { let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); - - Err(CargoError::StatusError( - format!( - "Cargo install failed with exit code {:?}\nstdout: {}\nstderr: {}", - output.status.code(), - stdout, - stderr - ) - .into(), - )) + Err(CargoError::StatusError(format!( + "Cargo install failed with exit code {:?}\nstdout: {}\nstderr: {}", + output.status.code(), + stdout, + stderr + ))) } } } @@ -92,13 +95,17 @@ impl<'a> Cargo<'a> { pub enum DependencyError { #[error(transparent)] CargoError(#[from] CargoError), - #[error(transparent)] - UnexpectedError(#[from] eyre::Error), + + #[error("IO error: {0}")] + IoError(#[from] io::Error), + + #[error("Other error: {0}")] + Other(String), } impl Dependency { fn install_binary(&self, bin_dir: String) -> Result<(), DependencyError> { - fs::create_dir_all(&bin_dir).context("Failed to create directory")?; + fs::create_dir_all(&bin_dir).map_err(|e| DependencyError::IoError(e))?; let binary_path = Path::new(&bin_dir.clone()).join(&self.binary_name); let bin_dir = Path::new(&bin_dir); @@ -111,8 +118,9 @@ impl Dependency { }; cargo.install_from_git(bin_dir)?; + fs::rename(original_binary_name_path, binary_path) - .context("Failed to rename the binary")?; + .map_err(|e| DependencyError::IoError(e))?; } Ok(()) @@ -121,12 +129,12 @@ impl Dependency { pub fn main() { let bin_dir = format!("{}/bin_dependencies", "."); - let config = Config::load_config().expect("Could not load config"); let dependencies = config.dependencies.binaries; - dependencies - .iter() - .for_each(|d| { let _ = d.install_binary(bin_dir.clone()); }); + + dependencies.iter().for_each(|d| { + let _ = d.install_binary(bin_dir.clone()); + }); println!("cargo:rerun-if-changed=build.rs"); } diff --git a/src/dependencies.toml b/workspace/pkg_builder/src/dependencies.toml similarity index 100% rename from src/dependencies.toml rename to workspace/pkg_builder/src/dependencies.toml diff --git a/src/main.rs b/workspace/pkg_builder/src/main.rs similarity index 73% rename from src/main.rs rename to workspace/pkg_builder/src/main.rs index 6a821b02..1303dea5 100644 --- a/src/main.rs +++ b/workspace/pkg_builder/src/main.rs @@ -1,14 +1,14 @@ -mod v1; +use cli::cli::run_cli; fn main() { - let result = v1::cli::run_cli(); + let result = run_cli(); match result { Ok(_) => { std::process::exit(0); - }, + } Err(err) => { println!("Failed to run: {:?}", err); std::process::exit(1); - }, + } } -} \ No newline at end of file +} diff --git a/workspace/types/Cargo.toml b/workspace/types/Cargo.toml new file mode 100644 index 00000000..4911dcb0 --- /dev/null +++ b/workspace/types/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "types" +version = "0.1.0" +edition = "2021" + +[lib] +name = "types" +path = "src/mod.rs" + +[dependencies] +serde ={ workspace = true} +thiserror = { workspace = true} +log = { workspace = true} +toml = { workspace = true} +semver = { workspace = true} +tempfile = {workspace = true} +url = { workspace = true } diff --git a/workspace/types/src/config.rs b/workspace/types/src/config.rs new file mode 100644 index 00000000..9a20249b --- /dev/null +++ b/workspace/types/src/config.rs @@ -0,0 +1,285 @@ +use log::warn; +use semver::Version; +use serde::de::Error as SerdeError; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::{ + borrow::Cow, + cmp::Ordering, + env, fs, + io::{self, ErrorKind}, + marker::PhantomData, + path::PathBuf, +}; +use thiserror::Error; +use toml::de::Error as TomlError; + +use crate::{ + defaults::{CONFIG_FILE_NAME, WORKDIR_ROOT}, + distribution::Distribution, +}; + +/// Represents the raw configuration file content +#[derive(Debug, Clone)] +pub struct ConfigFile { + content: Cow<'static, str>, + _marker: PhantomData, + pub path: PathBuf, +} +impl AsRef for ConfigFile { + fn as_ref(&self) -> &str { + &self.content + } +} + +/// Errors that can occur during configuration handling +#[derive(Debug, Error)] +pub enum ConfigError { + /// Error parsing TOML content + #[error("TOML parsing error: {0}")] + TomlParse(#[from] toml::de::Error), + + /// I/O error during file operations + #[error("I/O error: {0}")] + Io(#[from] io::Error), + + /// Version parsing error + #[error("Version parsing error: {0}")] + VersionParse(#[from] semver::Error), + + /// Incompatible package version + #[error("{0}")] + IncompatibleVersion(String), +} + +pub trait ConfigType { + fn default_config_path() -> &'static str; +} + +impl ConfigFile { + /// Loads configuration from the specified location or the current directory + /// + /// # Arguments + /// + /// * `config_path` - Optional path to configuration file or directory + /// + /// # Returns + /// + /// * `Result, ConfigError>` - The loaded configuration or an error + pub fn load(config_path: Option) -> Result { + let path = Self::resolve_config_path(config_path)?; + let content = fs::read_to_string(&path).map_err(ConfigError::Io)?; + Ok(ConfigFile { + content: Cow::Owned(content), + _marker: PhantomData, + path, + }) + } + + /// Resolves the configuration file path + fn resolve_config_path(config_path: Option) -> Result { + let path = match config_path { + Some(location) => { + let path = PathBuf::from(location); + if path.is_dir() { + path.join(T::default_config_path()) + } else { + path + } + } + None => env::current_dir() + .map_err(ConfigError::Io)? + .join(T::default_config_path()), + }; + + if !path.exists() { + return Err(ConfigError::Io(io::Error::new( + ErrorKind::NotFound, + format!("Path does not exist: {}", path.display()), + ))); + } + + Ok(path) + } + + /// Parses the configuration content into the generic type T + /// + /// # Returns + /// + /// * `Result` - The parsed configuration or an error + pub fn parse(self) -> Result + where + T: for<'de> serde::Deserialize<'de>, + { + toml::from_str::(&self.content).map_err(|e| { + if let Some(span) = e.span() { + let detailed_message = + format!("Error at position {}-{}: {}", span.start, span.end, e); + ConfigError::from(TomlError::custom(detailed_message)) + } else { + ConfigError::from(e) + } + }) + } + + /// Convenience method to load and parse in one operation + /// + /// # Arguments + /// + /// * `config_path` - Optional path to configuration file or directory + /// + /// # Returns + /// + /// * `Result` - The parsed configuration or an error + pub fn load_and_parse(config_path: Option) -> Result + where + T: for<'de> serde::Deserialize<'de>, + { + Self::load(config_path)?.parse() + } +} + +// not needed, just for wrapping the header +#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] +pub struct Config { + pub build_env: BuildEnv, +} +impl ConfigType for Config { + fn default_config_path() -> &'static str { + &CONFIG_FILE_NAME + } +} + +/// Build environment configuration +#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] +pub struct BuildEnv { + /// Distribution codename + pub codename: Distribution, + + /// Working directory path + pub workdir: PathBuf, + + /// Required pkg-builder version + pub pkg_builder_version: Version, +} + +impl BuildEnv { + /// Validate and apply defaults to the build environment configuration + /// + /// # Arguments + /// + /// * `current_pkg_version` - The current pkg-builder version to validate against + /// + /// # Returns + /// + /// * `Result` - The validated configuration or an error + pub fn validate_and_apply_defaults( + mut self, + current_pkg_version: &str, + ) -> Result { + // Parse the current version for comparison + let current_version = + Version::parse(current_pkg_version).map_err(ConfigError::VersionParse)?; + + let required_version = &self.pkg_builder_version; + + match required_version.cmp(¤t_version) { + Ordering::Greater => { + let incompatible_version = ConfigError::IncompatibleVersion(format!( + "Required pkg-builder version {} is higher than current version {}", + required_version, current_version + )); + return Err(incompatible_version); + } + Ordering::Less => { + warn!( + "Required pkg-builder version {} is lower than current version {}. This may cause compatibility issues.", + required_version, current_version + ); + } + Ordering::Equal => { + // Versions match, no action needed + } + } + + if self.workdir.as_os_str().is_empty() { + let default_path = format!("{}/{}", WORKDIR_ROOT, self.codename); + self.workdir = PathBuf::from(default_path); + } + + Ok(self) + } +} + + +#[derive(Debug, Deserialize, PartialEq, Clone)] +pub enum Architecture { + #[serde(rename = "amd64")] + Amd64 +} + +impl fmt::Display for Architecture { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Architecture::Amd64 => write!(f, "amd64"), + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use std::fs::File; + use std::io::Write; + use tempfile::tempdir; + + #[test] + fn test_config_file_load() { + let dir = tempdir().unwrap(); + let config_path = dir.path().join(CONFIG_FILE_NAME); + let mut file = File::create(&config_path).unwrap(); + + writeln!( + file, + r#" + [build_env] + codename = "noble numbat" + workdir = "/tmp/test" + pkg_builder_version = "1.0.0" + "# + ) + .unwrap(); + + let config_file = + ConfigFile::::load(Some(dir.path().to_string_lossy().to_string())).unwrap(); + assert!(config_file.content.contains("noble numbat")); + } + + #[test] + fn test_config_file_parse() { + let dir = tempdir().unwrap(); + let config_path = dir.path().join(CONFIG_FILE_NAME); + let mut file = File::create(&config_path).unwrap(); + + writeln!( + file, + r#" + [build_env] + codename = "noble numbat" + workdir = "/tmp/test" + pkg_builder_version = "1.0.0" + "# + ) + .unwrap(); + + let config_file = + ConfigFile::::load(Some(dir.path().to_string_lossy().to_string())).unwrap(); + let config = config_file.parse(); + assert!(config.is_ok()); + let config = config.unwrap(); + assert_eq!(config.build_env.codename, Distribution::noble()); + assert_eq!(config.build_env.workdir, PathBuf::from("/tmp/test")); + assert_eq!(config.build_env.pkg_builder_version, Version::new(1, 0, 0)); + } +} diff --git a/workspace/types/src/debian.rs b/workspace/types/src/debian.rs new file mode 100644 index 00000000..bceddec6 --- /dev/null +++ b/workspace/types/src/debian.rs @@ -0,0 +1,16 @@ +pub enum DebCommandPayload { + Verify { + verify_config: Option, + no_package: Option, + }, + Lintian, + Piuparts, + Autopkgtest, + Package { + run_lintian: Option, + run_piuparts: Option, + run_autopkgtest: Option, + }, + EnvCreate, + EnvClean, +} diff --git a/workspace/types/src/defaults.rs b/workspace/types/src/defaults.rs new file mode 100644 index 00000000..9001219e --- /dev/null +++ b/workspace/types/src/defaults.rs @@ -0,0 +1,4 @@ +/// Name of the default configuration file +pub const CONFIG_FILE_NAME: &str = "pkg-builder.toml"; +pub const VERIFY_CONFIG_FILE_NAME: &str = "pkg-builder-verify.toml"; +pub const WORKDIR_ROOT: &str = "~/.pkg-builder/packages"; diff --git a/workspace/types/src/distribution.rs b/workspace/types/src/distribution.rs new file mode 100644 index 00000000..6af9d189 --- /dev/null +++ b/workspace/types/src/distribution.rs @@ -0,0 +1,154 @@ +use std::fmt; + +use serde::de; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum DistributionError { + #[error("Unsupported distribution codename: {0}")] + UnsupportedCodename(String), +} + +/// Represents supported Linux distributions for VM image creation +/// +/// Each variant contains the specific codename for the distribution +/// which is used to identify compatible build commands and arguments. +#[derive(Debug, Clone, PartialEq)] +pub enum Distribution { + /// Debian distribution + Debian(DebianCodename), + /// Ubuntu distribution + Ubuntu(UbuntuCodename), +} +impl fmt::Display for Distribution { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Distribution::Debian(debian) => debian.fmt(f), + Distribution::Ubuntu(ubuntu) => ubuntu.fmt(f), + } + } +} +/// Supported Debian codenames +#[derive(Debug, Clone, PartialEq)] +pub enum DebianCodename { + Bookworm, +} + +impl DebianCodename { + /// Get the string representation of the codename + pub fn as_str(&self) -> &'static str { + match self { + DebianCodename::Bookworm => "bookworm", + } + } + + pub fn as_short(&self) -> &'static str { + match self { + DebianCodename::Bookworm => "bookworm", + } + } +} +impl fmt::Display for DebianCodename { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} +/// Supported Ubuntu codenames +#[derive(Debug, Clone, PartialEq)] +pub enum UbuntuCodename { + Noble, + Jammy, +} + +impl UbuntuCodename { + /// Get the string representation of the codename + pub fn as_str(&self) -> &'static str { + match self { + UbuntuCodename::Noble => "noble numbat", + UbuntuCodename::Jammy => "jammy jellyfish", + } + } + pub fn as_short(&self) -> &'static str { + match self { + UbuntuCodename::Noble => "noble", + UbuntuCodename::Jammy => "jammy", + } + } +} + +impl fmt::Display for UbuntuCodename { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +impl Distribution { + /// Creates a Debian Bookworm distribution + pub fn bookworm() -> Self { + Distribution::Debian(DebianCodename::Bookworm) + } + + /// Creates an Ubuntu Noble Numbat distribution + pub fn noble() -> Self { + Distribution::Ubuntu(UbuntuCodename::Noble) + } + + /// Creates an Ubuntu Jammy Jellyfish distribution + pub fn jammy() -> Self { + Distribution::Ubuntu(UbuntuCodename::Jammy) + } + + /// Creates a Distribution from a codename string + /// + /// # Arguments + /// * `codename` - The distribution codename (e.g., "bookworm", "noble") + /// + /// # Returns + /// * `Result` - A Distribution instance or an error if unsupported + pub fn from_codename(codename: &str) -> Result { + match codename { + "bookworm" => Ok(Self::bookworm()), + "noble" | "noble numbat" => Ok(Self::noble()), + "jammy" | "jammy jellyfish" => Ok(Self::jammy()), + _ => Err(DistributionError::UnsupportedCodename(codename.to_string())), + } + } + + pub fn as_short(&self) -> &str { + match self { + Distribution::Debian(codename) => codename.as_short(), + Distribution::Ubuntu(codename) => codename.as_short(), + } + } +} + +impl AsRef for Distribution { + fn as_ref(&self) -> &str { + match self { + Distribution::Debian(codename) => codename.as_str(), + Distribution::Ubuntu(codename) => codename.as_str(), + } + } +} +impl Serialize for Distribution { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Distribution::Debian(codename) => serializer.serialize_str(codename.as_str()), + Distribution::Ubuntu(codename) => serializer.serialize_str(codename.as_str()), + } + } +} + +impl<'de> Deserialize<'de> for Distribution { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let codename = String::deserialize(deserializer)?; + Distribution::from_codename(&codename).map_err(|e| de::Error::custom(format!("{}", e))) + } +} diff --git a/workspace/types/src/mod.rs b/workspace/types/src/mod.rs new file mode 100644 index 00000000..81406efe --- /dev/null +++ b/workspace/types/src/mod.rs @@ -0,0 +1,7 @@ +pub mod distribution; + +pub mod config; +pub mod debian; +pub mod defaults; +pub mod url; +pub mod version; diff --git a/workspace/types/src/url.rs b/workspace/types/src/url.rs new file mode 100644 index 00000000..052e1827 --- /dev/null +++ b/workspace/types/src/url.rs @@ -0,0 +1,92 @@ +use serde::{de, Deserialize, Deserializer, Serialize}; +use std::fmt; +use std::ops::{Deref, DerefMut}; +use url::Url as OriginalUrl; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Url(OriginalUrl); + +impl Serialize for Url { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.0.as_str()) + } +} + +impl Deref for Url { + type Target = OriginalUrl; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Url { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From for Url { + fn from(url: OriginalUrl) -> Self { + Url(url) + } +} + +impl Url { + pub fn into_inner(self) -> OriginalUrl { + self.0 + } +} + +impl<'a> TryFrom<&'a str> for Url { + type Error = url::ParseError; + + fn try_from(s: &'a str) -> Result { + let inner = OriginalUrl::parse(s)?; + Ok(Url(inner)) + } +} + +impl TryFrom for Url { + type Error = url::ParseError; + + fn try_from(s: String) -> Result { + >::try_from(&s) + } +} + +impl<'de> Deserialize<'de> for Url { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct UrlVisitor; + + impl<'de> de::Visitor<'de> for UrlVisitor { + type Value = Url; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string containing a valid URL") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + OriginalUrl::parse(value) + .map(Url) + .map_err(de::Error::custom) + } + } + + deserializer.deserialize_string(UrlVisitor) + } +} + +impl AsRef for Url { + fn as_ref(&self) -> &str { + self.0.as_str() + } +} diff --git a/workspace/types/src/version.rs b/workspace/types/src/version.rs new file mode 100644 index 00000000..c4ea8b49 --- /dev/null +++ b/workspace/types/src/version.rs @@ -0,0 +1,111 @@ +use semver::Version as OriginalVersion; +use serde::{de, Deserialize, Deserializer, Serialize}; +use std::borrow::Cow; +use std::fmt; +use std::ops::{Deref, DerefMut}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Version { + inner: OriginalVersion, + original_string: Cow<'static, str>, +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.original_string) + } +} + +impl Version { + pub fn as_str(&self) -> &str { + &self.original_string + } +} + +impl Serialize for Version { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.original_string) + } +} + +impl Deref for Version { + type Target = OriginalVersion; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for Version { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl From for Version { + fn from(version: OriginalVersion) -> Self { + let original_string = Cow::Owned(version.to_string()); + Version { + inner: version, + original_string, + } + } +} + +impl Version { + pub fn into_inner(self) -> OriginalVersion { + self.inner + } +} + +impl<'a> TryFrom<&'a str> for Version { + type Error = semver::Error; + + fn try_from(s: &'a str) -> Result { + let inner = OriginalVersion::parse(s)?; + Ok(Version { + inner, + original_string: Cow::Owned(s.to_string()), + }) + } +} + +impl TryFrom for Version { + type Error = semver::Error; + + fn try_from(s: String) -> Result { + >::try_from(&s) + } +} + +impl<'de> Deserialize<'de> for Version { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct VersionVisitor; + + impl<'de> de::Visitor<'de> for VersionVisitor { + type Value = Version; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string containing a valid semantic version (e.g., 1.2.3)") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + let inner = OriginalVersion::parse(value).map_err(de::Error::custom)?; + Ok(Version { + inner, + original_string: Cow::Owned(value.to_string()), + }) + } + } + + deserializer.deserialize_string(VersionVisitor) + } +}