1313from briefcase .config import AppConfig
1414from briefcase .exceptions import (
1515 BriefcaseCommandError ,
16+ InvalidStubBinary ,
1617 InvalidSupportPackage ,
1718 MissingAppSources ,
1819 MissingNetworkResourceError ,
@@ -86,31 +87,35 @@ class CreateCommand(BaseCommand):
8687 hidden_app_properties = {"permission" }
8788
8889 @property
89- def app_template_url (self ):
90+ def app_template_url (self ) -> str :
9091 """The URL for a cookiecutter repository to use when creating apps."""
9192 return f"https://github.com/beeware/briefcase-{ self .platform } -{ self .output_format } -template.git"
9293
93- def support_package_filename (self , support_revision ) :
94+ def support_package_filename (self , support_revision : str ) -> str :
9495 """The query arguments to use in a support package query request."""
9596 return f"Python-{ self .python_version_tag } -{ self .platform } -support.b{ support_revision } .tar.gz"
9697
97- def support_package_url (self , support_revision ) :
98+ def support_package_url (self , support_revision : str ) -> str :
9899 """The URL of the support package to use for apps of this type."""
99100 return (
100- f"https://briefcase-support.s3.amazonaws.com/python/{ self .python_version_tag } /{ self .platform } /"
101- + self .support_package_filename (support_revision )
101+ "https://briefcase-support.s3.amazonaws.com/python/"
102+ f"{ self .python_version_tag } /"
103+ f"{ self .platform } /"
104+ f"{ self .support_package_filename (support_revision )} "
102105 )
103106
104- def stub_binary_filename (self , support_revision , is_console_app ) :
107+ def stub_binary_filename (self , support_revision : str , is_console_app : bool ) -> str :
105108 """The filename for the stub binary."""
106109 stub_type = "Console" if is_console_app else "GUI"
107110 return f"{ stub_type } -Stub-{ self .python_version_tag } -b{ support_revision } .zip"
108111
109- def stub_binary_url (self , support_revision , is_console_app ) :
112+ def stub_binary_url (self , support_revision : str , is_console_app : bool ) -> str :
110113 """The URL of the stub binary to use for apps of this type."""
111114 return (
112- f"https://briefcase-support.s3.amazonaws.com/python/{ self .python_version_tag } /{ self .platform } /"
113- + self .stub_binary_filename (support_revision , is_console_app )
115+ "https://briefcase-support.s3.amazonaws.com/python/"
116+ f"{ self .python_version_tag } /"
117+ f"{ self .platform } /"
118+ f"{ self .stub_binary_filename (support_revision , is_console_app )} "
114119 )
115120
116121 def icon_targets (self , app : AppConfig ):
@@ -260,22 +265,13 @@ def _unpack_support_package(self, support_file_path, support_path):
260265 :param support_file_path: The path to the support file to be unpacked.
261266 :param support_path: The path where support files should be unpacked.
262267 """
263- # Additional protections for unpacking tar files were introduced in Python 3.12.
264- # This enables the behavior that will be the default in Python 3.14.
265- # However, the protections can only be enabled for tar files...not zip files.
266- is_zip = support_file_path .name .endswith ("zip" )
267- if sys .version_info >= (3 , 12 ) and not is_zip : # pragma: no-cover-if-lt-py312
268- tarfile_kwargs = {"filter" : "data" }
269- else :
270- tarfile_kwargs = {}
271-
272268 try :
273269 with self .input .wait_bar ("Unpacking support package..." ):
274270 support_path .mkdir (parents = True , exist_ok = True )
275271 self .tools .shutil .unpack_archive (
276272 support_file_path ,
277273 extract_dir = support_path ,
278- ** tarfile_kwargs ,
274+ ** self . tools . unpack_archive_kwargs ( support_file_path ) ,
279275 )
280276 except (shutil .ReadError , EOFError ) as e :
281277 raise InvalidSupportPackage (support_file_path ) from e
@@ -401,13 +397,8 @@ def cleanup_stub_binary(self, app: AppConfig):
401397 :param app: The config object for the app
402398 """
403399 with self .input .wait_bar ("Removing existing stub binary..." ):
404- binary_executable_path = self .binary_executable_path (app )
405- if binary_executable_path .exists ():
406- binary_executable_path .unlink ()
407-
408- unbuilt_executable_path = self .unbuilt_executable_path (app )
409- if unbuilt_executable_path .exists ():
410- unbuilt_executable_path .unlink ()
400+ self .binary_executable_path (app ).unlink (missing_ok = True )
401+ self .unbuilt_executable_path (app ).unlink (missing_ok = True )
411402
412403 def install_stub_binary (self , app : AppConfig ):
413404 """Install the application stub binary into the "unbuilt" location.
@@ -420,19 +411,41 @@ def install_stub_binary(self, app: AppConfig):
420411 with self .input .wait_bar ("Installing stub binary..." ):
421412 # Ensure the folder for the stub binary exists
422413 unbuilt_executable_path .parent .mkdir (exist_ok = True , parents = True )
423- # Install the stub binary into the unbuilt location. Allow for both raw
424- # and compressed artefacts.
425- if stub_binary_path .suffix in {".zip" , ".tar.gz" , ".tgz" }:
426- self .tools .shutil .unpack_archive (
427- stub_binary_path ,
428- extract_dir = unbuilt_executable_path .parent ,
429- )
414+
415+ # Determine if stub binary is a packed archive
416+ supported_archive_extensions = {
417+ ext for format in shutil .get_unpack_formats () for ext in format [1 ]
418+ }
419+ stub_path_exts = {
420+ # captures extensions like .tar.gz, .tar.bz2, etc.
421+ "" .join (stub_binary_path .suffixes [- 2 :]),
422+ # as well as .tar, .zip, etc.
423+ stub_binary_path .suffix ,
424+ }
425+ is_archive = not stub_path_exts .isdisjoint (supported_archive_extensions )
426+
427+ # Install the stub binary into the unbuilt location.
428+ # Allow for both raw and compressed artefacts.
429+ try :
430+ if is_archive :
431+ self .tools .shutil .unpack_archive (
432+ stub_binary_path ,
433+ extract_dir = unbuilt_executable_path .parent ,
434+ ** self .tools .unpack_archive_kwargs (stub_binary_path ),
435+ )
436+ elif stub_binary_path .is_file ():
437+ self .tools .shutil .copyfile (
438+ stub_binary_path , unbuilt_executable_path
439+ )
440+ else :
441+ raise InvalidStubBinary (stub_binary_path )
442+ except (shutil .ReadError , EOFError , OSError ) as e :
443+ raise InvalidStubBinary (stub_binary_path ) from e
430444 else :
431- self .tools .shutil .copyfile (stub_binary_path , unbuilt_executable_path )
432- # Ensure the binary is executable
433- self .tools .os .chmod (unbuilt_executable_path , 0o755 )
445+ # Ensure the binary is executable
446+ self .tools .os .chmod (unbuilt_executable_path , 0o755 )
434447
435- def _download_stub_binary (self , app : AppConfig ):
448+ def _download_stub_binary (self , app : AppConfig ) -> Path :
436449 try :
437450 # Work out if the app defines a custom override for
438451 # the support package URL.
0 commit comments