diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index a3a7e4b..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,57 +0,0 @@ -on: - push: - pull_request: - workflow_run: - -name: Build and deploy - -jobs: - build: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - - # Could instead install `texlive-full`, but that takes ~20 minutes. - # We'll specify the packages we need manually. - - name: "Install TeXLive" - run: | - sudo apt update - DEBIAN_FRONTEND=noninteractive \ - sudo apt install --yes \ - texlive texlive-xetex \ - texlive-games texlive-fonts-extra texlive-latex-extra \ - texlive-pictures texlive-pstricks - - # Typst isn't packaged, and manual download gives us - # more control anyway. - - name: "Download Typst" - run: | - wget -q "https://github.com/typst/typst/releases/download/v0.12.0/typst-x86_64-unknown-linux-musl.tar.xz" - tar -xf "typst-x86_64-unknown-linux-musl.tar.xz" - mv "typst-x86_64-unknown-linux-musl/typst" . - rm "typst-x86_64-unknown-linux-musl.tar.xz" - rm -dr "typst-x86_64-unknown-linux-musl" - - # Builds all handouts, LaTeX and Typst - - name: "Build handouts" - run: TYPST_PATH="$(pwd)/typst" python tools/build/main.py - - # Upload logs, even if build fails. - # LaTeX stdout/stderr isn't always helpful. - - name: "Save LaTeX logs" - uses: actions/upload-artifact@v3 - if: always() - with: - name: "LaTeX logs" - path: "**/*.log" - retention-days: 1 - - # Upload build output - - name: "Save output" - uses: actions/upload-artifact@v3 - with: - name: "Build output" - path: "output/*" - retention-days: 10 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1cdfe33 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,107 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + typos: + name: "Typos" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check typos + uses: crate-ci/typos@master + with: + config: ./tools/typos.toml + + typstyle: + name: "Typst formatting" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: "Download Typstyle" + run: | + wget -q "https://github.com/Enter-tainer/typstyle/releases/download/v0.12.14/typstyle-x86_64-unknown-linux-musl" + chmod +x typstyle-x86_64-unknown-linux-musl + + - name: Check typst formatting + run: | + find . -name "*.typ" -type f -print0 | xargs -0 \ + ./typstyle-x86_64-unknown-linux-musl --check + + build: + needs: + - typos + - typstyle + + name: "Build" + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + # Could instead install `texlive-full`, but that takes ~20 minutes. + # We'll specify the packages we need manually. + - name: "Install TeXLive" + run: | + sudo apt update + DEBIAN_FRONTEND=noninteractive \ + sudo apt install --yes \ + texlive texlive-xetex \ + texlive-games texlive-fonts-extra texlive-latex-extra \ + texlive-pictures texlive-pstricks \ + python3-requests + + # Typst isn't packaged, and manual download gives us + # more control anyway. + - name: "Download Typst" + run: | + wget -q "https://github.com/typst/typst/releases/download/v0.12.0/typst-x86_64-unknown-linux-musl.tar.xz" + tar -xf "typst-x86_64-unknown-linux-musl.tar.xz" + mv "typst-x86_64-unknown-linux-musl/typst" . + rm "typst-x86_64-unknown-linux-musl.tar.xz" + rm -dr "typst-x86_64-unknown-linux-musl" + + # Builds all handouts, LaTeX and Typst + - name: "Build handouts" + run: TYPST_PATH="$(pwd)/typst" python tools/scripts/build.py + + # Upload logs, even if build fails. + # LaTeX stdout/stderr isn't always helpful. + - name: "Save LaTeX logs" + uses: actions/upload-artifact@v3 + if: always() + with: + name: "LaTeX logs" + path: "**/*.log" + retention-days: 14 + + # Upload build output + - name: "Save output" + uses: actions/upload-artifact@v3 + with: + name: "Build output" + path: "output/*" + retention-days: 7 + + - name: "Publish package (hash)" + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + run: | + PUBLISH_USER="${{ secrets.PUBLISH_USER }}" \ + PUBLISH_KEY="${{ secrets.PUBLISH_KEY }}" \ + VERSION="${{ github.sha }}" \ + PACKAGE="${{ vars.PACKAGE }}" \ + python tools/scripts/publish.py + + - name: "Publish package (latest)" + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + run: | + PUBLISH_USER="${{ secrets.PUBLISH_USER }}" \ + PUBLISH_KEY="${{ secrets.PUBLISH_KEY }}" \ + VERSION="latest" \ + PACKAGE="${{ vars.PACKAGE }}" \ + python tools/scripts/publish.py diff --git a/.github/workflows/lints.yml b/.github/workflows/lints.yml deleted file mode 100644 index 6e35c58..0000000 --- a/.github/workflows/lints.yml +++ /dev/null @@ -1,30 +0,0 @@ -on: - push: - pull_request: - -name: Lints - -jobs: - typos: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Check typos - uses: crate-ci/typos@master - with: - config: ./tools/typos.toml - - typstyle: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: "Download Typstyle" - run: | - wget -q "https://github.com/Enter-tainer/typstyle/releases/download/v0.12.14/typstyle-x86_64-unknown-linux-musl" - chmod +x typstyle-x86_64-unknown-linux-musl - - - name: Check typst formatting - run: | - find . -name "*.typ" -type f -print0 | xargs -0 \ - ./typstyle-x86_64-unknown-linux-musl --check diff --git a/tools/build/main.py b/tools/scripts/build.py similarity index 89% rename from tools/build/main.py rename to tools/scripts/build.py index c33bfbd..536f48d 100644 --- a/tools/build/main.py +++ b/tools/scripts/build.py @@ -126,6 +126,9 @@ def build_typst(source_dir: Path, out_subdir: Path) -> IndexEntry | None: return None meta = read_meta_toml(meta_path) + handout_file = sanitize_file_name(f"{meta['title']}.pdf") + solutions_file = sanitize_file_name(f"{meta['title']}.sols.pdf") + # Do nothing if not published if not meta["publish_handout"]: return None @@ -136,7 +139,6 @@ def build_typst(source_dir: Path, out_subdir: Path) -> IndexEntry | None: out = OUT_DIR / out_subdir out.mkdir(parents=True, exist_ok=True) - file_name = sanitize_file_name(f"{meta['title']}.pdf") res = subprocess.run( [ TYPST_PATH, @@ -145,7 +147,7 @@ def build_typst(source_dir: Path, out_subdir: Path) -> IndexEntry | None: "main.typ", "--input", "show_solutions=false", - f"{out}/{file_name}", + f"{out}/{handout_file}", ], cwd=source_dir, stdout=subprocess.PIPE, @@ -158,14 +160,13 @@ def build_typst(source_dir: Path, out_subdir: Path) -> IndexEntry | None: # Build solutions if meta["publish_solutions"]: log(f"Building typst (solutions): {source_dir}") - file_name = sanitize_file_name(f"{meta['title']}.sols.pdf") res = subprocess.run( [ TYPST_PATH, "compile", "--ignore-system-fonts", "main.typ", - f"{out}/{file_name}", + f"{out}/{solutions_file}", ], cwd=source_dir, stdout=subprocess.PIPE, @@ -178,11 +179,9 @@ def build_typst(source_dir: Path, out_subdir: Path) -> IndexEntry | None: return { "title": meta["title"], "group": str(out_subdir), - "handout_file": f"{out_subdir}/{meta['title']}.pdf", + "handout_file": str(out / handout_file), "solutions_file": ( - f"{out_subdir}/{meta['title']}.sols.pdf" - if meta["publish_solutions"] - else None + str(out / solutions_file) if meta["publish_solutions"] else None ), } @@ -198,6 +197,9 @@ def build_xetex(source_dir: Path, out_subdir: Path) -> IndexEntry | None: return None meta = read_meta_toml(meta_path) + handout_file = sanitize_file_name(f"{meta['title']}.pdf") + solutions_file = sanitize_file_name(f"{meta['title']}.sols.pdf") + # Do nothing if not published if not meta["publish_handout"]: return None @@ -219,9 +221,8 @@ def build_xetex(source_dir: Path, out_subdir: Path) -> IndexEntry | None: stderr=subprocess.PIPE, ) - file_name = sanitize_file_name(f"{meta['title']}.pdf") try: - shutil.copy(source_dir / "main.pdf", f"{out}/{file_name}") + shutil.copy(source_dir / "main.pdf", f"{out}/{handout_file}") except Exception as e: log(f"Error: {e}") log_error(res) @@ -243,9 +244,8 @@ def build_xetex(source_dir: Path, out_subdir: Path) -> IndexEntry | None: stderr=subprocess.PIPE, ) - file_name = sanitize_file_name(f"{meta['title']}.sols.pdf") try: - shutil.copy(source_dir / "main.pdf", f"{out}/{file_name}") + shutil.copy(source_dir / "main.pdf", f"{out}/{solutions_file}") except Exception as e: log(f"Error: {e}") log_error(res) @@ -256,11 +256,9 @@ def build_xetex(source_dir: Path, out_subdir: Path) -> IndexEntry | None: return { "title": meta["title"], "group": str(out_subdir), - "handout_file": f"{out_subdir}/{meta['title']}.pdf", + "handout_file": str(out / handout_file), "solutions_file": ( - f"{out_subdir}/{meta['title']}.sols.pdf" - if meta["publish_solutions"] - else None + str(out / solutions_file) if meta["publish_solutions"] else None ), } diff --git a/tools/scripts/publish.py b/tools/scripts/publish.py new file mode 100644 index 0000000..2952dfd --- /dev/null +++ b/tools/scripts/publish.py @@ -0,0 +1,78 @@ +from pathlib import Path +import requests +import json +import os + +URL = "https://git.betalupi.com" +USER = os.environ["PUBLISH_USER"] +PACKAGE = os.environ["PACKAGE"] +VERSION = os.environ["VERSION"] +AUTH = requests.auth.HTTPBasicAuth(USER, os.environ["PUBLISH_KEY"]) + +ROOT: Path = Path(os.getcwd()) +SRC_DIR: Path = ROOT / "output" + + +def log(msg): + print(f"[PUBLISH.PY] {msg}") + + +log(f"Version is {VERSION}") +log(f"Package is {PACKAGE}") +log(f"Running in {ROOT}") +if not ROOT.is_dir(): + log("Root is not a directory, cannot continue") + exit(1) + +log(f"Source dir is {SRC_DIR}") +if not SRC_DIR.exists(): + log("Source dir doesn't exist, cannot continue") + exit(1) + +log(f"Deleting existing package {SRC_DIR}") +res = requests.delete( + f"{URL}/api/packages/{USER}/generic/{PACKAGE}/{VERSION}", + auth=AUTH, +) +if res.status_code != 204: + log(f"Deletion failed with code {res.status_code}, this is ok") + + +def upload(data, target: str): + res = requests.put( + f"{URL}/api/packages/{USER}/generic/{PACKAGE}/{VERSION}/{target}", + auth=AUTH, + data=data, + ) + + if res.status_code != 201: + log(f"Upload failed with code {res.status_code}") + exit(1) + + return f"{URL}/api/packages/{USER}/generic/{PACKAGE}/{VERSION}/{target}" + + +index_file = SRC_DIR / "index.json" +with index_file.open("r") as f: + index = json.load(f) + +new_index = [] +for handout in index: + title = handout["title"] + group = handout["group"] + h_file = SRC_DIR / handout["handout_file"] + s_file = SRC_DIR / handout["handout_file"] + log(f"Uploading {title}") + + h_url = None + s_url = None + + h_url = upload(h_file.open("rb").read(), f"{group} - {title}.pdf") + if s_file is not None: + s_url = upload(s_file.open("rb").read(), f"{group} - {title}.sols.pdf") + + new_index.append( + {"title": title, "group": group, "handout": h_url, "solutions": s_url} + ) + +upload(json.dumps(new_index), "index.json") diff --git a/tools/build/ruff.toml b/tools/scripts/ruff.toml similarity index 91% rename from tools/build/ruff.toml rename to tools/scripts/ruff.toml index f110dc4..c8f72c4 100644 --- a/tools/build/ruff.toml +++ b/tools/scripts/ruff.toml @@ -2,6 +2,7 @@ exclude = ["venv"] line-length = 88 indent-width = 4 target-version = "py39" +include = ["scripts/**/*.py"] [lint] select = ["E4", "E7", "E9", "F"]