Madinah |

Introduce farm plugins ci

Date: Oct 19, 2024 Sat
|
Estimated Reading Time: 2 min
|
Author: Madinah

Recently, I have been working on a project that requires me to support both JavaScript and Rust plugins. I have been using GitHub Actions to deploy the plugins, and I wanted to share how I managed to support both JavaScript and Rust plugins in the same repository.

Rust Plugins

Building Rust Plugins

Bacause of rust plugin need support multi-platform, so we should build in multi-platform before deploy to npm registry.

 name: Building Rust Binding And Upload Artifacts
on: workflow_call

jobs:
  build:
    name: Build and Upload Artifacts - ${{ matrix.settings.abi }}
    runs-on: ${{ matrix.settings.os }}
    strategy:
      fail-fast: false
      matrix:
        settings:
          - os: ubuntu-latest
            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
            abi: linux-x64-gnu
            build: >-
              git config --global --add safe.directory /build &&
              set -e &&
              unset CC_x86_64_unknown_linux_gnu &&
              unset CC &&
              pnpm --filter "{rust-plugins}[HEAD~1]" --sequential build --target x86_64-unknown-linux-gnu --abi linux-x64-gnu
          - os: ubuntu-latest
            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
            abi: linux-x64-musl
            build: >-
              git config --global --add safe.directory /build &&
              set -e &&
              unset CC_x86_64_unknown_linux_musl &&
              unset CC &&
              pnpm  --filter "{rust-plugins}[HEAD~1]" --sequential build --target x86_64-unknown-linux-musl --abi linux-x64-musl
          - os: windows-latest
            abi: win32-x64-msvc
          - os: macos-latest
            abi: darwin-arm64
          - os: macos-13
            abi: darwin-x64
          # cross compile
          # windows. Note swc plugins is not supported on ia32 and arm64
          - os: windows-latest
            abi: win32-ia32-msvc
            target: i686-pc-windows-msvc
            build: |
              export CARGO_PROFILE_RELEASE_LTO=false
              cargo install cargo-xwin --locked
              pnpm --filter "{rust-plugins}[HEAD~1]" --sequential build --target i686-pc-windows-msvc --abi win32-ia32-msvc --cargo-flags="--no-default-features"
          - os: windows-latest
            abi: win32-arm64-msvc
            target: aarch64-pc-windows-msvc
            build: |
              export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=256
              export CARGO_PROFILE_RELEASE_LTO=false
              cargo install cargo-xwin --locked
              pnpm --filter "{rust-plugins}[HEAD~1]" --sequential build --target aarch64-pc-windows-msvc --abi win32-arm64-msvc --cargo-flags="--no-default-features"

          # linux
          - os: ubuntu-latest
            abi: linux-arm64-musl
            target: aarch64-unknown-linux-musl
            zig: true
          - os: ubuntu-latest
            abi: linux-arm64-gnu
            target: aarch64-unknown-linux-gnu
            zig: true
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 2
      # - run: |
      # git fetch --no-tags --prune --depth=1 origin +refs/heads/main:refs/remotes/HEAD~1

      - name: Cache rust artifacts
        uses: Swatinem/rust-cache@v2
        with:
          shared-key: rust-build-${{ matrix.settings.abi }}

      - uses: actions/setup-node@v3
        with:
          node-version: 18
      - name: Install Dependencies
        run: npm config set registry https://registry.npmmirror.com && npm install -g [email protected] && pnpm i --frozen-lockfile
      - run: rustup target add ${{ matrix.settings.target }}
        if: ${{ matrix.settings.target }}
      # Use the v1 of this action
      - uses: mbround18/setup-osxcross@v1
        if: ${{ matrix.settings.osxcross }}
        # This builds executables & sets env variables for rust to consume.
        with:
          osx-version: '12.3'
      - uses: goto-bus-stop/setup-zig@v2
        if: ${{ matrix.settings.zig }}
      - name: Build in docker
        uses: addnab/docker-run-action@v3
        if: ${{ matrix.settings.docker }}
        with:
          image: ${{ matrix.settings.docker }}
          options: -v ${{ env.HOME }}/.cargo/git:/root/.cargo/git -v ${{ env.HOME }}/.cargo/registry:/root/.cargo/registry -v ${{ github.workspace }}:/build -w /build
          run: ${{ matrix.settings.build }}
      - name: Default Build
        if: ${{ !matrix.settings.docker && !matrix.settings.build }}
        run: |
          pnpm --filter "{rust-plugins}[HEAD~1]" --sequential build --abi ${{ matrix.settings.abi }} ${{ matrix.settings.target && format('--target {0}', matrix.settings.target) || '' }} ${{ matrix.settings.zig && '--zig' || '' }}
        shell: bash
      - name: Build
        if: ${{ !matrix.settings.docker && matrix.settings.build }}
        run: ${{ matrix.settings.build }}
        shell: bash
      - name: Upload Plugin dsv
        uses: actions/upload-artifact@v3
        with:
          name: ${{ github.sha }}-${{ matrix.settings.abi }}-dsv
          path: ./rust-plugins/dsv/npm/${{ matrix.settings.abi }}/index.farm
          if-no-files-found: ignore
      # other packages upload 

