ci: build in PRs (#202)

* ci: build in PRs

* ci: improve naming

* ci: upload individual artifacts

* ci: try running when PRs are pushed to

* ci: name

* ci: try individual again

* ci: friendship ended with gha; gitlab ci/cd is my new friend

* feat: introduce --from-artifact opt in install script
This commit is contained in:
nullishamy
2024-05-20 16:34:12 +01:00
committed by GitHub
parent 6eeda71fb7
commit 44be6bb595
4 changed files with 146 additions and 26 deletions

33
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: "Generate test artifacts"
on:
pull_request:
types: [opened, reopened, synchronize]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
- name: Install dependencies
run: pip install -r requirements.txt
- name: Install colloid specific dependencies
run: sudo apt update && sudo apt install -y sassc inkscape optipng
- name: Generate themes
run: |
python ./build.py mocha --all-accents --zip -d $PWD/releases &&
python ./build.py macchiato --all-accents --zip -d $PWD/releases &&
python ./build.py frappe --all-accents --zip -d $PWD/releases &&
python ./build.py latte --all-accents --zip -d $PWD/releases
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: '${{ github.sha }}-artifacts'
path: ./releases/*.zip

View File

@@ -1,4 +1,4 @@
name: "Release" name: "Mainline release"
on: on:
push: push:

View File

@@ -18,3 +18,23 @@ The palette patches are generated through `whiskers`,
so if you're changing them, they will need regenerated. Simply run `whiskers palette.tera` to rebuild them. so if you're changing them, they will need regenerated. Simply run `whiskers palette.tera` to rebuild them.
The process for building the theme is [documented in the README](./README.md#building). The process for building the theme is [documented in the README](./README.md#building).
### Running test builds
We support building and publishing test builds from PRs. When you open PRs, the CI will automatically build with your changes and push an artifact
which bundles all of the produced themes.
You can then download the artifacts as a zip (result should look similar to 7bff2448a81e36bf3b0e03bfbd649bebe6973ec7-artifacts.zip) and
pass the path into `install.py` under the `--from-artifact` option:
```bash
python3 install.py mocha blue --dest ./build --from-artifact ~/downloads/7bff2448a81e36bf3b0e03bfbd649bebe6973ec7-artifacts.zip
```
This will take the target flavor / accent out of the zip, and install it using the regular install process.
It is advised to pass a `--dest` when running in this mode, because the released zips follow the exact same naming scheme as regular builds.
This wil cause conflicts when you install, if you already had that theme installed. Passing a different destination allows you to move the
extracted folders to `~/.local/share/themes` yourself, adding a suffix as appropriate to avoid conflicts.
> [!WARNING]
> If you pass `--link` to the install script when working from a PR, it will forcibly overwrite your `~/.config/gtk-4.0/` symlinks.
> You will have to reinstall / relink to revert this.

View File

@@ -1,9 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os, zipfile, argparse, logging, io import os, zipfile, argparse, logging, io
from typing import Optional
from pathlib import Path from pathlib import Path
from dataclasses import dataclass from dataclasses import dataclass
from urllib.request import urlopen, Request from urllib.request import urlopen, Request
from urllib.parse import urlparse
logger = logging.getLogger("catppuccin-gtk") logger = logging.getLogger("catppuccin-gtk")
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
@@ -20,6 +22,17 @@ class InstallContext:
dest: Path dest: Path
link: bool link: bool
def build_info(self, include_url=True) -> str:
url = build_release_url(self)
info = f"""Installation info:
flavor: {self.flavor}
accent: {self.accent}
dest: {self.dest.absolute()}
link: {self.link}"""
if include_url:
info += f"\nremote_url: {url}"
return info
def parse_args(): def parse_args():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@@ -53,6 +66,13 @@ def parse_args():
help="Accent of the theme.", help="Accent of the theme.",
) )
parser.add_argument(
"--from-artifact",
type=Path,
dest="from_artifact",
help="Install from an artifact instead of a mainline release, pass the artifact path (e.g 7bff2448a81e36bf3b0e03bfbd649bebe6973ec7-artifacts.zip)",
)
parser.add_argument( parser.add_argument(
"--dest", "--dest",
"-d", "-d",
@@ -80,21 +100,12 @@ def build_release_url(ctx: InstallContext) -> str:
return f"{repo_root}/{release}/{zip_name}" return f"{repo_root}/{release}/{zip_name}"
def install(ctx: InstallContext): def fetch_zip(url: str) -> Optional[zipfile.ZipFile]:
url = build_release_url(ctx) req = Request(url)
build_info = f"""Installation info:
flavor: {ctx.flavor}
accent: {ctx.accent}
dest: {ctx.dest.absolute()}
link: {ctx.link}
remote_url: {url}"""
logger.info(build_info)
httprequest = Request(url)
zip_file = None zip_file = None
logger.info("Starting download...") logger.info("Starting download...")
with urlopen(httprequest) as response: with urlopen(req) as response:
logger.info(f"Response status: {response.status}") logger.info(f"Response status: {response.status}")
zip_file = zipfile.ZipFile(io.BytesIO(response.read())) zip_file = zipfile.ZipFile(io.BytesIO(response.read()))
logger.info("Download finished, zip is valid") logger.info("Download finished, zip is valid")
@@ -103,28 +114,81 @@ def install(ctx: InstallContext):
first_bad_file = zip_file.testzip() first_bad_file = zip_file.testzip()
if first_bad_file is not None: if first_bad_file is not None:
logger.error(f'Zip appears to be corrupt, first bad file is "{first_bad_file}"') logger.error(f'Zip appears to be corrupt, first bad file is "{first_bad_file}"')
return return None
logger.info("Download verified") logger.info("Download verified")
return zip_file
def add_libadwaita_links(ctx: InstallContext, rewrite: True):
dir_name = (
ctx.dest / f"catppuccin-{ctx.flavor}-{ctx.accent}-standard+default" / "gtk-4.0"
).absolute()
gtk4_dir = (Path(os.path.expanduser("~")) / ".config" / "gtk-4.0").absolute()
os.makedirs(gtk4_dir, exist_ok=True)
logger.info("Adding symlinks for libadwaita")
logger.info(f"Root: {dir_name}")
logger.info(f"Target: {gtk4_dir}")
try:
if rewrite:
os.remove(dir_name / "assets", gtk4_dir / "assets")
os.remove(dir_name / "gtk.css", gtk4_dir / "gtk.css")
os.remove(dir_name / "gtk-dark.css", gtk4_dir / "gtk-dark.css")
except FileNotFoundError:
logger.debug("Ignoring FileNotFound in symlink rewrite")
os.symlink(dir_name / "assets", gtk4_dir / "assets")
os.symlink(dir_name / "gtk.css", gtk4_dir / "gtk.css")
os.symlink(dir_name / "gtk-dark.css", gtk4_dir / "gtk-dark.css")
def install(ctx: InstallContext):
url = build_release_url(ctx)
logger.info(ctx.build_info())
zip_file = fetch_zip(url)
if zip_file is None:
return
logger.info("Extracting...") logger.info("Extracting...")
zip_file.extractall(ctx.dest) zip_file.extractall(ctx.dest)
logger.info("Extraction complete") logger.info("Extraction complete")
if ctx.link: if ctx.link:
dir_name = (ctx.dest / f"catppuccin-{ctx.flavor}-{ctx.accent}-standard+default" / 'gtk-4.0').absolute() add_libadwaita_links(ctx)
gtk4_dir = (Path(os.path.expanduser('~')) / '.config' / 'gtk-4.0').absolute()
os.makedirs(gtk4_dir, exist_ok=True)
logger.info("Adding symlinks for libadwaita") def install_from_artifact(ctx: InstallContext, artifact_path: Path):
logger.info(f'Root: {dir_name}') # Working from a pull request, special case it
logger.info(f'Target: {gtk4_dir}') logger.info(f"Extracting artifact from '{artifact_path}'")
os.symlink(dir_name / 'assets', gtk4_dir / 'assets') artifacts = zipfile.ZipFile(artifact_path)
os.symlink(dir_name / 'gtk.css', gtk4_dir / 'gtk.css')
os.symlink(dir_name / 'gtk-dark.css', gtk4_dir / 'gtk-dark.css')
logger.info("Verifying artifact...")
first_bad_file = artifacts.testzip()
if first_bad_file is not None:
logger.error(f'Zip appears to be corrupt, first bad file is "{first_bad_file}"')
return None
logger.info("Artifact verified")
logger.info(ctx.build_info(False))
# The zip, inside the artifacts, that we want to pull out
zip_name = f"catppuccin-{ctx.flavor}-{ctx.accent}-standard+default.zip"
logger.info(f"Pulling '{zip_name}' from the artifacts")
info = artifacts.getinfo(zip_name)
logger.info("Extracting the artifact...")
artifact = zipfile.ZipFile(io.BytesIO(artifacts.read(info)))
artifact.extractall(ctx.dest)
logger.info("Extraction complete")
if ctx.link:
logger.info("Adding links (with rewrite)")
add_libadwaita_links(ctx, True)
logger.info("Links added")
def main(): def main():
args = parse_args() args = parse_args()
dest = Path(os.path.expanduser("~")) / ".local" / "share" / "themes" dest = Path(os.path.expanduser("~")) / ".local" / "share" / "themes"
os.makedirs(dest, exist_ok=True) os.makedirs(dest, exist_ok=True)
@@ -135,9 +199,12 @@ def main():
flavor=args.flavor, accent=args.accent, dest=dest, link=args.link flavor=args.flavor, accent=args.accent, dest=dest, link=args.link
) )
install(ctx) if args.from_artifact:
install_from_artifact(ctx, args.from_artifact)
return
logger.info('Theme installation complete!') install(ctx)
logger.info("Theme installation complete!")
try: try: