envio 0.8.0

A secure command-line tool for managing environment variables
name: CICD

env:
  CICD_INTERMEDIATES_DIR: "_cicd-intermediates"

on:
  workflow_dispatch:
  pull_request:
    paths:
      - "src/**"
      - ".github/**"
      - "Cargo.toml"
      - "Cargo.lock"
      - "build/**"
  push:
    branches:
      - main
    paths:
      - "src/**"
      - ".github/**"
      - "Cargo.toml"
      - "Cargo.lock"
      - "build/**"
    tags:
      - "*"

permissions:
  contents: write

jobs:
  min_version:
    name: Minimum supported rust version
    runs-on: ubuntu-latest
    steps:
      - name: Checkout source code
        uses: actions/checkout@v4
      - name: Get the MSRV from the package metadata
        id: msrv
        run: cargo metadata --no-deps --format-version 1 | jq -r '"version=" + (.packages[] | select(.name == "envio").rust_version)' >> $GITHUB_OUTPUT
      - name: Install rust toolchain (v${{ steps.msrv.outputs.version }})
        uses: dtolnay/rust-toolchain@master
        with:
          toolchain: ${{ steps.msrv.outputs.version }}
          components: clippy, rustfmt
      - name: Run cargo fmt
        run: cargo fmt -- --check
      - name: Install libdbus-1-dev
        run: sudo apt-get update -y && sudo apt-get install -y libdbus-1-dev
      - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix)
        run: cargo clippy --locked --all-targets ${{ env.MSRV_FEATURES }}
      - name: Run tests
        run: cargo test --locked ${{ env.MSRV_FEATURES }}

  build:
    name: ${{ matrix.job.os }} (${{ matrix.job.target }})
    runs-on: ${{ matrix.job.os }}
    strategy:
      fail-fast: false
      matrix:
        job:
          - {
              target: x86_64-unknown-linux-gnu,
              os: ubuntu-latest
            }
          - {
              target: aarch64-unknown-linux-gnu,
              os: ubuntu-latest,
              use-cross: true,
            }
          - {
              target: i686-unknown-linux-gnu,
              os: ubuntu-latest,
              use-cross: true,
            }
          - { target: x86_64-apple-darwin, os: macos-15-intel  }
          - { target: aarch64-apple-darwin, os: macos-latest  }
          - { target: x86_64-pc-windows-gnu, os: windows-2025, arch: x86_64 }
          - { target: i686-pc-windows-msvc, os: windows-2025, arch: x86 }

    env:
      BUILD_CMD: cargo
    steps:
      - name: Set PKG_CONFIG
        if: matrix.job.use-cross
        shell: bash
        run: echo "PKG_CONFIG=/usr/bin/pkg-config" >> $GITHUB_ENV

      - name: Checkout source code
        uses: actions/checkout@v4

      - name: Install prerequisites
        shell: bash
        run: |
          case ${{ matrix.job.target }} in
            arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
            aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
          esac

      - name: Extract crate information
        shell: bash
        run: |
          echo "PROJECT_NAME=envio" >> $GITHUB_ENV
          echo "PROJECT_VERSION=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)" >> $GITHUB_ENV
          echo "PROJECT_DESCRIPTION=$(sed -n 's/^description = "\(.*\)"/\1/p' Cargo.toml)" >> $GITHUB_ENV
          echo "PROJECT_MAINTAINER=$(sed -n 's/^authors = \["\(.*\)"\]/\1/p' Cargo.toml)" >> $GITHUB_ENV
          echo "PROJECT_HOMEPAGE=$(sed -n 's/^homepage = "\(.*\)"/\1/p' Cargo.toml)" >> $GITHUB_ENV
          echo "RUST_VERSION=$(sed -n 's/^rust-version = "\(.*\)"/\1/p' Cargo.toml | head -n1)" >> $GITHUB_ENV

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@master
        with:
          toolchain: ${{ env.RUST_VERSION }}
          targets: ${{ matrix.job.target }}

      - name: Show version information (Rust, cargo, GCC)
        shell: bash
        run: |
          gcc --version || true
          rustup -V
          rustup toolchain list
          rustup default
          cargo -V
          rustc -V

      - name: Install cross
        if: matrix.job.use-cross
        shell: bash
        run: |
          cargo install cross --git https://github.com/cross-rs/cross

      - name: Overwrite build command env variable
        if: matrix.job.use-cross
        shell: bash
        run: |
          echo "BUILD_CMD=cross" >> $GITHUB_ENV

      - name: Build
        shell: bash
        run: |
          $BUILD_CMD build --locked --release --target=${{ matrix.job.target }}

      - name: Set binary path
        id: bin
        shell: bash
        run: |
          EXE_suffix=""
          case ${{ matrix.job.target }} in
            *-pc-windows-*) EXE_suffix=".exe" ;;
          esac;

          BIN_NAME="${{ env.PROJECT_NAME }}${EXE_suffix}"
          BIN_PATH="target/${{ matrix.job.target }}/release/${BIN_NAME}"

          echo "BIN_PATH=${BIN_PATH}" >> $GITHUB_OUTPUT
          echo "BIN_NAME=${BIN_NAME}" >> $GITHUB_OUTPUT

      - name: Run tests
        shell: bash
        run: $BUILD_CMD test --locked --target=${{ matrix.job.target }}

      - name: Create tarball
        id: package
        shell: bash
        run: |
          PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
          PKG_BASENAME=${PROJECT_NAME}-v${PROJECT_VERSION}-${{ matrix.job.target }}
          PKG_NAME=${PKG_BASENAME}${PKG_suffix}
          echo "PKG_NAME=${PKG_NAME}" >> $GITHUB_OUTPUT

          PKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/package"
          ARCHIVE_DIR="${PKG_STAGING}/${PKG_BASENAME}/"
          mkdir -p "${ARCHIVE_DIR}"
          mkdir -p "${ARCHIVE_DIR}/autocomplete"

          cp "${{ steps.bin.outputs.BIN_PATH }}" "$ARCHIVE_DIR"

          cp 'man/${{ env.PROJECT_NAME }}.1' "$ARCHIVE_DIR"

          cp "README.md" "LICENSE-MIT" "LICENSE-APACHE" "CHANGELOG.md" "$ARCHIVE_DIR"

          cp completions/'${{ env.PROJECT_NAME }}.bash' "$ARCHIVE_DIR/autocomplete/"
          cp completions/'${{ env.PROJECT_NAME }}.fish' "$ARCHIVE_DIR/autocomplete/"
          cp completions/'_${{ env.PROJECT_NAME }}'  "$ARCHIVE_DIR/autocomplete/"
          cp completions/'_${{ env.PROJECT_NAME }}.ps1'  "$ARCHIVE_DIR/autocomplete/"

          pushd "${PKG_STAGING}/" >/dev/null
          case ${{ matrix.job.target }} in
            *-pc-windows-*) 7z -y a "${PKG_NAME}" "${PKG_BASENAME}"/* | tail -2 ;;
            *) tar czf "${PKG_NAME}" "${PKG_BASENAME}"/* ;;
          esac;
          popd >/dev/null

          echo "PKG_PATH=${PKG_STAGING}/${PKG_NAME}" >> $GITHUB_OUTPUT

      - name: Create Debian package
        id: debian-package
        shell: bash
        if: startsWith(matrix.job.os, 'ubuntu')
        run: |
          COPYRIGHT_YEARS="2023 - "$(date "+%Y")
          DPKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/debian-package"
          DPKG_DIR="${DPKG_STAGING}/dpkg"
          mkdir -p "${DPKG_DIR}"
          DPKG_BASENAME=${PROJECT_NAME}
          DPKG_CONFLICTS=${PROJECT_NAME}-musl
          case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${PROJECT_NAME}-musl ; DPKG_CONFLICTS=${PROJECT_NAME} ;; esac;
          DPKG_VERSION=${PROJECT_VERSION}
          unset DPKG_ARCH
          case ${{ matrix.job.target }} in
            aarch64-*-linux-*) DPKG_ARCH=arm64 ;;
            arm-*-linux-*hf) DPKG_ARCH=armhf ;;
            i686-*-linux-*) DPKG_ARCH=i686 ;;
            x86_64-*-linux-*) DPKG_ARCH=amd64 ;;
            *) DPKG_ARCH=notset ;;
          esac;
          DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb"
          echo "DPKG_NAME=${DPKG_NAME}" >> $GITHUB_OUTPUT
          # Binary
          install -Dm755 "${{ steps.bin.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.bin.outputs.BIN_NAME }}"
          # Man page
          install -Dm644 'man/${{ env.PROJECT_NAME }}.1' "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1"
          gzip -n --best "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1"
          # Autocompletion files
          install -Dm644 completions/'${{ env.PROJECT_NAME }}.bash' "${DPKG_DIR}/usr/share/bash-completion/completions/${{ env.PROJECT_NAME }}"
          install -Dm644 completions/'${{ env.PROJECT_NAME }}.fish' "${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ env.PROJECT_NAME }}.fish"
          install -Dm644 completions/'_${{ env.PROJECT_NAME }}' "${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ env.PROJECT_NAME }}"
          # README and LICENSE
          install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/README.md"
          install -Dm644 "LICENSE-MIT" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-MIT"
          install -Dm644 "LICENSE-APACHE" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-APACHE"
          install -Dm644 "CHANGELOG.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog"
          gzip -n --best "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog"
          cat > "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright" <<EOF
          Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
          Upstream-Name: ${{ env.PROJECT_NAME }}
          Source: ${{ env.PROJECT_HOMEPAGE }}
          Files: *
          Copyright: ${{ env.PROJECT_MAINTAINER }}
          Copyright: $COPYRIGHT_YEARS ${{ env.PROJECT_MAINTAINER }}
          License: Apache-2.0 or MIT
          License: Apache-2.0
            On Debian systems, the complete text of the Apache-2.0 can be found in the
            file /usr/share/common-licenses/Apache-2.0.
          License: MIT
            Permission is hereby granted, free of charge, to any
            person obtaining a copy of this software and associated
            documentation files (the "Software"), to deal in the
            Software without restriction, including without
            limitation the rights to use, copy, modify, merge,
            publish, distribute, sublicense, and/or sell copies of
            the Software, and to permit persons to whom the Software
            is furnished to do so, subject to the following
            conditions:
            .
            The above copyright notice and this permission notice
            shall be included in all copies or substantial portions
            of the Software.
            .
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
            ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
            TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
            PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
            SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
            CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
            IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
            DEALINGS IN THE SOFTWARE.
          EOF
            chmod 644 "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright"
            # control file
            mkdir -p "${DPKG_DIR}/DEBIAN"
            cat > "${DPKG_DIR}/DEBIAN/control" <<EOF
          Package: ${DPKG_BASENAME}
          Version: ${DPKG_VERSION}
          Section: utils
          Priority: optional
          Maintainer: ${{ env.PROJECT_MAINTAINER }}
          Homepage: ${{ env.PROJECT_HOMEPAGE }}
          Architecture: ${DPKG_ARCH}
          Provides: ${{ env.PROJECT_NAME }}
          Conflicts: ${DPKG_CONFLICTS}
          Description: ${{ env.PROJECT_DESCRIPTION }}
          EOF
          DPKG_PATH="${DPKG_STAGING}/${DPKG_NAME}"
          echo "DPKG_PATH=${DPKG_PATH}" >> $GITHUB_OUTPUT
          # build dpkg
          fakeroot dpkg-deb --build "${DPKG_DIR}" "${DPKG_PATH}"

      - name: Set path for candle and light
        shell: bash
        if: startsWith(matrix.job.os, 'windows')
        run: echo "C:\Program Files (x86)\WiX Toolset v3.11\bin" >> $GITHUB_PATH

      - name: Build Windows Installer
        id: msi-installer
        shell: bash
        if: startsWith(matrix.job.os, 'windows')
        run: |
          cargo install cargo-wix
          cargo wix init
          cargo wix --target ${{ matrix.job.target }}

          PKG_NAME=${PROJECT_NAME}-${PROJECT_VERSION}-${{ matrix.job.arch }}.msi

          echo "PKG_NAME=${PKG_NAME}" >> $GITHUB_OUTPUT
          echo "PKG_PATH=target/wix/${PKG_NAME}" >> $GITHUB_OUTPUT

      - name: "Artifact upload: tarball"
        uses: actions/upload-artifact@master
        with:
          name: ${{ steps.package.outputs.PKG_NAME }}
          path: ${{ steps.package.outputs.PKG_PATH }}

      - name: "Artifact upload: Windows installer"
        uses: actions/upload-artifact@master
        if: steps.msi-installer.outputs.PKG_NAME
        with:
          name: ${{ steps.msi-installer.outputs.PKG_NAME }}
          path: ${{ steps.msi-installer.outputs.PKG_PATH }}

      - name: "Artifact upload: Debian package"
        uses: actions/upload-artifact@master
        if: steps.debian-package.outputs.DPKG_NAME
        with:
          name: ${{ steps.debian-package.outputs.DPKG_NAME }}
          path: ${{ steps.debian-package.outputs.DPKG_PATH }}

      - name: Check for release
        id: is-release
        shell: bash
        run: |
          unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/v[0-9].* ]]; then IS_RELEASE='true' ; fi
          echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT

      - name: Publish archives and packages
        uses: softprops/action-gh-release@v1
        if: steps.is-release.outputs.IS_RELEASE
        with:
          files: |
            ${{ steps.package.outputs.PKG_PATH }}
            ${{ steps.msi-installer.outputs.PKG_PATH }}
            ${{ steps.debian-package.outputs.DPKG_PATH }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}