rustscript-embedded 0.3.3

Embedded RustScript VMBC runtime with a no_std host-function ABI
name: Publish crates

on:
  push:
    tags:
      - 'v*'
      - '[0-9]*'
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to publish. Defaults to the pushed tag name without leading v.'
        required: false
        type: string
      pd_vm_version:
        description: 'pd-vm dependency version override'
        required: false
        type: string
      pd_vm_nostd_version:
        description: 'pd-vm-nostd dependency version override'
        required: false
        type: string

concurrency:
  group: publish-crates-${{ github.ref }}
  cancel-in-progress: false

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    env:
      PACKAGE_ORDER: rustscript-embedded
      PD_VM_VERSION: ${{ inputs.pd_vm_version || '0.22.6' }}
      PD_VM_NOSTD_VERSION: ${{ inputs.pd_vm_nostd_version || inputs.pd_vm_version || '0.22.6' }}
      PD_HOST_FUNCTION_VERSION: ${{ inputs.pd_vm_version || '0.22.6' }}
      PD_EDGE_ABI_VERSION: 0.1.1
      WORKSPACE_VERSION_OVERRIDES: micro-rustscript=${{ inputs.version }} rustscript=${{ inputs.pd_vm_version || '0.22.6' }} pd-edge=0.1.1
    steps:
      - name: Checkout micro-rustscript
        uses: actions/checkout@v4
        with:
          path: micro-rustscript
      - name: Checkout rustscript dependency
        uses: actions/checkout@v4
        with:
          repository: rustscript-lang/rustscript
          path: rustscript
      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable
      - name: Publish crates
        working-directory: micro-rustscript
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
          INPUT_VERSION: ${{ inputs.version }}
        run: |
          set -euo pipefail
          if [[ -z "${CARGO_REGISTRY_TOKEN:-}" ]]; then
            echo "CARGO_REGISTRY_TOKEN is required" >&2
            exit 1
          fi
          version="${INPUT_VERSION:-}"
          if [[ -z "$version" ]]; then
            version="${GITHUB_REF_NAME#v}"
          fi
          if [[ -z "$version" ]]; then
            echo "version is required" >&2
            exit 1
          fi
          export PUBLISH_VERSION="$version"
          python3 - <<'PY'
          import os, re
          from pathlib import Path
          version = os.environ['PUBLISH_VERSION']
          dep_versions = {
              'rustscript-embedded': version,
              'pd-vm': os.environ.get('PD_VM_VERSION') or version,
              'pd-vm-nostd': os.environ.get('PD_VM_NOSTD_VERSION') or version,
              'pd-host-function': os.environ.get('PD_HOST_FUNCTION_VERSION') or version,
              'pd-edge-abi': os.environ.get('PD_EDGE_ABI_VERSION') or version,
          }
          workspace_versions = {}
          for item in os.environ.get('WORKSPACE_VERSION_OVERRIDES', '').split():
              if '=' in item:
                  name, value = item.split('=', 1)
                  if value:
                      workspace_versions[name] = value
          dep_re = re.compile(r'^(\s*([A-Za-z0-9_-]+)\s*=\s*\{)(.*)(\}\s*)$')
          for path in Path('..').rglob('Cargo.toml'):
              if '.git' in path.parts or 'target' in path.parts:
                  continue
              manifest_version = workspace_versions.get(path.parent.name, version)
              lines = path.read_text().splitlines()
              out = []
              section = ''
              for line in lines:
                  stripped = line.strip()
                  if stripped.startswith('[') and stripped.endswith(']'):
                      section = stripped
                  if section in ('[workspace.package]', '[package]') and stripped.startswith('version = '):
                      line = re.sub(r'version\s*=\s*"[^"]*"', f'version = "{manifest_version}"', line)
                  match = dep_re.match(line)
                  if match and ('path' in match.group(3) or 'git' in match.group(3)):
                      prefix, key, body, suffix = match.groups()
                      package_match = re.search(r'package\s*=\s*"([^"]+)"', body)
                      dep_name = package_match.group(1) if package_match else key
                      dep_version = dep_versions.get(dep_name)
                      if dep_version:
                          if re.search(r'version\s*=\s*"[^"]*"', body):
                              body = re.sub(r'version\s*=\s*"[^"]*"', f'version = "{dep_version}"', body)
                          else:
                              body = body.rstrip()
                              if body and not body.endswith(','):
                                  body += ','
                              body += f' version = "{dep_version}"'
                          # Remove git source pins so published packages use crates.io releases.
                          body = re.sub(r'\s*,?\s*git\s*=\s*"[^"]*"', '', body)
                          body = re.sub(r'\s*,?\s*rev\s*=\s*"[^"]*"', '', body)
                          body = re.sub(r',\s*,', ',', body)
                          line = prefix + body + suffix
                  out.append(line)
              path.write_text('\n'.join(out) + '\n')
          PY
          python3 - <<'PY'
          import urllib.error, urllib.request, os, subprocess, time, sys
          version = os.environ['PUBLISH_VERSION']
          packages = os.environ['PACKAGE_ORDER'].split()
          token = os.environ['CARGO_REGISTRY_TOKEN']
          def exists(crate, version):
              try:
                  urllib.request.urlopen(f'https://crates.io/api/v1/crates/{crate}/{version}', timeout=20).read()
                  return True
              except urllib.error.HTTPError as exc:
                  if exc.code == 404:
                      return False
                  raise
          for package in packages:
              if exists(package, version):
                  print(f'{package} {version} already published; skipping')
                  continue
              for attempt in range(1, 19):
                  print(f'publishing {package} {version}, attempt {attempt}')
                  proc = subprocess.run(['cargo', 'publish', '-p', package, '--no-verify', '--allow-dirty', '--token', token], text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
                  print(proc.stdout)
                  if proc.returncode == 0 or exists(package, version):
                      break
                  if attempt == 18:
                      sys.exit(proc.returncode)
                  time.sleep(20)
          PY