1+ import boto3
2+
13from dataclasses import dataclass
24from functools import cache
35import os
1618 run_cmd ,
1719 run_cmd_output ,
1820 info ,
21+ warn ,
1922 set_executable ,
2023 version ,
2124 tauri_product_name ,
@@ -160,6 +163,66 @@ def build_cargo_bin(
160163 return out_path
161164
162165
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+
163226@cache
164227def gen_manifest () -> str :
165228 return json .dumps (
@@ -215,6 +278,8 @@ def macos_tauri_config(cli_path: pathlib.Path, chat_path: pathlib.Path, pty_path
215278 "tauri" : {
216279 "bundle" : {
217280 "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.
218283 str (cli_path ).removesuffix (f"-{ target } " ),
219284 str (chat_path ).removesuffix (f"-{ target } " ),
220285 str (pty_path ).removesuffix (f"-{ target } " ),
@@ -794,6 +859,8 @@ def build(
794859 output_bucket : str | None = None ,
795860 signing_bucket : str | None = None ,
796861 aws_account_id : str | None = None ,
862+ chat_build_bucket_name : str | None = None ,
863+ chat_download_role_arn : str | None = None ,
797864 apple_id_secret : str | None = None ,
798865 signing_role_name : str | None = None ,
799866 stage_name : str | None = None ,
@@ -851,6 +918,11 @@ def build(
851918 for variant in variants :
852919 info (f"Building variant: { variant .name } " )
853920
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+
854926 info ("Building" , CLI_PACKAGE_NAME )
855927 cli_path = build_cargo_bin (
856928 variant = variant ,
@@ -861,16 +933,6 @@ def build(
861933 targets = targets ,
862934 )
863935
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-
874936 info ("Building" , PTY_PACKAGE_NAME )
875937 pty_path = build_cargo_bin (
876938 variant = variant ,
0 commit comments