In the above ci config, first we build defferent platform rust plugins. But in then build step, we use pnpm --filter "{rust-plugins}[HEAD~1]" to build only changed rust plugins. This is very important, because we don’t want to build all rust plugins every time. Then filter only build changed rust plugins under rust-plugins directory.

Deploying Rust Plugins

 name: Publish packages and crates
on:
  push:
    branches:
      - main

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  call-rust-build:
    if: contains(github.event.head_commit.message, 'rust-plugins') || contains(github.event.head_commit.message, 'all')
    uses: ./.github/workflows/build.yaml

  release:
    name: Release
    if: contains(github.event.head_commit.message, 'rust-plugins') || contains(github.event.head_commit.message, 'all')
    needs: [call-rust-build]
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v3
        with:
          fetch-depth: 2
      - run: |
          git fetch --no-tags --prune --depth=1 origin +refs/heads/main:refs/remotes/HEAD~1

      - name: Setup Node.js 18.x
        uses: actions/setup-node@v3
        with:
          node-version: 18.x

      # batch download artifacts
      - uses: actions/download-artifact@v3
        with:
          path: /tmp/artifacts
      - name: Move Artifacts
        run: |
          for abi in linux-x64-gnu linux-x64-musl darwin-x64 win32-x64-msvc linux-arm64-musl linux-arm64-gnu darwin-arm64 win32-ia32-msvc win32-arm64-msvc
          do
             for package in dsv react-components virtual yaml strip image url icons auto-import mdx
              do
                folder_path="/tmp/artifacts/${{github.sha}}-${abi}-${package}"
                if [ -d "${folder_path}" ] && [ -n "$(ls -A $folder_path)" ]; then
                  mv /tmp/artifacts/${{ github.sha }}-${abi}-${package}/* ./packages/${package}/npm/${abi}
                  ls -R $folder_path
                  ls -R ./packages/${package}/npm/${abi}
                  test -f ./packages/${package}/npm/${abi}/index.farm
                else
                  echo "${folder_path} is empty"
                fi
              done
          done

      - name: Install Dependencies
        run: npm install -g [email protected] && pnpm i --frozen-lockfile

      - name: Publish to npm
        run: |
          npm set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} && npm config set access public && pnpm --filter "{rust-plugins}[HEAD~1]" publish --no-git-checks 

In the above ci config, we use contains to determine whether to run the ci. If the commit message contains rust-plugins or all , then we run the ci. In the release job, we first download the artifacts that we built in the previous ci. Then we move the artifacts to the corresponding directory. Finally, we publish the rust plugins to the npm registry.

JavaScript Plugins

Building JavaScript Plugins

 name: PR build plugins
on: workflow_call

jobs:
  build:
    runs-on: ubuntu-latest
    name: release
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 2
      # - run: |
      # git fetch --no-tags --prune --depth=1 origin +refs/heads/main:refs/remotes/HEAD~1
      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: 20
          registry-url: https://registry.npmjs.org/

      - name: Enable Corepack
        id: pnpm-setup
        run: |
          corepack enable

      - name: Initliaze .npmrc
        run: >
          echo -e "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}\n$(cat .npmrc)" > .npmrc
          && cat -n .npmrc

      - name: pnpm install
        run: pnpm install --frozen-lockfile

      - name: Build Packages
        run: |
          pnpm --filter "{js-plugins}[HEAD~1]" build 

the config is as same as rust plugins, but we use pnpm --filter "{js-plugins}[HEAD~1]" build to build only changed js plugins.

Deploying JavaScript Plugins

 name: Release Packages

on:
  push:
    branches:
      - main

jobs:
  release:
    runs-on: ubuntu-latest
    if: contains(github.event.head_commit.message, 'js-plugins') || contains(github.event.head_commit.message, 'all')
    name: release
    steps:
      - name: Checkout repo
        uses: actions/checkout@v3
        with:
          fetch-depth: 2

      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: 20
          registry-url: https://registry.npmjs.org/

      - name: Enable Corepack
        id: pnpm-setup
        run: |
          corepack enable

      - name: Initliaze .npmrc
        run: >
          echo -e "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}\n$(cat .npmrc)" > .npmrc
          && cat -n .npmrc

      - name: pnpm install
        run: pnpm install --frozen-lockfile

      - name: Build Packages
        run: |
          pnpm --filter "{js-plugins}[HEAD~1]" build

      - name: Release and Publish Packages
        run: |
          npm set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} && npm config set access public && pnpm --filter "{js-plugins}[HEAD~1]" publish --no-git-checks 

the relase config is as same as rust plugins too.

Summary

  • use pnpm --filter "{xx}[HEAD~1]" to build only changed to reduce build time.
  • use contains to determine whether to run the ci.

This work is licensed under CC BY-NC-SA 4.0