diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e48ce89..bf36af2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: - '9.8' - '9.10' - '9.12' - # - '9.14' + - '9.14' include: - ghc_version: '9.8.1' oldest: true @@ -41,6 +41,23 @@ jobs: cabal configure --enable-test --test-show-details=streaming + - + # TODO: Remove these exclusions as packages are updated with 9.14 + if: ${{ matrix.ghc_version == '9.14' }} + name: Add 9.14 flags + run: > + cabal configure + --enable-append + --allow-newer='aeson:*' + --allow-newer='boring:*' + --allow-newer='indexed-traversable:*' + --allow-newer='indexed-traversable-instances:*' + --allow-newer='ordered-containers:*' + --allow-newer='semialign:*' + --allow-newer='text-iso8601:*' + --allow-newer='these:*' + --allow-newer='time-compat:*' + --allow-newer='uuid-types:*' - if: ${{ matrix.oldest }} name: Use oldest dependencies @@ -79,7 +96,7 @@ jobs: name: Install hooky run: | curl -fsSL \ - https://github.com/brandonchinn178/hooky/releases/download/v1.0.0/hooky-1.0.0-linux-x86_64 \ + https://github.com/brandonchinn178/hooky/releases/download/v1.0.2/hooky-1.0.2-linux-x86_64 \ -o /usr/local/bin/hooky chmod +x /usr/local/bin/hooky - @@ -102,9 +119,6 @@ jobs: id: setup name: Set up GHC uses: haskell-actions/setup@v2 - - - name: Strip unreleased section from CHANGELOG - run: sed -i -n '/^## Unreleased/d; /^## /,$p' CHANGELOG.md - name: Create sdist bundle run: cabal sdist --output-directory=. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 520c5d9..97e9cbb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,31 +1,60 @@ name: Release on: workflow_dispatch +defaults: + run: + shell: bash -eu -o pipefail {0} + jobs: + cabal_file: + runs-on: ubuntu-latest + steps: + - + uses: actions/checkout@v6 + - + uses: haskell-actions/parse-cabal-file@v1 + id: cabal_file + with: + cabal_file: kdl-hs.cabal + outputs: + version: ${{ steps.cabal_file.outputs.version }} + ci: uses: ./.github/workflows/ci.yml + check_changelog: + needs: + - cabal_file + runs-on: ubuntu-latest + env: + version: ${{ needs.cabal_file.outputs.version }} + steps: + - + uses: actions/checkout@v6 + - + name: Validate CHANGELOG + run: + scripts/release_notes.py validate-changelog + --release-version "${version}" + release: runs-on: ubuntu-latest needs: + - cabal_file - ci + - check_changelog + + env: + version: ${{ needs.cabal_file.outputs.version }} steps: - - uses: actions/checkout@v3 + uses: actions/checkout@v6 - uses: actions/download-artifact@v4 with: name: kdl-sdist - path: ./sdist/ - - - id: cabal_file - uses: haskell-actions/parse-cabal-file@v1 - with: - cabal_file: kdl-hs.cabal - - - name: Set version label - run: echo 'VERSION=v${{ steps.cabal_file.outputs.version }}' >> "${GITHUB_ENV}" + path: ./.release/sdist/ - id: hackage_token_secret name: Load Hackage token secret name @@ -33,23 +62,20 @@ jobs: USERNAME="$(echo "${GITHUB_ACTOR}" | tr '[:lower:]' '[:upper:]' | tr '-' '_')" echo "name=HACKAGE_TOKEN_${USERNAME}" >> "${GITHUB_OUTPUT}" - - name: Get CHANGELOG section - run: | - sed '/^## Unreleased/,/^$/d' CHANGELOG.md > /tmp/changelog-without-unreleased - if [[ "$(head -n 1 /tmp/changelog-without-unreleased)" != "## ${VERSION}" ]]; then - echo "CHANGELOG doesn't look updated" >&2 - exit 1 - fi - sed '1 d; /^## v/,$ d' /tmp/changelog-without-unreleased > /tmp/changelog-body + name: Generate release notes + run: + scripts/release_notes.py generate + --release-version "${version}" + | tee .release/release-notes.md - uses: haskell-actions/hackage-publish@v1 with: hackageToken: ${{ secrets[steps.hackage_token_secret.outputs.name] }} - packagesPath: ./sdist/ + packagesPath: ./.release/sdist/ - uses: softprops/action-gh-release@v1 with: - tag_name: ${{ env.VERSION }} - body_path: /tmp/changelog-body + tag_name: v${{ env.version }} + body_path: .release/release-notes.md draft: true target_commitish: ${{ github.sha }} diff --git a/CHANGELOG.md b/CHANGELOG.md index cd954a9..122115d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,3 @@ -## Unreleased - ## v1.0.0 * Implement KDL v2 parser diff --git a/kdl-hs.cabal b/kdl-hs.cabal index 2277599..15c93c8 100644 --- a/kdl-hs.cabal +++ b/kdl-hs.cabal @@ -18,6 +18,7 @@ extra-source-files: test/KDL/__snapshots__/DecoderSpec.snap.md test/KDL/__snapshots__/ParserSpec.snap.md test/KDL/__snapshots__/RenderSpec.snap.md + tools/kdl-test source-repository head type: git diff --git a/scripts/release_notes.py b/scripts/release_notes.py new file mode 100755 index 0000000..3636d80 --- /dev/null +++ b/scripts/release_notes.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +import argparse +import itertools +from pathlib import Path + +TOP = Path(__file__).resolve().parent.parent + + +def main() -> None: + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(required=True) + + parser_validate_changelog = subparsers.add_parser("validate-changelog") + parser_validate_changelog.add_argument("--release-version", required=True) + parser_validate_changelog.set_defaults( + run=lambda args: validate_changelog( + version=args.release_version, + ) + ) + + parser_generate = subparsers.add_parser("generate") + parser_generate.add_argument("--release-version", required=True) + parser_generate.set_defaults( + run=lambda args: generate( + version=args.release_version, + ) + ) + + args = parser.parse_args() + args.run(args) + + +def validate_changelog(version: str) -> None: + _ = get_changelog_for(version) + + +def get_changelog_for(version: str) -> str: + changelog = (TOP / "CHANGELOG.md").read_text().splitlines() + + if changelog[0] != f"## v{version}": + raise Exception( + f""" + CHANGELOG doesn't look updated. + Expected version: {version!r} + Got header: {changelog[0]!r} + """ + ) + + return "\n".join( + itertools.takewhile( + lambda line: not line.startswith("#"), + changelog[1:], + ) + ) + + +def generate(version: str) -> None: + release_notes = [ + get_changelog_for(version), + ] + print("\n".join(release_notes)) + + +if __name__ == "__main__": + main() diff --git a/test/KDL/ParserSpec.hs b/test/KDL/ParserSpec.hs index 613fbec..c6390e9 100644 --- a/test/KDL/ParserSpec.hs +++ b/test/KDL/ParserSpec.hs @@ -4,6 +4,7 @@ module KDL.ParserSpec (spec) where import Control.Monad (forM_) +import Data.Maybe (isJust) import Data.Text.IO qualified as Text import KDL qualified import Skeletest @@ -11,6 +12,7 @@ import Skeletest.Predicate qualified as P import System.Directory (findExecutable, listDirectory) import System.FilePath (takeExtension, ()) import System.IO.Temp (withSystemTempDirectory) +import System.IO.Unsafe (unsafePerformIO) import System.Process (callProcess) spec :: Spec @@ -44,17 +46,20 @@ spec = do -- tested in `parse` actual `shouldBe` expected - describe "kdl-test examples" $ do + (if dotSlashInstalled then id else skip "dotslash not installed") . describe "kdl-test examples" $ do it "decodes correctly" $ do decoder <- findExecutable "kdl-hs-test-decoder" >>= maybe (error "Could not find kdl-hs-test-decoder") pure - callProcess "scripts/kdl-test" ["run", "--decoder", decoder] + callProcess "tools/kdl-test" ["run", "--decoder", decoder] it "roundtrips successfully" $ do FixtureTmpDir tmpdir <- getFixture let dir = tmpdir "kdl-examples" - callProcess "scripts/kdl-test" ["extract", "--dir", dir] + callProcess "tools/kdl-test" ["extract", "--dir", dir] files <- filter ((== ".kdl") . takeExtension) <$> listDirectory (dir "valid") forM_ files $ \file -> do context file $ do content <- Text.readFile (dir "valid" file) (fmap KDL.render . KDL.parse) content `shouldBe` Right content + +dotSlashInstalled :: Bool +dotSlashInstalled = unsafePerformIO $ isJust <$> findExecutable "dotslash" diff --git a/scripts/kdl-test b/tools/kdl-test similarity index 100% rename from scripts/kdl-test rename to tools/kdl-test