Skip to content

Commit c3f5841

Browse files
committed
chore: CI-CD and scripts added
1 parent 6a7a155 commit c3f5841

7 files changed

Lines changed: 815 additions & 0 deletions

File tree

.cargo/config.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# [build]
2+
# target = "x86_64-pc-windows-gnu"
3+
4+
[alias]
5+
run-ctrl = "run -p lecoo-ctrl"
6+
build-win = "build --release --target x86_64-pc-windows-gnu -p lecoo-ec-daemon"
7+
build-ctrl-win = "build --release --target x86_64-pc-windows-gnu -p lecoo-ctrl"
8+
build-linux = "build --release -p lecoo-ec-daemon"
9+
build-ctrl-linux = "build --release -p lecoo-ctrl"

.github/workflows/release.yml

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
name: Create Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*.*.*"
7+
8+
jobs:
9+
prepare:
10+
name: Prepare matrix
11+
runs-on: ubuntu-latest
12+
outputs:
13+
matrix: ${{ steps.matrix.outputs.result }}
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- uses: actions/setup-python@v5
18+
with:
19+
python-version: "3.12"
20+
21+
- name: Generate build matrix from release.toml
22+
id: matrix
23+
shell: python
24+
run: |
25+
import json, tomllib, pathlib, os, sys
26+
27+
config = tomllib.loads(
28+
pathlib.Path("release.toml").read_text(encoding="utf-8")
29+
)
30+
bundles = [
31+
b for b in config.get("bundles", [])
32+
if b.get("enabled", True)
33+
]
34+
if not bundles:
35+
print("ERROR: no enabled bundles in release.toml", file=sys.stderr)
36+
sys.exit(1)
37+
38+
matrix = {
39+
"include": [
40+
{"id": b["id"], "runner": b["runner"]}
41+
for b in bundles
42+
]
43+
}
44+
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
45+
f.write(f"result={json.dumps(matrix)}\n")
46+
47+
print(json.dumps(matrix, indent=2))
48+
49+
build:
50+
name: "Build · ${{ matrix.id }}"
51+
needs: prepare
52+
strategy:
53+
fail-fast: false
54+
matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}
55+
runs-on: ${{ matrix.runner }}
56+
57+
steps:
58+
- uses: actions/checkout@v4
59+
60+
- uses: actions/setup-python@v5
61+
with:
62+
python-version: "3.12"
63+
64+
- name: Extract bundle config
65+
id: cfg
66+
shell: python
67+
env:
68+
BUNDLE_ID: ${{ matrix.id }}
69+
REF_NAME: ${{ github.ref_name }}
70+
run: |
71+
import json, tomllib, pathlib, os
72+
73+
config = tomllib.loads(
74+
pathlib.Path("release.toml").read_text(encoding="utf-8")
75+
)
76+
bid = os.environ["BUNDLE_ID"]
77+
bundle = next(b for b in config["bundles"] if b["id"] == bid)
78+
79+
pathlib.Path("bundle.json").write_text(
80+
json.dumps(bundle), encoding="utf-8"
81+
)
82+
83+
version = os.environ["REF_NAME"].lstrip("v")
84+
archive = bundle["archive"].replace("{version}", version)
85+
86+
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
87+
f.write(f"archive={archive}\n")
88+
89+
- uses: dtolnay/rust-toolchain@stable
90+
91+
- name: Add extra Rust targets
92+
shell: python
93+
run: |
94+
import json, pathlib, subprocess
95+
bundle = json.loads(pathlib.Path("bundle.json").read_text())
96+
for t in bundle.get("rust_targets", []):
97+
print(f" rustup target add {t}")
98+
subprocess.run(["rustup", "target", "add", t], check=True)
99+
100+
- name: Run setup commands
101+
shell: python
102+
run: |
103+
import json, pathlib, subprocess
104+
bundle = json.loads(pathlib.Path("bundle.json").read_text())
105+
for cmd in bundle.get("setup", []):
106+
print(f" ▸ {cmd}")
107+
subprocess.run(cmd, shell=True, check=True)
108+
109+
- name: Cache Cargo
110+
uses: actions/cache@v4
111+
with:
112+
path: |
113+
~/.cargo/registry
114+
~/.cargo/git
115+
target
116+
key: ${{ runner.os }}-cargo-${{ matrix.id }}-${{ hashFiles('**/Cargo.lock') }}
117+
restore-keys: |
118+
${{ runner.os }}-cargo-${{ matrix.id }}-
119+
${{ runner.os }}-cargo-
120+
121+
- name: Build
122+
shell: python
123+
run: |
124+
import json, pathlib, subprocess, sys
125+
126+
bundle = json.loads(pathlib.Path("bundle.json").read_text())
127+
128+
commands = bundle.get("build", [])
129+
if isinstance(commands, str):
130+
commands = [commands]
131+
132+
for i, cmd in enumerate(commands, 1):
133+
print(f"\n{'='*60}")
134+
print(f" [{i}/{len(commands)}] {cmd}")
135+
print(f"{'='*60}\n", flush=True)
136+
result = subprocess.run(cmd, shell=True)
137+
if result.returncode != 0:
138+
print(f"FAILED (exit {result.returncode}): {cmd}", file=sys.stderr)
139+
sys.exit(result.returncode)
140+
141+
print(f"\nAll {len(commands)} build(s) succeeded.")
142+
143+
- name: Package artifact
144+
shell: python
145+
env:
146+
REF_NAME: ${{ github.ref_name }}
147+
run: |
148+
import json, pathlib, shutil, hashlib, os, sys
149+
150+
bundle = json.loads(pathlib.Path("bundle.json").read_text())
151+
version = os.environ["REF_NAME"].lstrip("v")
152+
archive = bundle["archive"].replace("{version}", version)
153+
154+
# Stage files
155+
staging = pathlib.Path("staging")
156+
if staging.exists():
157+
shutil.rmtree(staging)
158+
staging.mkdir()
159+
160+
for f in bundle["files"]:
161+
src = pathlib.Path(f["src"])
162+
dst = staging / f["dst"]
163+
if not src.exists():
164+
print(f"ERROR: file not found -> {src}", file=sys.stderr)
165+
sys.exit(1)
166+
dst.parent.mkdir(parents=True, exist_ok=True)
167+
if src.is_dir():
168+
shutil.copytree(src, dst)
169+
else:
170+
shutil.copy2(src, dst)
171+
print(f" {src} -> {dst}")
172+
173+
# Create archive
174+
dist = pathlib.Path("dist")
175+
dist.mkdir(exist_ok=True)
176+
177+
if archive.endswith(".tar.gz"):
178+
base = str(dist / archive[:-7])
179+
shutil.make_archive(base, "gztar", root_dir=str(staging))
180+
elif archive.endswith(".zip"):
181+
base = str(dist / archive[:-4])
182+
shutil.make_archive(base, "zip", root_dir=str(staging))
183+
else:
184+
print(f"ERROR: unsupported format -> {archive}", file=sys.stderr)
185+
sys.exit(1)
186+
187+
archive_path = dist / archive
188+
size = archive_path.stat().st_size
189+
print(f"\n Archive: {archive_path} ({size:,} bytes)")
190+
191+
# SHA-256
192+
sha = hashlib.sha256(archive_path.read_bytes()).hexdigest()
193+
(dist / f"{archive}.sha256").write_text(sha)
194+
print(f" SHA-256: {sha}")
195+
196+
- name: Upload
197+
uses: actions/upload-artifact@v4
198+
with:
199+
name: release-${{ matrix.id }}
200+
path: dist/*
201+
202+
203+
release:
204+
name: Publish Release
205+
needs: [prepare, build]
206+
runs-on: ubuntu-latest
207+
permissions:
208+
contents: write
209+
210+
steps:
211+
- uses: actions/checkout@v4
212+
213+
- uses: actions/setup-python@v5
214+
with:
215+
python-version: "3.12"
216+
217+
- name: Download all build artifacts
218+
uses: actions/download-artifact@v4
219+
with:
220+
pattern: release-*
221+
path: dist
222+
merge-multiple: true
223+
224+
- name: Generate release notes
225+
shell: python
226+
env:
227+
REF_NAME: ${{ github.ref_name }}
228+
REPO: ${{ github.repository }}
229+
run: |
230+
import tomllib, pathlib, os, re
231+
232+
config = tomllib.loads(
233+
pathlib.Path("release.toml").read_text(encoding="utf-8")
234+
)
235+
tag = os.environ["REF_NAME"]
236+
version = tag.lstrip("v")
237+
repo = os.environ["REPO"]
238+
base = f"https://github.com/{repo}/releases/download/{tag}"
239+
240+
body = config.get("release", {}).get("notes_header", "").strip()
241+
body += "\n\n## Downloads\n\n"
242+
body += "| File | Checksum |\n|---|---|\n"
243+
244+
for b in config.get("bundles", []):
245+
if not b.get("enabled", True):
246+
continue
247+
a = b["archive"].replace("{version}", version)
248+
body += f"| [{a}]({base}/{a}) | [SHA256]({base}/{a}.sha256) |\n"
249+
250+
body += "\n"
251+
252+
# Changelog section
253+
cl = pathlib.Path("CHANGELOG.md")
254+
if cl.exists():
255+
lines = cl.read_text(encoding="utf-8").splitlines()
256+
pattern = re.compile(rf"^## \[{re.escape(version)}\]")
257+
capturing = False
258+
captured = []
259+
for line in lines:
260+
if pattern.match(line):
261+
capturing = True
262+
continue
263+
if capturing and line.startswith("## ["):
264+
break
265+
if capturing:
266+
captured.append(line)
267+
content = "\n".join(captured).strip()
268+
if content:
269+
body += f"## What's Changed\n\n{content}\n"
270+
271+
pathlib.Path("releasenotes.md").write_text(body, encoding="utf-8")
272+
print(body)
273+
274+
- name: Create GitHub Release
275+
uses: softprops/action-gh-release@v2
276+
with:
277+
files: dist/*
278+
name: "Release ${{ github.ref_name }}"
279+
body_path: releasenotes.md
280+
prerelease: ${{ contains(github.ref_name, '-') }}

release.toml

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
[release]
2+
notes_header = """
3+
## 🚀 Lecoo Control Center
4+
5+
A low-level Embedded Controller (EC) daemon and CLI for fan control, power limits (TDP), charge limits (FlexiCharger), and lighting management for Lecoo Pro 14.
6+
7+
### ⚠️ Important Disclaimer
8+
This software interacts directly with your system's hardware. **Use at your own risk!** Incorrect configurations (e.g., setting custom fan curves to 0 RPM under heavy load) can result in overheating and irreversible hardware damage.
9+
10+
### 🛠️ How to Install
11+
12+
**Windows:**
13+
1. Download the `*-windows.zip` archive below and extract it to a folder.
14+
2. Right-click on `install.bat` and select **"Run as Administrator"**.
15+
3. Open a *new* terminal window and type `lecoo-ctrl help`.
16+
17+
**Linux:**
18+
1. Download the `*-linux.tar.gz` archive and extract it.
19+
2. Open a terminal in the extracted folder and run: `sudo ./install.sh`.
20+
3. Use the `lecoo-ctrl` command to manage the daemon.
21+
22+
### 📖 Useful Links
23+
* 🇬🇧 [English README](https://github.com/LaVashikk/Lecoo-Control-Center/blob/main/README.md) | 🇷🇺 [Russian README](https://github.com/LaVashikk/Lecoo-Control-Center/blob/main/README_RU.md)
24+
* 🐛 [Report a Bug or Request a Feature](https://github.com/LaVashikk/Lecoo-Control-Center/issues)
25+
* ☕ [Support the Development](https://pay.cloudtips.ru/p/7e960f26)
26+
"""
27+
28+
[[bundles]]
29+
id = "windows"
30+
enabled = true
31+
runner = "windows-latest"
32+
archive = "lecoo-{version}-windows.zip"
33+
34+
rust_targets = ["x86_64-pc-windows-msvc"]
35+
36+
setup = []
37+
38+
build = [
39+
"cargo build --release --target x86_64-pc-windows-msvc -p lecoo-ec-daemon",
40+
"cargo build --release --target x86_64-pc-windows-msvc -p lecoo-ctrl",
41+
# "cargo build --release --target x86_64-pc-windows-msvc -p lecoo-gui",
42+
]
43+
44+
[[bundles.files]]
45+
src = "target/x86_64-pc-windows-msvc/release/lecoo-ec-daemon.exe"
46+
dst = "lecoo-ec-daemon.exe"
47+
48+
[[bundles.files]]
49+
src = "target/x86_64-pc-windows-msvc/release/lecoo-ctrl.exe"
50+
dst = "lecoo-ctrl.exe"
51+
52+
[[bundles.files]]
53+
src = "libs/inpoutx64.dll"
54+
dst = "inpoutx64.dll"
55+
56+
[[bundles.files]]
57+
src = "scripts/windows/install.bat"
58+
dst = "install.bat"
59+
60+
[[bundles.files]]
61+
src = "scripts/windows/uninstall.bat"
62+
dst = "uninstall.bat"
63+
64+
[[bundles.files]]
65+
src = "LICENSE"
66+
dst = "LICENSE"
67+
68+
[[bundles]]
69+
id = "linux-x86_64"
70+
enabled = true
71+
runner = "ubuntu-latest"
72+
archive = "lecoo-{version}-linux.tar.gz"
73+
74+
rust_targets = []
75+
76+
setup = []
77+
78+
build = [
79+
"cargo build --release -p lecoo-ec-daemon",
80+
"cargo build --release -p lecoo-ctrl",
81+
]
82+
83+
[[bundles.files]]
84+
src = "target/release/lecoo-ec-daemon"
85+
dst = "lecoo-ec-daemon"
86+
87+
[[bundles.files]]
88+
src = "target/release/lecoo-ctrl"
89+
dst = "lecoo-ctrl"
90+
91+
[[bundles.files]]
92+
src = "scripts/linux/install.sh"
93+
dst = "install.sh"
94+
95+
[[bundles.files]]
96+
src = "scripts/linux/uninstall.sh"
97+
dst = "uninstall.sh"
98+
99+
[[bundles.files]]
100+
src = "LICENSE"
101+
dst = "LICENSE"

0 commit comments

Comments
 (0)