1
+ import boto3
2
+
1
3
from dataclasses import dataclass
2
4
from functools import cache
3
5
import os
16
18
run_cmd ,
17
19
run_cmd_output ,
18
20
info ,
21
+ warn ,
19
22
set_executable ,
20
23
version ,
21
24
tauri_product_name ,
@@ -160,6 +163,66 @@ def build_cargo_bin(
160
163
return out_path
161
164
162
165
166
+ def fetch_chat_bin (chat_build_bucket_name : str | None , chat_download_role_arn : str | None ) -> pathlib .Path :
167
+ """
168
+ Downloads the chat binary from the provided bucket (using the IAM role to authenticate as).
169
+ If the build bucket or role is not provided, then a dummy script is created and returned instead.
170
+
171
+ The returned path follows the convention: `{binary_name}-{target_triple}`
172
+ """
173
+ info (f"Chat build bucket name: { chat_build_bucket_name } " )
174
+ info (f"IAM Role to assume: { chat_download_role_arn } " )
175
+
176
+ # To prevent requiring a network request and S3 bucket for downloading the chat
177
+ # binary (e.g. for local dev testing), we create a dummy script to bundle instead.
178
+ if not chat_build_bucket_name or not chat_download_role_arn :
179
+ warn ("missing required chat arguments, creating dummy binary" )
180
+ dummy_dir = BUILD_DIR / "dummy_chat"
181
+ dummy_dir .mkdir (exist_ok = True )
182
+ dummy_path = dummy_dir / f"qchat-{ get_target_triple ()} "
183
+ dummy_path .write_text ("#!/usr/bin/env sh\n \n echo dummy chat binary\n " )
184
+ set_executable (dummy_path )
185
+ return dummy_path
186
+
187
+ """Get S3 client with cross-account role credentials"""
188
+ if profile_name := os .getenv ("CHAT_BUILD_BUCKET_ACCESS_AWS_PROFILE" ):
189
+ info (f"Using AWS_PROFILE override { profile_name } for accessing the chat build bucket" )
190
+ session = boto3 .Session (profile_name = profile_name )
191
+ sts = session .client ("sts" )
192
+ else :
193
+ sts = boto3 .client ("sts" )
194
+
195
+ # Assume the cross-account role
196
+ response = sts .assume_role (RoleArn = chat_download_role_arn , RoleSessionName = "QChatBuildBucketS3Access" )
197
+ creds = response ["Credentials" ]
198
+
199
+ # Return S3 client with assumed role credentials
200
+ s3 = boto3 .client (
201
+ "s3" ,
202
+ aws_access_key_id = creds ["AccessKeyId" ],
203
+ aws_secret_access_key = creds ["SecretAccessKey" ],
204
+ aws_session_token = creds ["SessionToken" ],
205
+ )
206
+
207
+ # The path to the download should be:
208
+ # BUILD_BUCKET/prod/latest/{target}/qchat.zip
209
+ target = get_target_triple ()
210
+ chat_bucket_path = f"prod/latest/{ target } /qchat.zip"
211
+ chat_dl_dir = BUILD_DIR / "chat_download"
212
+ chat_dl_dir .mkdir (exist_ok = True )
213
+ chat_dl_path = chat_dl_dir / "qchat.zip"
214
+ info (f"Downloading qchat zip from bucket: { chat_bucket_path } and path: { chat_bucket_path } " )
215
+ s3 .download_file (chat_build_bucket_name , chat_bucket_path , chat_dl_path )
216
+
217
+ # unzip and return the path to the contained binary
218
+ run_cmd (["unzip" , "-o" , chat_dl_path , "-d" , chat_dl_dir ])
219
+
220
+ # Append target triple, as expected by tauri cli.
221
+ chat_path = chat_dl_dir / f"qchat-{ target } "
222
+ (chat_dl_dir / "qchat" ).rename (chat_path )
223
+ return chat_path
224
+
225
+
163
226
@cache
164
227
def gen_manifest () -> str :
165
228
return json .dumps (
@@ -215,6 +278,8 @@ def macos_tauri_config(cli_path: pathlib.Path, chat_path: pathlib.Path, pty_path
215
278
"tauri" : {
216
279
"bundle" : {
217
280
"externalBin" : [
281
+ # Note that tauri bundling expects the target triple to be appended to each binary,
282
+ # but in the config it must be strippled.
218
283
str (cli_path ).removesuffix (f"-{ target } " ),
219
284
str (chat_path ).removesuffix (f"-{ target } " ),
220
285
str (pty_path ).removesuffix (f"-{ target } " ),
@@ -794,6 +859,8 @@ def build(
794
859
output_bucket : str | None = None ,
795
860
signing_bucket : str | None = None ,
796
861
aws_account_id : str | None = None ,
862
+ chat_build_bucket_name : str | None = None ,
863
+ chat_download_role_arn : str | None = None ,
797
864
apple_id_secret : str | None = None ,
798
865
signing_role_name : str | None = None ,
799
866
stage_name : str | None = None ,
@@ -851,6 +918,11 @@ def build(
851
918
for variant in variants :
852
919
info (f"Building variant: { variant .name } " )
853
920
921
+ info ("Fetching" , CHAT_PACKAGE_NAME )
922
+ chat_path = fetch_chat_bin (
923
+ chat_build_bucket_name = chat_build_bucket_name , chat_download_role_arn = chat_download_role_arn
924
+ )
925
+
854
926
info ("Building" , CLI_PACKAGE_NAME )
855
927
cli_path = build_cargo_bin (
856
928
variant = variant ,
@@ -861,16 +933,6 @@ def build(
861
933
targets = targets ,
862
934
)
863
935
864
- info ("Building" , CHAT_PACKAGE_NAME )
865
- chat_path = build_cargo_bin (
866
- variant = variant ,
867
- release = release ,
868
- package = CHAT_PACKAGE_NAME ,
869
- output_name = CHAT_BINARY_NAME ,
870
- features = cargo_features ,
871
- targets = targets ,
872
- )
873
-
874
936
info ("Building" , PTY_PACKAGE_NAME )
875
937
pty_path = build_cargo_bin (
876
938
variant = variant ,
0 commit comments