aboutsummaryrefslogtreecommitdiff
path: root/.github
diff options
context:
space:
mode:
authorspicyjpeg <88942473+spicyjpeg@users.noreply.github.com>2021-11-28 17:31:33 +0100
committerspicyjpeg <88942473+spicyjpeg@users.noreply.github.com>2021-11-28 17:31:33 +0100
commit603b42797c4b0e7a3e2a3cac320daecf1ee34feb (patch)
treeff11eb37407bdb399bf53e69d2111f12af17d4b2 /.github
parent45123e1b968d1883fed9b8526157ce2c4bffc4a7 (diff)
downloadpsn00bsdk-603b42797c4b0e7a3e2a3cac320daecf1ee34feb.tar.gz
Add GitHub Actions CI/release workflow, rewrite changelog
Diffstat (limited to '.github')
-rw-r--r--.github/scripts/generate_release_notes.py194
-rw-r--r--.github/workflows/build.yml208
2 files changed, 402 insertions, 0 deletions
diff --git a/.github/scripts/generate_release_notes.py b/.github/scripts/generate_release_notes.py
new file mode 100644
index 0000000..f3e4870
--- /dev/null
+++ b/.github/scripts/generate_release_notes.py
@@ -0,0 +1,194 @@
+#!/usr/bin/env python3
+# PSn00bSDK release notes generator
+# (C) 2021 spicyjpeg - MPL licensed
+
+import sys, re
+from time import gmtime, strptime
+from argparse import ArgumentParser, FileType
+
+## Helpers
+
+VERSION_REGEX = re.compile(r"^(?:refs\/tags\/)?(?:v|ver|version|release)? *(.*)")
+
+def parse_date(date):
+ return strptime(date.strip(), "%Y-%m-%d")
+
+def normalize_version(version):
+ match = VERSION_REGEX.match(version.lower())
+ if not match:
+ raise ValueError(f"invalid version string: {version}")
+
+ return match.group(1)
+
+## Changelog parser
+
+BLOCK_REGEX = re.compile(r"^#{2,}[ \t]*([0-9]{4}-[0-9]{2}-[0-9]{2})(?:[:\- \t]+(.+?))?$", re.MULTILINE)
+AUTHOR_REGEX = re.compile(r"^([A-Za-z0-9_].*?)[ \t]*:.*?$", re.MULTILINE)
+FIRST_VERSION = "initial"
+
+def parse_authors(block):
+ # [ _crap, author, body, author, body, ... ]
+ items = AUTHOR_REGEX.split(block.strip())
+
+ if items[0].strip():
+ raise RuntimeError("a block has changes listed with no author specified")
+
+ authors = {}
+ for i in range(1, len(items), 2):
+ name, body = items[i:i + 2]
+
+ name = name.strip()
+ body = body.strip()
+ if name not in authors:
+ authors[name] = ""
+
+ authors[name] += body
+
+ return authors
+
+def parse_blocks(changelog):
+ # [ _crap, date, version, body, date, version, body, ... ]
+ items = BLOCK_REGEX.split(changelog.strip())
+
+ #if items[0].strip():
+ #raise RuntimeError("the changelog doesn't start with a valid block")
+
+ # Iterate over all blocks from bottom to top (i.e. oldest first).
+ last_version = FIRST_VERSION
+
+ for i in range(len(items), 1, -3):
+ date, version, body = items[i - 3:i]
+
+ # If no version is present in the header, assume it's the same as the
+ # previous block's version.
+ if version:
+ version = normalize_version(version)
+ last_version = version
+ else:
+ version = last_version
+
+ yield parse_date(date), version, parse_authors(body)
+
+## Release notes generation
+
+VERSION_TEMPLATE = """New in version **{version}** (contributed by {authors}):
+
+{changes}
+
+"""
+NOTES_TEMPLATE = """{notes}
+
+-------------------------------------------------------
+_These notes have been generated automatically._
+_See the changelog or commit history for more details._
+"""
+
+NO_VERSIONS_TEMPLATE = "No information available about this release."
+AUTHOR_LINK_TEMPLATE = "**{0}**"
+#AUTHOR_LINK_TEMPLATE = "[{0}](https://github.com/{0})"
+
+def generate_notes(versions):
+ notes = ""
+
+ for version, ( authors, changes ) in versions.items():
+ _authors = list(set(authors))
+ _authors.sort()
+ _authors = map(AUTHOR_LINK_TEMPLATE.format, _authors)
+
+ notes += VERSION_TEMPLATE.format(
+ version = version,
+ authors = ", ".join(_authors),
+ changes = "\n\n".join(changes)
+ )
+
+ if not notes:
+ notes = NO_VERSIONS_TEMPLATE
+
+ return NOTES_TEMPLATE.format(notes = notes.strip())
+
+## Main
+
+def get_args():
+ parser = ArgumentParser(
+ description = "Generates and outputs release notes from a Markdown changelog file.",
+ add_help = False
+ )
+
+ tools_group = parser.add_argument_group("Tools")
+ tools_group.add_argument(
+ "-h", "--help",
+ action = "help",
+ help = "Show this help message and exits"
+ )
+
+ files_group = parser.add_argument_group("Files")
+ files_group.add_argument(
+ "changelog",
+ type = FileType("rt"),
+ help = "Markdown changelog file to parse"
+ )
+ files_group.add_argument(
+ "-o", "--output",
+ type = FileType("wt"),
+ default = sys.stdout,
+ help = "Where to output release notes (stdout by default)",
+ metavar = "file"
+ )
+
+ filter_group = parser.add_argument_group("Filters")
+ filter_group.add_argument(
+ "-v", "--version",
+ action = "append",
+ type = str,
+ help = "Ignore all changes not belonging to a version (can be specified multiple times)",
+ metavar = "name"
+ )
+ filter_group.add_argument(
+ "-f", "--from-date",
+ type = parse_date,
+ default = parse_date("2000-01-01"),
+ help = "Ignore all changes before date",
+ metavar = "yyyy-mm-dd"
+ )
+ filter_group.add_argument(
+ "-t", "--to-date",
+ type = parse_date,
+ default = gmtime(),
+ help = "Ignore all changes after date",
+ metavar = "yyyy-mm-dd"
+ )
+
+ return parser.parse_args()
+
+def main():
+ args = get_args()
+ version_list = list(map(normalize_version, args.version or []))
+
+ with args.changelog as _file:
+ changelog = _file.read()
+
+ # Iterate over all blocks in the changelog and sort them by version,
+ # merging all changes and authors for each version.
+ versions = {}
+
+ for date, version, authors in parse_blocks(changelog):
+ # Apply version and date filters.
+ if version_list and (version not in version_list):
+ continue
+ if date < args.from_date or date > args.to_date:
+ continue
+
+ if version not in versions:
+ versions[version] = [], []
+
+ _authors, _changes = versions[version]
+ _authors.extend(authors.keys())
+ _changes.extend(authors.values())
+
+ notes = generate_notes(versions)
+
+ with args.output as _file:
+ _file.write(notes)
+
+if __name__ == "__main__":
+ main()
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..d6746b0
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,208 @@
+# PSn00bSDK GitHub Actions CI script
+# (C) 2021 spicyjpeg - MPL licensed
+
+# The GCC toolchain is stored in the GitHub Actions cache after being built. To
+# minimize build times, all the toolchain build steps are skipped if there is a
+# cached copy of the toolchain that has not expired (even though the build-gcc
+# job still has to run in order to check the cache's contents). The cache is
+# shared between all actions in a repo.
+
+name: Build PSn00bSDK
+on: [ push, pull_request ]
+env:
+ BINUTILS_VERSION: 2.36
+ GCC_VERSION: 11.1.0
+ GCC_TARGET: mipsel-none-elf
+
+jobs:
+ # This is based on doc/toolchain.md, no surprises here other than the cache.
+ build-gcc:
+ name: Build GCC toolchain
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Initialize toolchain cache
+ id: _cache
+ uses: actions/cache@v2
+ with:
+ key: gcc-${{ env.GCC_TARGET }}-${{ env.GCC_VERSION }}
+ path: gcc
+
+ - name: Install prerequisites
+ if: ${{ steps._cache.outputs.cache-hit != 'true' }}
+ run: |
+ sudo apt-get update -y
+ sudo apt-get install -y --no-install-recommends make g++-mingw-w64-x86-64
+
+ - name: Download and extract sources
+ if: ${{ steps._cache.outputs.cache-hit != 'true' }}
+ run: |
+ wget -q -O binutils.tar.xz https://ftpmirror.gnu.org/gnu/binutils/binutils-${{ env.BINUTILS_VERSION }}.tar.xz
+ wget -q -O gcc.tar.xz https://ftpmirror.gnu.org/gnu/gcc/gcc-${{ env.GCC_VERSION }}/gcc-${{ env.GCC_VERSION }}.tar.xz
+ tar xf binutils.tar.xz
+ tar xf gcc.tar.xz
+ cd gcc-${{ env.GCC_VERSION }}
+ contrib/download_prerequisites
+
+ - name: Build binutils for Linux
+ if: ${{ steps._cache.outputs.cache-hit != 'true' }}
+ run: |
+ mkdir binutils_linux
+ cd binutils_linux
+ ../binutils-${{ env.BINUTILS_VERSION }}/configure --prefix=${{ github.workspace }}/gcc/linux --target=${{ env.GCC_TARGET }} --disable-docs --disable-nls --with-float=soft
+ make -j 2
+ make install-strip
+ echo "${{ github.workspace }}/gcc/linux/bin" >>$GITHUB_PATH
+
+ - name: Build GCC for Linux
+ if: ${{ steps._cache.outputs.cache-hit != 'true' }}
+ run: |
+ mkdir gcc_linux
+ cd gcc_linux
+ ../gcc-${{ env.GCC_VERSION }}/configure --prefix=${{ github.workspace }}/gcc/linux --target=${{ env.GCC_TARGET }} --disable-docs --disable-nls --disable-libada --disable-libssp --disable-libquadmath --disable-libstdc++-v3 --with-float=soft --enable-languages=c,c++ --with-gnu-as --with-gnu-ld
+ make -j 2
+ make install-strip
+
+ - name: Build binutils for Windows
+ if: ${{ steps._cache.outputs.cache-hit != 'true' }}
+ run: |
+ mkdir binutils_windows
+ cd binutils_windows
+ ../binutils-${{ env.BINUTILS_VERSION }}/configure --prefix=${{ github.workspace }}/gcc/windows --build=x86_64-linux-gnu --host=x86_64-w64-mingw32 --target=${{ env.GCC_TARGET }} --disable-docs --disable-nls --with-float=soft
+ make -j 2
+ make install-strip
+
+ - name: Build GCC for Windows
+ if: ${{ steps._cache.outputs.cache-hit != 'true' }}
+ run: |
+ mkdir gcc_windows
+ cd gcc_windows
+ ../gcc-${{ env.GCC_VERSION }}/configure --prefix=${{ github.workspace }}/gcc/windows --build=x86_64-linux-gnu --host=x86_64-w64-mingw32 --target=${{ env.GCC_TARGET }} --disable-docs --disable-nls --disable-libada --disable-libssp --disable-libquadmath --disable-libstdc++-v3 --with-float=soft --enable-languages=c,c++ --with-gnu-as --with-gnu-ld
+ make -j 2
+ make install-strip
+
+ # No surprises here either. The GitHub Actions VMs even come with most of the
+ # dependencies required to build PSn00bSDK preinstalled.
+ build-sdk-windows:
+ name: Build PSn00bSDK on Windows
+ runs-on: windows-latest
+ needs: build-gcc
+
+ steps:
+ # Due to a bug in the cache action (and in order to use Ninja and pacman)
+ # the directories MSys2 stores binaries in must be added to PATH. For
+ # some reason they are not present in PATH by default.
+ # https://github.com/actions/cache/issues/576
+ - name: Add MSys2 to PATH
+ run: |
+ echo "C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
+ echo "C:\msys64\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
+
+ - name: Initialize toolchain cache
+ uses: actions/cache@v2
+ with:
+ key: gcc-${{ env.GCC_TARGET }}-${{ env.GCC_VERSION }}
+ path: gcc
+
+ - name: Install prerequisites
+ run: |
+ pacman -S --noconfirm mingw-w64-x86_64-ninja mingw-w64-x86_64-gcc
+
+ - name: Fetch repo contents
+ uses: actions/checkout@v2
+ with:
+ path: sdk
+
+ - name: Update repo submodules
+ run: |
+ cd sdk
+ git submodule update --init --recursive --remote
+
+ - name: Build and package PSn00bSDK
+ run: |
+ cmake --preset ci -S sdk -DPSN00BSDK_TC=${{ github.workspace }}\gcc\windows
+ cmake --build build
+ cmake --build build -t package
+
+ - name: Upload build artifacts
+ uses: actions/upload-artifact@v2
+ with:
+ name: psn00bsdk-windows
+ path: |
+ build/packages/*
+ !build/packages/_CPack_Packages
+
+ build-sdk-linux:
+ name: Build PSn00bSDK on Linux
+ runs-on: ubuntu-latest
+ needs: build-gcc
+
+ steps:
+ - name: Initialize toolchain cache
+ uses: actions/cache@v2
+ with:
+ key: gcc-${{ env.GCC_TARGET }}-${{ env.GCC_VERSION }}
+ path: gcc
+
+ - name: Install prerequisites
+ run: |
+ sudo apt-get update -y
+ sudo apt-get install -y --no-install-recommends ninja-build
+
+ - name: Fetch repo contents
+ uses: actions/checkout@v2
+ with:
+ path: sdk
+
+ - name: Update repo submodules
+ run: |
+ cd sdk
+ git submodule update --init --recursive --remote
+
+ - name: Build and package PSn00bSDK
+ run: |
+ cmake --preset ci -S sdk -DPSN00BSDK_TC=${{ github.workspace }}/gcc/linux
+ cmake --build build
+ cmake --build build -t package
+
+ - name: Upload build artifacts
+ uses: actions/upload-artifact@v2
+ with:
+ name: psn00bsdk-linux
+ path: |
+ build/packages/*
+ !build/packages/_CPack_Packages
+
+ # This job takes care of creating a new release and upload the build
+ # artifacts if the last commit is associated to a tag.
+ create-release:
+ name: Create release
+ runs-on: ubuntu-latest
+ needs: [ build-sdk-windows, build-sdk-linux ]
+
+ steps:
+ - name: Fetch repo contents
+ uses: actions/checkout@v2
+ with:
+ path: sdk
+
+ - name: Fetch build artifacts
+ if: ${{ github.ref_type == 'tag' }}
+ uses: actions/download-artifact@v2
+ with:
+ path: .
+
+ - name: Generate release notes
+ if: ${{ github.ref_type == 'tag' }}
+ run: |
+ sdk/.github/scripts/generate_release_notes.py -v ${{ github.ref_name }} -o release.md sdk/CHANGELOG.md
+
+ - name: Publish release
+ if: ${{ github.ref_type == 'tag' }}
+ uses: softprops/action-gh-release@v1
+ with:
+ fail_on_unmatched_files: true
+ body_path: release.md
+ files: |
+ psn00bsdk-windows/*
+ psn00bsdk-linux/*