diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 087df05..9fe19bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,20 @@ name: ci -# macos-latest is the macOS 26 image. It does not install Xcode 16.4, and the -# simulator-discovery script previously used here failed on that image -# (https://github.com/fulldecent/FDWaveformView/actions/runs/25414955912). -# This job pins the macOS 15 image, whose default Xcode is 16.4 and which -# lists an iPhone 16 simulator on iOS 18.5: -# https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md +# GitHub-hosted Xcode 27. macos-26 stops at Xcode 26, which cannot load +# swift-tools-version 6.4. +# https://github.blog/changelog/2026-07-16-xcode-27-runner-image-now-in-public-preview/ +# https://github.com/actions/runner-images/blob/main/images/macos/xcode-27-arm64-Readme.md +# +# swift6-module-template runs `xcrun swift test` on this runner. This package +# imports UIKit and its platforms are iOS and visionOS, so that command builds +# for macOS and cannot compile. The destination below is the iPhone 17 +# simulator on iOS 27.0, which the Xcode 27.0 image lists. +# +# release.yml calls this workflow. It reads $.jobs.build-test. on: + workflow_call: push: - branches: - - main pull_request: concurrency: @@ -18,28 +22,18 @@ concurrency: cancel-in-progress: true jobs: - test: - name: Build and Test - runs-on: macos-15 - + build-test: + name: Build and test + runs-on: xcode-27 steps: - - name: Check out repository - uses: actions/checkout@v4 - - - name: Select Xcode - run: sudo xcode-select -s /Applications/Xcode_16.4.app + - name: Check out this commit + uses: actions/checkout@v7 - name: Show build environment run: | xcodebuild -version xcrun swift --version - - name: Build - run: | - xcodebuild build \ - -scheme FDWaveformView \ - -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.5' - - name: Check formatting run: | swift format format --in-place --recursive Sources/ Tests/ @@ -49,4 +43,4 @@ jobs: run: | xcodebuild test \ -scheme FDWaveformView \ - -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.5' + -destination 'platform=iOS Simulator,name=iPhone 17,OS=27.0' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2c908b4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,122 @@ +# Release +# +# Open or update the release pull request, test the package, and publish the +# GitHub Release. +# +# Adapted from swift6-module-template release 16.5.0: +# https://github.com/fulldecent/swift6-module-template/blob/v16.5.0/.github/workflows/release.yml +# That workflow attests a Linux static library from `swift build -c release`. +# This package imports UIKit, so there is no Linux library to publish. Swift +# Package Manager installs from the git tag. The published release is the tag +# and the generated notes. +# +# Tags stay `5.1.1`, without a `v` prefix, because that is the shape of the +# existing tags. release-please-config.json sets include-v-in-tag to false. +# +# One-time setup +# 1. GitHub repo > Settings > General > Releases > Enable release immutability. +# 2. GitHub repo > Settings > Actions > General > Workflow permissions: Read and write permissions. +# 3. Under Workflow permissions, check "Allow GitHub Actions to create and approve pull requests". + +name: Release + +on: + push: + branches: ["main"] + workflow_dispatch: + inputs: + tag: + description: Tag to publish, for example 5.1.2. Leave empty to open or update the release pull request only. + required: false + type: string + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + propose: + name: Open or update the release pull request + if: github.event_name == 'push' || inputs.tag == '' + runs-on: ubuntu-latest + outputs: + pr: ${{ steps.merged.outputs.pr }} + tag_name: ${{ steps.merged.outputs.tag_name }} + steps: + - name: Open the release pull request + uses: googleapis/release-please-action@v5 + with: + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + skip-github-release: true + + - name: Find a merged release pull request + id: merged + if: github.event_name == 'push' + env: + GH_TOKEN: ${{ github.token }} + run: | + found=$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/pulls" --jq ' + .[] | select(.merged_at != null and any(.labels[]; .name == "autorelease: pending")) + | "\(.number) \(.title | capture("release (?[0-9]+[.][0-9]+[.][0-9]+)").v)"' | head -n 1) + if [ -n "$found" ]; then + read -r pr tag <<< "$found" + echo "pr=$pr" >> "$GITHUB_OUTPUT" + echo "tag_name=$tag" >> "$GITHUB_OUTPUT" + fi + + build-test: + name: Build and test + needs: propose + if: | + always() && + (needs.propose.result == 'success' || needs.propose.result == 'skipped') && + (needs.propose.outputs.tag_name != '' || + (github.event_name == 'workflow_dispatch' && inputs.tag != '')) + uses: ./.github/workflows/ci.yml + + publish: + name: Publish release + needs: [propose, build-test] + if: | + always() && + needs.build-test.result == 'success' && + (needs.propose.outputs.tag_name != '' || + (github.event_name == 'workflow_dispatch' && inputs.tag != '')) + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Resolve the tag + id: tag + env: + TAG: ${{ inputs.tag || needs.propose.outputs.tag_name }} + run: | + case "$TAG" in + [0-9]*.[0-9]*.[0-9]*) ;; + *) echo "tag must look like 5.1.2" >&2; exit 1 ;; + esac + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + + - name: Publish + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ steps.tag.outputs.tag }} + run: | + gh release create "$TAG" \ + --repo "$GITHUB_REPOSITORY" \ + --target "$GITHUB_SHA" \ + --title "$TAG" \ + --generate-notes \ + --latest + + - name: Mark the release pull request tagged + if: needs.propose.outputs.pr != '' + env: + GH_TOKEN: ${{ github.token }} + PR: ${{ needs.propose.outputs.pr }} + run: | + gh api -X DELETE "repos/$GITHUB_REPOSITORY/issues/$PR/labels/autorelease:%20pending" + gh api "repos/$GITHUB_REPOSITORY/issues/$PR/labels" -f "labels[]=autorelease: tagged" diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..6c87755 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "5.1.1" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index ee33b52..1823fff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,10 @@ All notable changes to this project will be documented in this file. ### Changed -- Pin GitHub Actions to the macOS 15 image, Xcode 16.4, and an iPhone 16 simulator on iOS 18.5. The workflow on `macos-latest` failed before the package compiled. +- Require Swift tools 6.4 and enable Approachable Concurrency. Xcode 26 cannot load that tools version. +- Test on the GitHub-hosted Xcode 27 runner, using an iPhone 17 simulator on iOS 27.0. +- Hand releases to Release Please. Commit messages use `fix:`, `feat:` or `BREAKING CHANGE:`. Tags stay unprefixed, matching `5.1.1`. - Inline `.gitignore` from the current Swift and Xcode ignore files, plus the macOS and secret rules from project-template. -- Describe the release steps in terms of `CHANGELOG.md` and a GitHub tag. --- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2af3f6d..4d24e4d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,27 +1,16 @@ # Contributing -All contributors are welcome. Please use issues and pull requests to contribute to the project. And update [CHANGELOG.md](CHANGELOG.md) when committing. +All contributors are welcome. Please use issues and pull requests to contribute to the project. Commit messages use `fix:`, `feat:` or `BREAKING CHANGE:`. Release Please writes [CHANGELOG.md](CHANGELOG.md) from those messages. ## Release process -1. Confirm the build is [passing in GitHub Actions](https://github.com/fulldecent/FDWaveformView/actions) -2. Push a release commit - 1. Create a new Main section at the top of [CHANGELOG.md](CHANGELOG.md) - 2. Rename the old Main section like: +Merging the release pull request tags the release and publishes the GitHub Release. The settings that pull request needs are in [Releases](README.md#releases). Confirm the build is [passing in GitHub Actions](https://github.com/fulldecent/FDWaveformView/actions) before merging it. - ```markdown - ## [5.1.1](https://github.com/fulldecent/FDWaveformView/releases/tag/5.1.1) - - Released on 2025-12-06. - ``` - -3. Create a GitHub release - 1. Tag the release (like `5.1.1`) - 2. Paste notes from [CHANGELOG.md](CHANGELOG.md) +Tags stay `5.1.1`, without a `v` prefix. That matches the tags already on this repository. [release-please-config.json](release-please-config.json) sets `include-v-in-tag` to false so Release Please finds tag `5.1.1`. ## Maintenance Do this every quarter or so. -1. Review the Xcode pin in [.github/workflows/ci.yml](.github/workflows/ci.yml). The job uses the `macos-15` runner and `/Applications/Xcode_16.4.app`, then tests on an iPhone 16 simulator running iOS 18.5. Those are the versions listed for that image in the [macOS 15 runner readme](https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md). `macos-latest` is the macOS 26 image and does not install Xcode 16.4, so a workflow that selects `macos-latest` and then looks up a simulator will fail before it compiles this package. -2. Review [actions/checkout](https://github.com/actions/checkout) and move the `uses:` pin forward when a new major version is safe. The workflow currently uses `actions/checkout@v4`. +1. Review the Swift tools version in [Package.swift](Package.swift) and the runner in [.github/workflows/ci.yml](.github/workflows/ci.yml). The job uses the GitHub-hosted `xcode-27` image because a Swift tools version of 6.4 does not load on Xcode 26. The test destination is an iPhone 17 simulator on iOS 27.0, which that image lists for Xcode 27.0. `xcrun swift test` builds this package for macOS, and the sources import UIKit, so the workflow uses `xcodebuild` instead. The image contents are in the [Xcode 27 runner readme](https://github.com/actions/runner-images/blob/main/images/macos/xcode-27-arm64-Readme.md). +2. Review external actions in [.github/workflows](.github/workflows). The workflow currently uses `actions/checkout@v7` and `googleapis/release-please-action@v5`. diff --git a/Package.swift b/Package.swift index d62d30c..959d04b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.10 +// swift-tools-version: 6.4 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -19,9 +19,17 @@ let package = Package( // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .target( - name: "FDWaveformView"), + name: "FDWaveformView", + swiftSettings: [ + .enableUpcomingFeature("ApproachableConcurrency") + ] + ), .testTarget( name: "FDWaveformViewTests", - dependencies: ["FDWaveformView"]), + dependencies: ["FDWaveformView"], + swiftSettings: [ + .enableUpcomingFeature("ApproachableConcurrency") + ] + ), ] ) diff --git a/README.md b/README.md index 6299de8..d54e61c 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ UIView.animate(withDuration: 0.3) { - Antialiased waveforms draw extra pixels to avoid jagged edges. - Autolayout-driven size changes trigger re-rendering to prevent pixelation. -- Supports iOS 15+, visionOS 1+, and Swift tools version 5.10, as declared in `Package.swift`. +- Supports iOS 15+, visionOS 1+, and Swift tools version 6.4, as declared in `Package.swift`. Xcode 26 cannot load that tools version. - Includes unit tests that run on GitHub Actions. ## Installation @@ -110,12 +110,14 @@ A couple other things are exposed that we do not consider public API: ## Testing -GitHub Actions runs the suite on the macOS 15 image with Xcode 16.4. That image provides an iPhone 16 simulator on iOS 18.5. The command is: +GitHub Actions runs the suite on the [Xcode 27 runner image](https://github.com/actions/runner-images/blob/main/images/macos/xcode-27-arm64-Readme.md). The default Xcode there is 27.0, and that copy provides an iPhone 17 simulator on iOS 27.0. The command is: ```sh -xcodebuild test -scheme FDWaveformView -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.5' +xcodebuild test -scheme FDWaveformView -destination 'platform=iOS Simulator,name=iPhone 17,OS=27.0' ``` +`xcrun swift test` builds the package for macOS. These sources import UIKit, so that command does not compile this package. + On another Xcode, list the simulators that copy of Xcode can see and pass one of those ids. `xcodebuild` only accepts destinations from the selected Xcode, so an id printed by a different install will fail. ```sh @@ -129,7 +131,16 @@ The Example app target deploys to iOS 18.6, so its simulator has to be iOS 18.6 xcodebuild build -project Example/Example.xcodeproj -scheme Example -destination 'id=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' ``` +## Releases + +Commit messages use `fix:`, `feat:` or `BREAKING CHANGE:`. [Release Please](https://github.com/googleapis/release-please) opens a release pull request from those messages. Merging that pull request runs [.github/workflows/release.yml](.github/workflows/release.yml), which tests the package and publishes the GitHub Release. + +[`.release-please-manifest.json`](.release-please-manifest.json) is the last released version, `5.1.1`. Tags do not use a `v` prefix, because the existing tags do not. Swift Package Manager installs from the git tag. This module imports UIKit, so the release does not attach a Linux static library. + +In the repository settings, under Actions, General, Workflow permissions, select read and write permissions and check "Allow GitHub Actions to create and approve pull requests". Under General, Releases, enable release immutability. + ## Contributing - This project's layout is based on - Ignore rules are inlined from [Swift.gitignore](https://github.com/github/gitignore/blob/main/Swift.gitignore) and [Global/Xcode.gitignore](https://github.com/github/gitignore/blob/main/Global/Xcode.gitignore). The macOS and secret rules above them come from [project-template](https://github.com/fulldecent/project-template). +- Releases follow [swift6-module-template](https://github.com/fulldecent/swift6-module-template/blob/v16.5.0/.github/workflows/release.yml), release 16.5.0. The published result here is the git tag. That template publishes a Linux static library, and this module imports UIKit. diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..f5ea202 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "simple", + "include-component-in-tag": false, + "include-v-in-tag": false, + "packages": { + ".": {} + } +}