diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5ba53fa..f9a437c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,16 +19,16 @@ jobs: matrix: pair: - erlang: master - rebar3: 3.25.0 + rebar3: 3.27.3 os: ubuntu-24.04 - erlang: 28 - rebar3: 3.25.0 + rebar3: 3.27.3 os: ubuntu-24.04 lint: true - erlang: 27 - rebar3: 3.23.0 + rebar3: 3.27.3 os: ubuntu-22.04 - erlang: 26 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1419a4c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,80 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: read + +jobs: + release: + name: Release + runs-on: ubuntu-24.04 + timeout-minutes: 15 + # The GitHub environment that hex_core's workload identity on hex.pm names + environment: release + permissions: + contents: write # create the GitHub release + id-token: write # the OIDC token mix hex.publish exchanges with Workload Identity + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1 + with: + otp-version: "28" + elixir-version: "1.20" + rebar3-version: "3.27.3" + install-hex: false + + # Hex 2.6.0-dev, the first version that publishes with Workload Identity + - name: Install Hex + run: mix archive.install github hexpm/hex ref 1d155e718aa76ab75d03f6f4bdcb0d8042f9a0ad --force + + - name: Read the release notes from the changelog + env: + TAG: ${{ github.ref_name }} + run: | + version=$(erl -noshell -eval '{ok, [{application, hex_core, App}]} = file:consult("src/hex_core.app.src"), io:put_chars(proplists:get_value(vsn, App)), halt().') + + if [ "$TAG" != "v$version" ]; then + echo "::error::Tag $TAG doesn't match version $version in src/hex_core.app.src" + exit 1 + fi + + # The lines under "## $TAG (date)" up to the next "## " heading, + # without leading and trailing blank lines + awk -v heading="## $TAG " ' + /^## / { + if (found) exit + found = index($0, heading) == 1 + next + } + !found { next } + /^[[:space:]]*$/ { if (started) blank++; next } + { + for (; blank > 0; blank--) print "" + print + started = 1 + } + ' CHANGELOG.md > "$RUNNER_TEMP/release_notes.md" + + if [ ! -s "$RUNNER_TEMP/release_notes.md" ]; then + echo "::error::CHANGELOG.md has no section for $TAG" + exit 1 + fi + + - name: Publish to Hex + env: + MIX_EXS: publish.exs + run: mix hex.publish --yes + + - name: Create the GitHub release + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + TAG: ${{ github.ref_name }} + run: gh release create "$TAG" --title "$TAG" --verify-tag --notes-file "$RUNNER_TEMP/release_notes.md" diff --git a/RELEASE.md b/RELEASE.md index 3c093d5..bbd8c8c 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -4,15 +4,13 @@ 2. Ensure CHANGELOG is updated and add current date -3. Commit changes above with title "Release v$VERSION" +3. Spot check docs: `rebar3 ex_doc` -4. Push master and the new tag +4. Commit changes above with title "Release v$VERSION" -5. Create GitHub release: `open "https://github.com/hexpm/hex_core/releases/new?title=v$VERSION&tag=v$VERSION&body=content%0Afrom%0Achangelog"` +5. Push main and the new tag `v$VERSION` -6. Spot check docs: `rebar3 ex_doc` - -7. Publish package: `rebar3 hex publish` +Pushing the tag runs the [Release workflow](.github/workflows/release.yml) in the `release` environment. It publishes the package and docs to Hex with Workload Identity and creates the GitHub release with the version's CHANGELOG section as its notes. To publish by hand instead, run `MIX_EXS=publish.exs mix hex.publish`. ## Places where version is mentioned diff --git a/publish.exs b/publish.exs new file mode 100644 index 0000000..71211d0 --- /dev/null +++ b/publish.exs @@ -0,0 +1,33 @@ +# Publishes the package and docs with Mix: `MIX_EXS=publish.exs mix hex.publish`. +# +# It isn't named mix.exs because Mix would then build hex_core with Mix +# instead of rebar3 when it's a git dependency. The package doesn't include +# this file and lists rebar3 as its build tool, so Mix builds it with rebar3. +defmodule HexCore.MixProject do + use Mix.Project + + {:ok, [{:application, :hex_core, app}]} = :file.consult("src/hex_core.app.src") + @app app + + def project do + [ + app: :hex_core, + version: to_string(@app[:vsn]), + description: to_string(@app[:description]), + language: :erlang, + deps: [], + package: [ + build_tools: ["rebar3"], + files: ~w(CHANGELOG.md LICENSE README.md rebar.config rebar.config.script rebar.lock src), + exclude_patterns: [~r"^src/safe_erl_term\.erl$"], + licenses: Enum.map(@app[:licenses], &to_string/1), + links: Map.new(@app[:links], fn {name, url} -> {to_string(name), to_string(url)} end) + ], + aliases: [docs: &docs/1] + ] + end + + defp docs(_args) do + {_, 0} = System.cmd("rebar3", ["ex_doc"], into: IO.stream()) + end +end