Skip to content

Commit 2a34c90

Browse files
committed
feat: add build script for building qchat
1 parent 25cdf07 commit 2a34c90

File tree

2 files changed

+277
-0
lines changed

2 files changed

+277
-0
lines changed

build-scripts/qchatbuild.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import pathlib
2+
import os
3+
import shutil
4+
from typing import Mapping, Sequence
5+
from build import generate_sha
6+
from const import CHAT_BINARY_NAME, CHAT_PACKAGE_NAME, LINUX_ARCHIVE_NAME
7+
from signing import (
8+
CdSigningData,
9+
CdSigningType,
10+
load_gpg_signer,
11+
rebundle_dmg,
12+
cd_sign_file,
13+
apple_notarize_file,
14+
)
15+
from util import info, isDarwin, run_cmd
16+
from rust import cargo_cmd_name, rust_env, rust_targets
17+
18+
BUILD_DIR_RELATIVE = pathlib.Path(os.environ.get("BUILD_DIR") or "build")
19+
BUILD_DIR = BUILD_DIR_RELATIVE.absolute()
20+
21+
22+
def run_cargo_tests():
23+
args = [cargo_cmd_name()]
24+
args.extend(["test", "--locked", "--package", CHAT_PACKAGE_NAME])
25+
run_cmd(
26+
args,
27+
env={
28+
**os.environ,
29+
**rust_env(release=False),
30+
},
31+
)
32+
33+
34+
def run_clippy():
35+
args = [cargo_cmd_name(), "clippy", "--locked", "--package", CHAT_PACKAGE_NAME]
36+
run_cmd(
37+
args,
38+
env={
39+
**os.environ,
40+
**rust_env(release=False),
41+
},
42+
)
43+
44+
45+
def build_chat_bin(
46+
release: bool,
47+
output_name: str | None = None,
48+
targets: Sequence[str] = [],
49+
):
50+
package = CHAT_PACKAGE_NAME
51+
52+
args = [cargo_cmd_name(), "build", "--locked", "--package", package]
53+
54+
if release:
55+
args.append("--release")
56+
57+
if release:
58+
target_subdir = "release"
59+
else:
60+
target_subdir = "debug"
61+
62+
# create "universal" binary for macos
63+
if isDarwin():
64+
out_path = BUILD_DIR / f"{output_name or package}-universal-apple-darwin"
65+
args = [
66+
"lipo",
67+
"-create",
68+
"-output",
69+
out_path,
70+
]
71+
for target in targets:
72+
args.append(pathlib.Path("target") / target / target_subdir / package)
73+
run_cmd(args)
74+
return out_path
75+
else:
76+
# linux does not cross compile arch
77+
target = targets[0]
78+
target_path = pathlib.Path("target") / target / target_subdir / package
79+
out_path = BUILD_DIR / "bin" / f"{(output_name or package)}-{target}"
80+
out_path.parent.mkdir(parents=True, exist_ok=True)
81+
shutil.copy2(target_path, out_path)
82+
return out_path
83+
84+
85+
def build_macos(chat_path: pathlib.Path):
86+
pass
87+
88+
89+
def build_linux(chat_path: pathlib.Path):
90+
"""
91+
Creates tar.gz, tar.xz, tar.zst, and zip archives under `BUILD_DIR`.
92+
93+
Each archive has the following structure:
94+
- archive/qchat
95+
"""
96+
archive_name = CHAT_BINARY_NAME
97+
98+
archive_path = pathlib.Path(archive_name)
99+
archive_path.mkdir(parents=True, exist_ok=True)
100+
shutil.copy2(chat_path, archive_path / CHAT_BINARY_NAME)
101+
102+
signer = load_gpg_signer()
103+
104+
info(f"Building {archive_name}.tar.gz")
105+
tar_gz_path = BUILD_DIR / f"{archive_name}.tar.gz"
106+
run_cmd(["tar", "-czf", tar_gz_path, archive_path])
107+
generate_sha(tar_gz_path)
108+
if signer:
109+
signer.sign_file(tar_gz_path)
110+
111+
info(f"Building {archive_name}.tar.xz")
112+
tar_xz_path = BUILD_DIR / f"{archive_name}.tar.xz"
113+
run_cmd(["tar", "-cJf", tar_xz_path, archive_path])
114+
generate_sha(tar_xz_path)
115+
if signer:
116+
signer.sign_file(tar_xz_path)
117+
118+
info(f"Building {archive_name}.tar.zst")
119+
tar_zst_path = BUILD_DIR / f"{archive_name}.tar.zst"
120+
run_cmd(["tar", "-I", "zstd", "-cf", tar_zst_path, archive_path], {"ZSTD_CLEVEL": "19"})
121+
generate_sha(tar_zst_path)
122+
if signer:
123+
signer.sign_file(tar_zst_path)
124+
125+
info(f"Building {archive_name}.zip")
126+
zip_path = BUILD_DIR / f"{archive_name}.zip"
127+
run_cmd(["zip", "-r", zip_path, archive_path])
128+
generate_sha(zip_path)
129+
if signer:
130+
signer.sign_file(zip_path)
131+
132+
# clean up
133+
shutil.rmtree(archive_path)
134+
if signer:
135+
signer.clean()
136+
137+
138+
def build(
139+
release: bool,
140+
output_bucket: str | None = None,
141+
signing_bucket: str | None = None,
142+
aws_account_id: str | None = None,
143+
apple_id_secret: str | None = None,
144+
signing_role_name: str | None = None,
145+
stage_name: str | None = None,
146+
run_lints: bool = True,
147+
run_test: bool = True,
148+
):
149+
if signing_bucket and aws_account_id and apple_id_secret and signing_role_name:
150+
signing_data = CdSigningData(
151+
bucket_name=signing_bucket,
152+
aws_account_id=aws_account_id,
153+
notarizing_secret_id=apple_id_secret,
154+
signing_role_name=signing_role_name,
155+
)
156+
else:
157+
signing_data = None
158+
159+
match stage_name:
160+
case "prod" | None:
161+
info("Building for prod")
162+
case "gamma":
163+
info("Building for gamma")
164+
case _:
165+
raise ValueError(f"Unknown stage name: {stage_name}")
166+
167+
targets = rust_targets()
168+
169+
info(f"Release: {release}")
170+
info(f"Targets: {targets}")
171+
info(f"Signing app: {signing_data is not None}")
172+
173+
if run_test:
174+
info("Running cargo tests")
175+
run_cargo_tests()
176+
177+
if run_lints:
178+
info("Running cargo clippy")
179+
run_clippy()
180+
181+
info("Building", CHAT_PACKAGE_NAME)
182+
chat_path = build_chat_bin(
183+
release=release,
184+
output_name=CHAT_BINARY_NAME,
185+
targets=targets,
186+
)
187+
188+
pass

