copp 0.2.2

Convex-objective path parameterization for robotic trajectory planning.
Documentation
name: MATLAB Bindings

on:
  push:
    branches:
      - "**"
    tags:
      - "v*"
    # Run on every push to any branch (no path filtering)
  pull_request:
    # Run on every pull request (no path filtering)
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: matlab-${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  CARGO_TERM_COLOR: always
  COPP_MATLAB_RELEASE: R2024b
  RUSTFLAGS: "-C target-cpu=generic"

jobs:
  package:
    name: ${{ matrix.platform }} package
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-24.04
            platform: linux-x86_64
            supported_win64: false
            supported_mac: false
            supported_glnxa64: true
          - os: windows-2022
            platform: windows-x86_64
            supported_win64: true
            supported_mac: false
            supported_glnxa64: false
          - os: macos-15
            platform: macos-aarch64
            supported_win64: false
            supported_mac: true
            supported_glnxa64: false
          - os: macos-15-intel
            platform: macos-x86_64
            supported_win64: false
            supported_mac: true
            supported_glnxa64: false
    env:
      COPP_MATLAB_PLATFORM: ${{ matrix.platform }}
      COPP_MATLAB_SUPPORT_WIN64: ${{ matrix.supported_win64 }}
      COPP_MATLAB_SUPPORT_MAC: ${{ matrix.supported_mac }}
      COPP_MATLAB_SUPPORT_GLNXA64: ${{ matrix.supported_glnxa64 }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Prepare package metadata
        shell: pwsh
        run: |
          $refName = $env:GITHUB_REF_NAME
          if ($env:GITHUB_EVENT_NAME -eq "pull_request") {
            $refName = "pr-${{ github.event.pull_request.number }}"
          }

          $safeRefName = $refName -replace '[^A-Za-z0-9._-]', '-'
          $toolboxVersion = "0.0.1"

          if ($env:GITHUB_REF_TYPE -eq "tag") {
            $tagVersion = $env:GITHUB_REF_NAME -replace '^v', ''
            if ($tagVersion -match '^[0-9]+(\.[0-9]+){1,3}([+-].*)?$') {
              $toolboxVersion = ($tagVersion -split '[+-]')[0]
            }
          }

          "COPP_MATLAB_PACKAGE_NAME=copp-matlab-$safeRefName-${{ matrix.platform }}" >> $env:GITHUB_ENV
          "COPP_MATLAB_TOOLBOX_VERSION=$toolboxVersion" >> $env:GITHUB_ENV

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Cache Cargo
        uses: Swatinem/rust-cache@v2
        with:
          key: matlab-${{ matrix.platform }}

      - name: Install Linux system dependencies
        if: runner.os == 'Linux'
        run: |
          sudo apt-get update
          sudo apt-get install -y build-essential pkg-config libfontconfig1-dev

      - name: Build Rust MATLAB native library
        run: cargo build --release --lib --features matlab

      - name: Set up MATLAB
        uses: matlab-actions/setup-matlab@v3
        with:
          release: ${{ env.COPP_MATLAB_RELEASE }}

      - name: Build, package, and smoke test MATLAB toolbox
        uses: matlab-actions/run-command@v3
        env:
          LD_PRELOAD: /usr/lib/x86_64-linux-gnu/libstdc++.so.6
          LD_LIBRARY_PATH: /usr/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH}
        with:
          command: |
            repo = string(pwd);
            matlabRoot = fullfile(repo, "bindings", "matlab");

            cd(matlabRoot);
            build(Linkage="static", Configuration="release");

            packageDir = "";
            for candidate = ["+copp", "+copp"]
                if isfolder(fullfile(matlabRoot, candidate))
                    packageDir = candidate;
                    break
                end
            end
            assert(strlength(packageDir) > 0, "COPP MATLAB package folder was not found.");

            addpath(matlabRoot);
            if packageDir == "+copp"
                versionText = copp.version();
                toolboxName = "COPP for MATLAB";
            else
                versionText = copp.version();
                toolboxName = "COPP for MATLAB";
            end
            disp("Built COPP MATLAB package version: " + string(versionText));

            artifactRoot = fullfile(repo, "release-artifacts");
            stageRoot = fullfile(repo, "dist", getenv("COPP_MATLAB_PACKAGE_NAME"));
            if isfolder(artifactRoot)
                rmdir(artifactRoot, "s");
            end
            if isfolder(stageRoot)
                rmdir(stageRoot, "s");
            end
            mkdir(artifactRoot);
            mkdir(stageRoot);

            copyfile(fullfile(matlabRoot, packageDir), fullfile(stageRoot, packageDir));
            copyfile(fullfile(matlabRoot, "examples"), fullfile(stageRoot, "examples"));
            copyfile(fullfile(matlabRoot, "README.md"), fullfile(stageRoot, "MATLAB-README.md"));
            for fileName = ["README.md", "CHANGELOG.md", "LICENSE"]
                sourceFile = fullfile(repo, fileName);
                if isfile(sourceFile)
                    copyfile(sourceFile, fullfile(stageRoot, fileName));
                end
            end

            toolboxId = "0d55637d-8f40-49b9-9d1a-7cad2cf11bd1";
            toolboxVersion = string(getenv("COPP_MATLAB_TOOLBOX_VERSION"));
            outputFile = fullfile(artifactRoot, string(getenv("COPP_MATLAB_PACKAGE_NAME")) + ".mltbx");

            previousDir = pwd;
            cd(stageRoot);
            opts = matlab.addons.toolbox.ToolboxOptions(stageRoot, toolboxId);
            opts.ToolboxName = toolboxName;
            opts.ToolboxVersion = toolboxVersion;
            opts.Summary = "MATLAB bindings for COPP.";
            opts.Description = "MATLAB package wrapping the COPP native solver core through a MEX gateway.";
            opts.AuthorName = "TOPP-THU";
            opts.AuthorEmail = "hello@copp.pro";
            opts.ToolboxMatlabPath = {"."};
            opts.OutputFile = outputFile;
            opts.MinimumMatlabRelease = string(getenv("COPP_MATLAB_RELEASE"));
            opts.MaximumMatlabRelease = "";
            opts.SupportedPlatforms.Win64 = strcmpi(getenv("COPP_MATLAB_SUPPORT_WIN64"), "true");
            opts.SupportedPlatforms.Mac = strcmpi(getenv("COPP_MATLAB_SUPPORT_MAC"), "true");
            opts.SupportedPlatforms.Glnxa64 = strcmpi(getenv("COPP_MATLAB_SUPPORT_GLNXA64"), "true");
            opts.SupportedPlatforms.MatlabOnline = false;

            matlab.addons.toolbox.packageToolbox(opts);
            cd(previousDir);

            installerRoot = string(tempname);
            mkdir(installerRoot);
            copyfile(fullfile(matlabRoot, "install_copp.m"), installerRoot);

            rmpath(matlabRoot);
            rehash toolboxcache;
            addpath(installerRoot);

            packageName = extractAfter(packageDir, "+");
            installed = install_copp( ...
                AssetRoot=artifactRoot, ...
                Version="latest", ...
                Package=packageName, ...
                AgreeToLicense=true);
            disp(installed);

      - name: Generate SHA256
        shell: pwsh
        run: |
          $toolbox = Get-ChildItem -Path release-artifacts -Filter "*.mltbx" | Select-Object -First 1
          if (-not $toolbox) {
            throw "No MATLAB toolbox was generated."
          }

          $hash = Get-FileHash $toolbox.FullName -Algorithm SHA256
          "$($hash.Hash.ToLower())  $($toolbox.Name)" |
            Set-Content -Path "$($toolbox.FullName).sha256" -NoNewline

      - name: Upload MATLAB toolbox artifact
        uses: actions/upload-artifact@v4
        with:
          name: copp-matlab-${{ matrix.platform }}
          path: |
            release-artifacts/*.mltbx
            release-artifacts/*.mltbx.sha256
          if-no-files-found: error

  publish-draft:
    name: Create Draft GitHub Release
    needs: package
    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - name: Download MATLAB package artifacts
        uses: actions/download-artifact@v4
        with:
          path: release-assets

      - name: Create or update draft GitHub Release
        shell: pwsh
        env:
          GH_TOKEN: ${{ github.token }}
          GH_REPO: ${{ github.repository }}
          RELEASE_TAG: ${{ github.ref_name }}
        run: |
          $assetPaths = @(
            Get-ChildItem release-assets -Recurse -File |
              Where-Object { $_.Name -like "*.mltbx" -or $_.Name -like "*.mltbx.sha256" } |
              Sort-Object Name |
              ForEach-Object { $_.FullName }
          )
          if ($assetPaths.Count -eq 0) {
            throw "No release assets were downloaded."
          }

          $notesFile = "release-notes.md"
          @'
          Prebuilt MATLAB toolbox packages are attached below.

          See CHANGELOG.md in this tag for API changes and breaking changes.
          '@ | Set-Content -Path $notesFile -NoNewline

          gh release view $env:RELEASE_TAG *> $null
          if ($LASTEXITCODE -eq 0) {
            & gh release upload $env:RELEASE_TAG $assetPaths --clobber
            & gh release edit $env:RELEASE_TAG `
              --draft `
              --title $env:RELEASE_TAG `
              --notes-file $notesFile
          } else {
            & gh release create $env:RELEASE_TAG $assetPaths `
              --draft `
              --verify-tag `
              --title $env:RELEASE_TAG `
              --notes-file $notesFile
          }