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:
33
.github/workflows/build.yml
vendored
Normal file
33
.github/workflows/build.yml
vendored
Normal 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
|
||||||
|
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: "Release"
|
name: "Mainline release"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
@@ -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.
|
||||||
|
115
install.py
115
install.py
@@ -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:
|
||||||
|
Reference in New Issue
Block a user