build-scripts/qchatmain.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import argparse
2+
import os
3+
from pathlib import Path
4+
import shutil
5+
import subprocess
6+
from qchatbuild import build
7+
from const import CLI_BINARY_NAME, CLI_PACKAGE_NAME, PTY_BINARY_NAME
8+
from doc import run_doc
9+
from rust import cargo_cmd_name, rust_env
10+
from test import all_tests
11+
from util import Variant, get_variants
12+
13+
14+
class StoreIfNotEmptyAction(argparse.Action):
15+
def __call__(self, parser, namespace, values, option_string=None):
16+
if values and len(values) > 0:
17+
setattr(namespace, self.dest, values)
18+
19+
20+
parser = argparse.ArgumentParser(
21+
prog="build",
22+
description="Builds the qchat binary",
23+
)
24+
subparsers = parser.add_subparsers(help="sub-command help", dest="subparser", required=True)
25+
26+
build_subparser = subparsers.add_parser(name="build")
27+
build_subparser.add_argument(
28+
"--output-bucket",
29+
action=StoreIfNotEmptyAction,
30+
help="The name of bucket to store the build artifacts",
31+
)
32+
build_subparser.add_argument(
33+
"--signing-bucket",
34+
action=StoreIfNotEmptyAction,
35+
help="The name of bucket to store the build artifacts",
36+
)
37+
build_subparser.add_argument(
38+
"--aws-account-id",
39+
action=StoreIfNotEmptyAction,
40+
help="The AWS account ID",
41+
)
42+
build_subparser.add_argument(
43+
"--apple-id-secret",
44+
action=StoreIfNotEmptyAction,
45+
help="The Apple ID secret",
46+
)
47+
build_subparser.add_argument(
48+
"--signing-role-name",
49+
action=StoreIfNotEmptyAction,
50+
help="The name of the signing role",
51+
)
52+
build_subparser.add_argument(
53+
"--stage-name",
54+
action=StoreIfNotEmptyAction,
55+
help="The name of the stage",
56+
)
57+
build_subparser.add_argument(
58+
"--not-release",
59+
action="store_true",
60+
help="Build a non-release version",
61+
)
62+
build_subparser.add_argument(
63+
"--skip-tests",
64+
action="store_true",
65+
help="Skip running npm and rust tests",
66+
)
67+
build_subparser.add_argument(
68+
"--skip-lints",
69+
action="store_true",
70+
help="Skip running lints",
71+
)
72+
73+
args = parser.parse_args()
74+
75+
match args.subparser:
76+
case "build":
77+
build(
78+
release=not args.not_release,
79+
output_bucket=args.output_bucket,
80+
signing_bucket=args.signing_bucket,
81+
aws_account_id=args.aws_account_id,
82+
apple_id_secret=args.apple_id_secret,
83+
signing_role_name=args.signing_role_name,
84+
stage_name=args.stage_name,
85+
run_lints=not args.skip_lints,
86+
run_test=not args.skip_tests,
87+
)
88+
case _:
89+
raise ValueError(f"Unsupported subparser {args.subparser}")

0 commit comments

Comments
 (0)