44"""
55
66import os
7+ import sys
78from pathlib import Path
8- from typing import List , Optional
9+ from typing import List , Optional , Tuple
910from context import BuildContext
1011from utils import (
1112 log_info ,
1415 log_warning ,
1516 IS_WINDOWS ,
1617 IS_MACOS ,
18+ IS_LINUX ,
1719 join_paths ,
1820)
1921
2931# Service account file name
3032SERVICE_ACCOUNT_FILE = "gclient.json"
3133
34+ # GCS bucket configuration
35+ GCS_BUCKET_NAME = "nxtscape"
3236
33- def upload_to_gcs (ctx : BuildContext , file_paths : List [Path ]) -> tuple [bool , List [str ]]:
37+
38+ def _get_platform_dir (platform_override : Optional [str ] = None ) -> str :
39+ """Get platform directory name for GCS path"""
40+ if platform_override :
41+ return platform_override
42+
43+ if IS_WINDOWS :
44+ return "win"
45+ elif IS_MACOS :
46+ return "macos"
47+ else :
48+ return "linux"
49+
50+
51+ def upload_to_gcs (
52+ ctx : BuildContext ,
53+ file_paths : List [Path ],
54+ platform_override : Optional [str ] = None
55+ ) -> Tuple [bool , List [str ]]:
3456 """Upload build artifacts to Google Cloud Storage
35- Returns: (success, list of GCS URIs)"""
57+
58+ Args:
59+ ctx: BuildContext with root_dir and nxtscape_version
60+ file_paths: List of file paths to upload
61+ platform_override: Optional platform override (macos/linux/win)
62+
63+ Returns:
64+ (success, list of GCS URIs)
65+ """
3666 if not GCS_AVAILABLE :
3767 log_warning ("google-cloud-storage not installed. Skipping GCS upload." )
3868 log_info ("Install with: pip install google-cloud-storage" )
@@ -43,18 +73,12 @@ def upload_to_gcs(ctx: BuildContext, file_paths: List[Path]) -> tuple[bool, List
4373 return True , []
4474
4575 # Determine platform subdirectory
46- if IS_WINDOWS :
47- platform_dir = "win"
48- elif IS_MACOS :
49- platform_dir = "macos"
50- else :
51- platform_dir = "linux"
76+ platform_dir = _get_platform_dir (platform_override )
5277
5378 # Build GCS path: gs://nxtscape/resources/<version>/<platform>/
54- bucket_name = "nxtscape"
5579 gcs_prefix = f"resources/{ ctx .nxtscape_version } /{ platform_dir } "
5680
57- log_info (f"\n ☁️ Uploading artifacts to gs://{ bucket_name } /{ gcs_prefix } /" )
81+ log_info (f"\n ☁️ Uploading artifacts to gs://{ GCS_BUCKET_NAME } /{ gcs_prefix } /" )
5882
5983 # Check for service account file
6084 service_account_path = join_paths (ctx .root_dir , SERVICE_ACCOUNT_FILE )
@@ -71,7 +95,7 @@ def upload_to_gcs(ctx: BuildContext, file_paths: List[Path]) -> tuple[bool, List
7195 str (service_account_path )
7296 )
7397 client = storage .Client (credentials = credentials )
74- bucket = client .bucket (bucket_name )
98+ bucket = client .bucket (GCS_BUCKET_NAME )
7599
76100 uploaded_files = []
77101 gcs_uris = []
@@ -93,8 +117,8 @@ def upload_to_gcs(ctx: BuildContext, file_paths: List[Path]) -> tuple[bool, List
93117 # Note: With uniform bucket-level access, objects inherit bucket's IAM policies
94118 # No need to set individual object ACLs
95119
96- public_url = f"https://storage.googleapis.com/{ bucket_name } /{ blob_name } "
97- gcs_uri = f"gs://{ bucket_name } /{ blob_name } "
120+ public_url = f"https://storage.googleapis.com/{ GCS_BUCKET_NAME } /{ blob_name } "
121+ gcs_uri = f"gs://{ GCS_BUCKET_NAME } /{ blob_name } "
98122 uploaded_files .append (public_url )
99123 gcs_uris .append (gcs_uri )
100124 log_success (f"✓ Uploaded: { public_url } " )
@@ -194,3 +218,149 @@ def download_from_gcs(
194218 except Exception as e :
195219 log_error (f"Failed to download from GCS: { e } " )
196220 return False
221+
222+
223+ def _detect_artifacts (dist_path : Path , platform_override : Optional [str ] = None ) -> List [Path ]:
224+ """Detect artifacts in a dist directory based on platform
225+
226+ Args:
227+ dist_path: Path to the dist/<version> directory
228+ platform_override: Optional platform override (macos/linux/win)
229+
230+ Returns:
231+ List of artifact file paths found
232+ """
233+ artifacts = []
234+
235+ # Determine which file types to look for
236+ if platform_override :
237+ if platform_override == "macos" :
238+ patterns = ["*.dmg" ]
239+ elif platform_override == "win" :
240+ patterns = ["*.exe" , "*.zip" ]
241+ elif platform_override == "linux" :
242+ patterns = ["*.AppImage" ]
243+ else :
244+ log_error (f"Invalid platform: { platform_override } . Must be macos/linux/win" )
245+ return []
246+ else :
247+ # Auto-detect based on current platform
248+ if IS_MACOS :
249+ patterns = ["*.dmg" ]
250+ elif IS_WINDOWS :
251+ patterns = ["*.exe" , "*.zip" ]
252+ else : # Linux
253+ patterns = ["*.AppImage" ]
254+
255+ # Find all matching files
256+ for pattern in patterns :
257+ artifacts .extend (dist_path .glob (pattern ))
258+
259+ return sorted (artifacts )
260+
261+
262+ def handle_upload_dist (
263+ dist_path : Path ,
264+ root_dir : Path ,
265+ platform_override : Optional [str ] = None
266+ ) -> bool :
267+ """Upload pre-built artifacts from a dist directory to GCS
268+
269+ This is the main entry point for manual uploads of already-built artifacts.
270+
271+ Args:
272+ dist_path: Path to dist/<version> directory containing artifacts
273+ root_dir: Root directory of the project (for finding gclient.json)
274+ platform_override: Optional platform override (macos/linux/win)
275+
276+ Returns:
277+ True if successful, False otherwise
278+
279+ Example:
280+ handle_upload_dist(Path("dist/61"), Path("."), platform_override="macos")
281+ """
282+ log_info ("=" * 60 )
283+ log_info ("📤 Manual GCS Upload" )
284+ log_info ("=" * 60 )
285+
286+ # 1. Validate dist_path exists
287+ if not dist_path .exists ():
288+ log_error (f"Distribution directory does not exist: { dist_path } " )
289+ return False
290+
291+ if not dist_path .is_dir ():
292+ log_error (f"Path is not a directory: { dist_path } " )
293+ return False
294+
295+ # 2. Extract version from path (assume dist/<version> structure)
296+ version = dist_path .name
297+ log_info (f"📦 Version detected: { version } " )
298+
299+ # 3. Determine platform
300+ platform_dir = _get_platform_dir (platform_override )
301+ if platform_override :
302+ log_info (f"🖥️ Platform (override): { platform_dir } " )
303+ else :
304+ log_info (f"🖥️ Platform (auto-detected): { platform_dir } " )
305+
306+ # 4. Scan for artifacts
307+ log_info (f"\n 🔍 Scanning for artifacts in: { dist_path } " )
308+ artifacts = _detect_artifacts (dist_path , platform_override )
309+
310+ if not artifacts :
311+ log_warning ("No artifacts found to upload" )
312+ log_info ("\n Expected file types by platform:" )
313+ log_info (" - macOS: *.dmg" )
314+ log_info (" - Windows: *.exe, *.zip" )
315+ log_info (" - Linux: *.AppImage" )
316+ return False
317+
318+ # 5. Preview files
319+ log_info (f"\n 📋 Found { len (artifacts )} artifact(s):" )
320+ total_size = 0
321+ for artifact in artifacts :
322+ size_mb = artifact .stat ().st_size / (1024 * 1024 )
323+ total_size += size_mb
324+ log_info (f" - { artifact .name } ({ size_mb :.2f} MB)" )
325+
326+ log_info (f"\n Total size: { total_size :.2f} MB" )
327+ log_info (f"Upload destination: gs://{ GCS_BUCKET_NAME } /resources/{ version } /{ platform_dir } /" )
328+
329+ # 6. Create minimal BuildContext for upload
330+ # BuildContext will try to load chromium_src, but we'll provide a dummy one
331+ # since we don't need it for uploads
332+ try :
333+ ctx = BuildContext (
334+ root_dir = root_dir ,
335+ chromium_src = Path ("/dev/null" ), # Dummy path, won't be used
336+ architecture = "" , # Not needed for upload
337+ build_type = "release" , # Not needed for upload
338+ )
339+ # Override the version with what we detected
340+ ctx .nxtscape_version = version
341+ except Exception as e :
342+ # If BuildContext fails, we can still upload with minimal info
343+ log_warning (f"Could not create full BuildContext: { e } " )
344+ log_info ("Creating minimal context for upload..." )
345+
346+ # Create a simple object with just what we need
347+ class MinimalContext :
348+ def __init__ (self , root_dir : Path , version : str ):
349+ self .root_dir = root_dir
350+ self .nxtscape_version = version
351+
352+ ctx = MinimalContext (root_dir , version )
353+
354+ # 7. Upload using existing upload_to_gcs function
355+ success , gcs_uris = upload_to_gcs (ctx , artifacts , platform_override = platform_override )
356+
357+ if success :
358+ log_success ("\n ✅ Upload completed successfully!" )
359+ if gcs_uris :
360+ log_info ("\n Uploaded URIs:" )
361+ for uri in gcs_uris :
362+ log_info (f" { uri } " )
363+ return True
364+ else :
365+ log_error ("\n ❌ Upload failed" )
366+ return False
0 commit comments