diff --git a/.forgejo/workflows/publish.yml b/.forgejo/workflows/publish.yml index 6d270be15..3a296ff69 100644 --- a/.forgejo/workflows/publish.yml +++ b/.forgejo/workflows/publish.yml @@ -18,37 +18,46 @@ jobs: - name: Checkout uses: actions/checkout@v5 with: - fetch-depth: 0 + fetch-depth: 2 filter: blob:none sparse-checkout: | modpacks datapacks - - name: Install jq - run: | - if ! command -v jq &> /dev/null; then - apt-get update && apt-get install -y jq - fi - - name: Find changed manifests id: find run: | + set -eu MANIFESTS=$(git diff-tree --no-commit-id --name-only -r HEAD \ | grep -E '^(modpacks|datapacks)/.*/manifest\.json$' || true) + if [ -z "$MANIFESTS" ]; then - echo "has_manifests=false" >> $GITHUB_OUTPUT - echo "manifests=[]" >> $GITHUB_OUTPUT + echo "has_manifests=false" >> "$GITHUB_OUTPUT" + echo "manifests=[]" >> "$GITHUB_OUTPUT" echo "no changed manifests." - else - JSON=$(echo "$MANIFESTS" | jq -R -s -c 'split("\n") | map(select(length > 0))') - echo "has_manifests=true" >> $GITHUB_OUTPUT - echo "manifests=$JSON" >> $GITHUB_OUTPUT - echo "manifests to publish: $JSON" + exit 0 fi + JSON="[" + first=1 + while IFS= read -r line; do + [ -z "$line" ] && continue + if [ "$first" -eq 1 ]; then + first=0 + else + JSON="${JSON}," + fi + JSON="${JSON}\"${line}\"" + done <<< "$MANIFESTS" + JSON="${JSON}]" + + echo "has_manifests=true" >> "$GITHUB_OUTPUT" + echo "manifests=${JSON}" >> "$GITHUB_OUTPUT" + echo "manifests to publish: ${JSON}" + publish: needs: detect - if: always() + if: needs.detect.outputs.has_manifests == 'true' runs-on: technocality strategy: fail-fast: false @@ -73,12 +82,7 @@ jobs: - name: Generate Changelog id: changelog - run: | - git config user.name "Forgejo Action" - git config user.email "actions@noreply.forgejo" - git add . - git commit -m "internal: prepare changelog" --allow-empty - npx tsx tools/changelog/generate-changelog.ts "${{ matrix.manifest }}" + run: npx tsx tools/changelog/generate-changelog.ts "${{ matrix.manifest }}" - name: Cache Publisher Binary id: cache-publisher @@ -87,16 +91,16 @@ jobs: path: ./publisher-bin key: publisher-v1-${{ runner.os }}-${{ hashFiles('src/actions/publish/**/*.rs', 'src/actions/publish/Cargo.toml', 'src/actions/publish/Cargo.lock') }} + - name: Install Rust + if: steps.cache-publisher.outputs.cache-hit != 'true' + uses: https://github.com/dtolnay/rust-toolchain@stable + - name: Rust Cache if: steps.cache-publisher.outputs.cache-hit != 'true' uses: https://github.com/Swatinem/rust-cache@v2 with: workspaces: "src/actions/publish -> target" - - name: Install Rust - if: steps.cache-publisher.outputs.cache-hit != 'true' - uses: https://github.com/dtolnay/rust-toolchain@stable - - name: Build Publisher if: steps.cache-publisher.outputs.cache-hit != 'true' run: | @@ -113,7 +117,7 @@ jobs: - name: Setup Go if: steps.cache-go.outputs.cache-hit != 'true' - uses: actions/setup-go@v5 + uses: https://github.com/actions/setup-go@v5 with: go-version: 'stable' cache: true @@ -125,6 +129,14 @@ jobs: - name: Add Go bin to PATH run: echo "$HOME/go/bin" >> $GITHUB_PATH + - name: Cache Packwiz Downloads + uses: actions/cache@v4 + with: + path: ~/.cache/packwiz + key: packwiz-cache-${{ runner.os }}-${{ github.run_id }} + restore-keys: | + packwiz-cache-${{ runner.os }}- + - name: Run Publisher id: meta run: | @@ -145,4 +157,4 @@ jobs: version: "${{ steps.meta.outputs.ver }}" changelog: "${{ steps.changelog.outputs.notes }}" loaders: ${{ steps.meta.outputs.type == 'modpack' && steps.meta.outputs.loader || 'minecraft' }} - game-versions: "${{ steps.meta.outputs.mc }}" + game-versions: "${{ steps.meta.outputs.mc }}" \ No newline at end of file diff --git a/src/actions/publish/publish.rs b/src/actions/publish/publish.rs index 76bff08c5..00ed7bfeb 100644 --- a/src/actions/publish/publish.rs +++ b/src/actions/publish/publish.rs @@ -4,8 +4,9 @@ use std::{ env, fs::{self, OpenOptions}, io::Write, - path::Path, + path::{Path, PathBuf}, process::Command, + thread, }; #[derive(Clone, Copy)] @@ -111,52 +112,71 @@ fn build_modpack( loader: &str, ) -> Result<()> { let filename_base = format!("{p_name}-{mc_ver}-{loader}-{p_ver}"); - let mut built = 0; + let mut jobs: Vec<(Platform, PathBuf)> = Vec::new(); for platform in Platform::ALL { let target_folder = format!("{mc_ver}-{}", platform.short()); let target_path = p_dir.join(&target_folder); - if !target_path.exists() { + if target_path.exists() { + jobs.push((platform, target_path)); + } else { println!( "skipping {}: folder {} not found", platform.short(), target_path.display() ); - continue; } - - let refresh = Command::new("packwiz") - .args(["refresh", "-y"]) - .current_dir(&target_path) - .status() - .context("failed to invoke packwiz refresh")?; - if !refresh.success() { - bail!("packwiz refresh failed in {}", target_path.display()); - } - - let out_file = artifacts_dir.join(format!( - "{filename_base}-{}.{}", - platform.short(), - platform.ext() - )); - let out_str = out_file - .to_str() - .ok_or_else(|| anyhow!("non-UTF8 output path"))?; - - let export = Command::new("packwiz") - .args([platform.cli(), "export", "--output", out_str]) - .current_dir(&target_path) - .status() - .context("failed to invoke packwiz export")?; - if !export.success() { - bail!("packwiz export failed for {target_folder}"); - } - built += 1; } - if built == 0 { + if jobs.is_empty() { bail!("no platform folders (mc_ver-mr / mc_ver-cf) found"); } + + let mut handles = Vec::new(); + for (platform, target_path) in jobs { + let filename_base = filename_base.clone(); + let artifacts_dir = artifacts_dir.to_path_buf(); + + handles.push(thread::spawn(move || -> Result<()> { + let out_file = artifacts_dir.join(format!( + "{filename_base}-{}.{}", + platform.short(), + platform.ext() + )); + let out_str = out_file + .to_str() + .ok_or_else(|| anyhow!("non-UTF8 output path"))?; + + let export = Command::new("packwiz") + .args([platform.cli(), "export", "--output", out_str]) + .current_dir(&target_path) + .status() + .context("failed to invoke packwiz export")?; + if !export.success() { + bail!("packwiz export failed for {}", target_path.display()); + } + + println!("exported {}", out_file.display()); + Ok(()) + })); + } + + let mut errors = Vec::new(); + for h in handles { + match h.join() { + Ok(Ok(())) => {} + Ok(Err(e)) => errors.push(e), + Err(_) => errors.push(anyhow!("export thread panicked")), + } + } + + if !errors.is_empty() { + for e in &errors { + eprintln!("error: {e:#}"); + } + bail!("{} export(s) failed", errors.len()); + } + Ok(()) }