def install(self): """ Download and install WiX. """ try: wix_zip_path = self.command.download_url( url=WIX_DOWNLOAD_URL, download_path=self.command.tools_path, ) except requests_exceptions.ConnectionError: raise NetworkFailure("download WiX") try: print("Installing WiX...") # TODO: Py3.6 compatibility; os.fsdecode not required in Py3.7 self.command.shutil.unpack_archive(os.fsdecode(wix_zip_path), extract_dir=os.fsdecode( self.wix_home)) except (shutil.ReadError, EOFError): raise BriefcaseCommandError(""" Unable to unpack WiX ZIP file. The download may have been interrupted or corrupted. Delete {wix_zip_path} and run briefcase again.""".format( wix_zip_path=wix_zip_path)) # Zip file no longer needed once unpacked. wix_zip_path.unlink()
def generate_app_template(self, app: BaseConfig): """ Create an application bundle. :param app: The config object for the app """ # If the app config doesn't explicitly define a template, # use a default template. if app.template is None: app.template = self.app_template_url print("Using app template: {app_template}".format( app_template=app.template, )) # Make sure we have an updated cookiecutter template, # checked out to the right branch cached_template = self.update_cookiecutter_cache( template=app.template, branch=self.python_version_tag ) # Construct a template context from the app configuration. extra_context = app.__dict__.copy() # Augment with some extra fields. extra_context.update({ # Transformations of explicit properties into useful forms 'module_name': app.module_name, # Properties that are a function of the execution 'year': date.today().strftime('%Y'), 'month': date.today().strftime('%B'), }) # Add in any extra template context required by the output format. extra_context.update(self.output_format_template_context(app)) try: # Create the platform directory (if it doesn't already exist) output_path = self.bundle_path(app).parent output_path.mkdir(parents=True, exist_ok=True) # Unroll the template self.cookiecutter( str(cached_template), no_input=True, output_dir=str(output_path), checkout=self.python_version_tag, extra_context=extra_context ) except subprocess.CalledProcessError: # Computer is offline # status code == 128 - certificate validation error. raise NetworkFailure("clone template repository") except cookiecutter_exceptions.RepositoryNotFound: # Either the template path is invalid, # or it isn't a cookiecutter template (i.e., no cookiecutter.json) raise InvalidTemplateRepository(app.template) except cookiecutter_exceptions.RepositoryCloneFailed: # Branch does not exist for python version raise TemplateUnsupportedVersion(self.python_version_tag)
def install_app_support_package(self, app: BaseConfig): """ Install the application support packge. :param app: The config object for the app """ try: # Work out if the app defines a custom override for # the support package URL. try: support_package_url = app.support_package print("Using custom support package {support_package_url}". format(support_package_url=support_package_url)) except AttributeError: support_package_url = self.support_package_url print("Using support package {support_package_url}".format( support_package_url=support_package_url)) if support_package_url.startswith( 'https://') or support_package_url.startswith('http://'): try: print( "... pinned to revision {app.support_revision}".format( app=app)) # If a revision has been specified, add the revision # as an query argument in the support package URL. # This is a lot more painful than "add arg to query" should # be because (a) url splits aren't appendable, and # (b) Python 3.5 doesn't guarantee dictionary order. url_parts = list(urlsplit(support_package_url)) query = [] for key, value in parse_qsl(url_parts[3]): query.append((key, value)) query.append(('revision', app.support_revision)) url_parts[3] = urlencode(query) support_package_url = urlunsplit(url_parts) except AttributeError: # No support revision specified. print("... using most recent revision") # Download the support file, caching the result # in the user's briefcase support cache directory. support_filename = self.download_url( url=support_package_url, download_path=self.dot_briefcase_path / 'support') else: support_filename = Path(support_package_url) except requests_exceptions.ConnectionError: raise NetworkFailure('downloading support package') try: print("Unpacking support package...") support_path = self.support_path(app) support_path.mkdir(parents=True, exist_ok=True) self.shutil.unpack_archive(str(support_filename), extract_dir=str(support_path)) except (shutil.ReadError, EOFError): print() raise InvalidSupportPackage(support_package_url)
def install(self): """ Download and install a JDK. """ try: jdk_zip_path = self.command.download_url( url=self.adoptOpenJDK_download_url, download_path=self.command.tools_path, ) except requests_exceptions.ConnectionError: raise NetworkFailure("download Java 8 JDK") try: print("Installing AdoptOpenJDK...") # TODO: Py3.6 compatibility; os.fsdecode not required in Py3.7 self.command.shutil.unpack_archive(os.fsdecode(jdk_zip_path), extract_dir=os.fsdecode( self.command.tools_path)) except (shutil.ReadError, EOFError): raise BriefcaseCommandError("""\ Unable to unpack AdoptOpenJDK ZIP file. The download may have been interrupted or corrupted. Delete {jdk_zip_path} and run briefcase again.""".format( jdk_zip_path=jdk_zip_path)) jdk_zip_path.unlink() # Zip file no longer needed once unpacked. # The tarball will unpack into ~.briefcase/tools/jdk8u242-b08 # (or whatever name matches the current release). # We turn this into ~.briefcase/tools/java so we have a consistent name. java_unpack_path = self.command.tools_path / "jdk{self.release}-{self.build}".format( self=self) java_unpack_path.rename(self.command.tools_path / "java")
def install(self): """Download and install linuxdeploy.""" try: linuxdeploy_appimage_path = self.command.download_url( url=self.linuxdeploy_download_url, download_path=self.command.tools_path) self.command.os.chmod(linuxdeploy_appimage_path, 0o755) self.patch_elf_header() except requests_exceptions.ConnectionError as e: raise NetworkFailure("downloading linuxdeploy AppImage") from e
def new_app(self, template: Optional[str] = None, **options): """ Ask questions to generate a new application, and generate a stub project from the briefcase-template. """ if template is None: template = 'https://github.com/Satireven/milkui-template' if self.input.enabled: print() print("Let's build a new MilkUI app!") print() context = self.build_app_context() print() print("Generating a new application '{formal_name}'".format( **context )) cached_template = self.update_cookiecutter_cache( template=template, branch='v0.3' ) # Make extra sure we won't clobber an existing application. if (self.base_path / context['app_name']).exists(): print() raise BriefcaseCommandError( "A directory named '{app_name}' already exists.".format( **context ) ) try: # Unroll the new app template self.cookiecutter( str(cached_template), no_input=True, output_dir=str(self.base_path), checkout="v0.3", extra_context=context ) except subprocess.CalledProcessError: # Computer is offline # status code == 128 - certificate validation error. raise NetworkFailure("clone template repository") print(""" Application '{formal_name}' has been generated. To run your application, type: cd {app_name} milkui dev """.format(**context))
def verify_tools(self): super().verify_tools() try: print() print("Ensure we have the linuxdeploy AppImage...") self.linuxdeploy_appimage = self.download_url( url=self.linuxdeploy_download_url, download_path=self.dot_briefcase_path / 'tools') self.os.chmod(str(self.linuxdeploy_appimage), 0o755) except requests_exceptions.ConnectionError: raise NetworkFailure('downloading linuxdeploy AppImage')
def install(self): """ Download and install linuxdeploy. """ try: linuxdeploy_appimage_path = self.command.download_url( url=self.linuxdeploy_download_url, download_path=self.command.tools_path ) self.command.os.chmod(str(linuxdeploy_appimage_path), 0o755) except requests_exceptions.ConnectionError: raise NetworkFailure('downloading linuxdeploy AppImage')
def new_app(self, template: Optional[str] = None, **options): """Ask questions to generate a new application, and generate a stub project from the briefcase-template.""" if template is None: template = "https://github.com/beeware/briefcase-template" self.input.prompt() self.input.prompt("Let's build a new Briefcase app!") self.input.prompt() context = self.build_app_context() self.logger.info() self.logger.info( f"Generating a new application '{context['formal_name']}'") cached_template = self.update_cookiecutter_cache(template=template, branch="v0.3") # Make extra sure we won't clobber an existing application. if (self.base_path / context["app_name"]).exists(): raise BriefcaseCommandError( f"A directory named '{context['app_name']}' already exists.") try: # Unroll the new app template self.cookiecutter( str(cached_template), no_input=True, output_dir=os.fsdecode(self.base_path), checkout="v0.3", extra_context=context, ) except subprocess.CalledProcessError as e: # Computer is offline # status code == 128 - certificate validation error. raise NetworkFailure("clone template repository") from e except cookiecutter_exceptions.RepositoryNotFound as e: # Either the template path is invalid, # or it isn't a cookiecutter template (i.e., no cookiecutter.json) raise InvalidTemplateRepository(template) from e self.logger.info(f""" Application '{context['formal_name']}' has been generated. To run your application, type: cd {context['app_name']} briefcase dev """)
def install_app_support_package(self, app: BaseConfig): """ Install the application support packge. :param app: The config object for the app """ try: # Work out if the app defines a custom override for # the support package URL. try: support_package_url = app.support_package print("Using custom support package {support_package_url}".format( support_package_url=support_package_url )) except AttributeError: support_package_url = self.support_package_url print("Using support package {support_package_url}".format( support_package_url=support_package_url )) if support_package_url.startswith('https://') or support_package_url.startswith('http://'): # Download the support file, caching the result # in the user's briefcase support cache directory. support_filename = self.download_url( url=support_package_url, download_path=Path.home() / '.briefcase' / 'support' ) else: support_filename = support_package_url except requests_exceptions.ConnectionError: raise NetworkFailure('downloading support package') try: print("Unpacking support package...") support_path = self.support_path(app) support_path.mkdir(parents=True, exist_ok=True) self.shutil.unpack_archive( str(support_filename), extract_dir=str(support_path) ) except shutil.ReadError: raise InvalidSupportPackage(support_filename.name)
def verify_sdk(self): """ Install the Android SDK if needed. """ tools_path = self.sdk_path / "tools" / "bin" sdkmanager_exe = "sdkmanager.exe" if self.host_os == "Windows" else "sdkmanager" # This method marks some files as executable, so `tools_ok` checks for # that as well. On Windows, all generated files are executable. tools_ok = (tools_path.exists() and all([ self.os.access(str(tool), self.os.X_OK) for tool in tools_path.glob("*") ]) and (tools_path / sdkmanager_exe).exists()) if tools_ok: return print("Setting up Android SDK...") try: sdk_zip_path = self.download_url( url=self.sdk_url, download_path=self.dot_briefcase_path / "tools", ) except requests_exceptions.ConnectionError: raise NetworkFailure("download Android SDK") try: with ZipFile(str(sdk_zip_path)) as sdk_zip: sdk_zip.extractall(path=str(self.sdk_path)) except BadZipFile: raise BriefcaseCommandError("""\ Unable to unpack Android SDK ZIP file. The download may have been interrupted or corrupted. Delete {sdk_zip_path} and run briefcase again.""".format( sdk_zip_path=sdk_zip_path)) sdk_zip_path.unlink() # Zip file no longer needed once unpacked. # `ZipFile` ignores the permission metadata in the Android SDK ZIP # file, so we manually fix permissions. for binpath in tools_path.glob("*"): if not self.os.access(str(binpath), self.os.X_OK): binpath.chmod(0o755)
def install(self): """ Download and install the Android SDK. """ try: sdk_zip_path = self.command.download_url( url=self.sdk_url, download_path=self.command.tools_path, ) except requests_exceptions.ConnectionError: raise NetworkFailure("download Android SDK") try: print("Install Android SDK...") # TODO: Py3.6 compatibility; os.fsdecode not required in Py3.7 self.command.shutil.unpack_archive(os.fsdecode(sdk_zip_path), extract_dir=os.fsdecode(self.root_path)) except (shutil.ReadError, EOFError): raise BriefcaseCommandError( """\ Unable to unpack Android SDK ZIP file. The download may have been interrupted or corrupted. Delete {sdk_zip_path} and run briefcase again.""".format( sdk_zip_path=sdk_zip_path ) ) # Zip file no longer needed once unpacked. sdk_zip_path.unlink() # Python zip unpacking ignores permission metadata. # On non-Windows, we manually fix permissions. if self.command.host_os != "Windows": for binpath in (self.root_path / "tools" / "bin").glob("*"): if not self.command.os.access(binpath, self.command.os.X_OK): binpath.chmod(0o755) # Licences must be accepted. self.verify_license()
def install_app_support_package(self, app: BaseConfig): """Install the application support package. :param app: The config object for the app """ try: # Work out if the app defines a custom override for # the support package URL. try: support_package_url = app.support_package custom_support_package = True self.logger.info( f"Using custom support package {support_package_url}") except AttributeError: support_package_url = self.support_package_url custom_support_package = False self.logger.info( f"Using support package {support_package_url}") if support_package_url.startswith( "https://") or support_package_url.startswith("http://"): try: self.logger.info( f"... pinned to revision {app.support_revision}") # If a revision has been specified, add the revision # as a query argument in the support package URL. # This is a lot more painful than "add arg to query" should # be because (a) url splits aren't appendable, and # (b) Python 3.5 doesn't guarantee dictionary order. url_parts = list(urlsplit(support_package_url)) query = list(parse_qsl(url_parts[3])) query.append(("revision", app.support_revision)) url_parts[3] = urlencode(query) support_package_url = urlunsplit(url_parts) except AttributeError: # No support revision specified. self.logger.info("... using most recent revision") # Download the support file, caching the result # in the user's briefcase support cache directory. support_filename = self.download_url( url=support_package_url, download_path=self.dot_briefcase_path / "support", ) else: support_filename = Path(support_package_url) except MissingNetworkResourceError as e: # If there is a custom support package, report the missing resource as-is. if custom_support_package: raise else: raise MissingSupportPackage( python_version_tag=self.python_version_tag, host_arch=self.host_arch, ) from e except requests_exceptions.ConnectionError as e: raise NetworkFailure("downloading support package") from e try: self.logger.info("Unpacking support package...") support_path = self.support_path(app) support_path.mkdir(parents=True, exist_ok=True) # TODO: Py3.6 compatibility; os.fsdecode not required in Py3.7 self.shutil.unpack_archive(os.fsdecode(support_filename), extract_dir=os.fsdecode(support_path)) except (shutil.ReadError, EOFError) as e: raise InvalidSupportPackage(support_package_url) from e
def verify_jdk(command): """ Verify that a Java 8 JDK exists. If ``JAVA_HOME`` is set, try that version. If it is a JRE, or its *not* a Java 8 JDK, download one. On macOS, also try invoking /usr/libexec/java_home. If that location points to a Java 8 JDK, use it. Otherwise, download a JDK from AdoptOpenJDK and unpack it into the ``~.briefcase`` path. :param command: The command that needs to perform the verification check. :returns: The value for ``JAVA_HOME`` """ java_home = command.os.environ.get('JAVA_HOME', '') install_message = None # macOS has a helpful system utility to determine JAVA_HOME. Try it. if not java_home and command.host_os == 'Darwin': try: # If no JRE/JDK is installed, /usr/libexec/java_home # raises an error. java_home = command.subprocess.check_output( ['/usr/libexec/java_home'], universal_newlines=True, stderr=subprocess.STDOUT, ).strip('\n') except subprocess.CalledProcessError: # No java on this machine. pass if java_home: try: # If JAVA_HOME is defined, try to invoke javac. # This verifies that we have a JDK, not a just a JRE. output = command.subprocess.check_output( [ str(Path(java_home) / 'bin' / 'javac'), '-version', ], universal_newlines=True, stderr=subprocess.STDOUT, ) # This should be a string of the form "javac 1.8.0_144\n" version_str = output.strip('\n').split(' ')[1] vparts = version_str.split('.') if len(vparts) == 3 and vparts[:2] == ['1', '8']: # It appears to be a Java 8 JDK. return Path(java_home) else: # It's not a Java 8 JDK. java_home = None install_message = """ ************************************************************************* ** WARNING: JAVA_HOME does not point to a Java 8 JDK ** ************************************************************************* Android requires a Java 8 JDK, but the location pointed to by the JAVA_HOME environment variable: {java_home} isn't a Java 8 JDK (it appears to be Java {version_str}). Briefcase will use its own JDK instance. ************************************************************************* """.format(java_home=java_home, version_str=version_str) except FileNotFoundError: java_home = None install_message = """ ************************************************************************* ** WARNING: JAVA_HOME does not point to a JDK ** ************************************************************************* The location pointed to by the JAVA_HOME environment variable: {java_home} does not appear to be a JDK. It may be a Java Runtime Environment. Briefcase will use its own JDK instance. ************************************************************************* """.format(java_home=java_home) except subprocess.CalledProcessError: java_home = None install_message = """ ************************************************************************* ** WARNING: Unable to invoke the Java compiler ** ************************************************************************* Briefcase received an unexpected error when trying to invoke javac, the Java compiler, at the location indicated by the JAVA_HOME environment variable. Briefcase will continue by downloading and using its own JDK. Please report this as a bug at: https://github.com/beeware/briefcase/issues/new In your report, please including the output from running: {java_home}/bin/javac -version from the command prompt. ************************************************************************* """.format(java_home=java_home) except IndexError: java_home = None install_message = """ ************************************************************************* ** WARNING: Unable to determine the version of Java that is installed ** ************************************************************************* Briefcase was unable to interpret the version information returned by the Java compiler at the location indicated by the JAVA_HOME environment variable. Briefcase will continue by downloading and using its own JDK. Please report this as a bug at: https://github.com/beeware/briefcase/issues/new In your report, please including the output from running: {java_home}/bin/javac -version from the command prompt. ************************************************************************* """.format(java_home=java_home) # If we've reached this point, any user-provided JAVA_HOME is broken; # use the Briefcase one. java_home = command.dot_briefcase_path / 'tools' / 'java' # The macOS download has a weird layout (inherited from the official Oracle # release). The actual JAVA_HOME is deeper inside the directory structure. if command.host_os == 'Darwin': java_home = java_home / 'Contents' / 'Home' if (java_home / 'bin').exists(): # Using briefcase cached Java version return java_home # We only display the warning messages on the pass where we actually # install the JDK. if install_message: print(install_message) print("Obtaining a Java 8 JDK...") # As of April 10 2020, 8u242-b08 is the current AdoptOpenJDK # https://adoptopenjdk.net/releases.html jdk_release = '8u242' jdk_build = 'b08' jdk_platform = { 'Darwin': 'mac', 'Windows': 'windows', 'Linux': 'linux', }.get(command.host_os) extension = { 'Windows': 'zip', }.get(command.host_os, 'tar.gz') jdk_url = ( 'https://github.com/AdoptOpenJDK/openjdk8-binaries/' 'releases/download/jdk{jdk_release}-{jdk_build}/' 'OpenJDK8U-jdk_x64_{jdk_platform}_hotspot_{jdk_release}{jdk_build}.{extension}' ).format( jdk_release=jdk_release, jdk_build=jdk_build, jdk_platform=jdk_platform, extension=extension, ) try: jdk_zip_path = command.download_url( url=jdk_url, download_path=command.dot_briefcase_path / "tools", ) except requests_exceptions.ConnectionError: raise NetworkFailure("download Java 8 JDK") try: command.shutil.unpack_archive( str(jdk_zip_path), extract_dir=str(command.dot_briefcase_path / "tools") ) except (shutil.ReadError, EOFError): raise BriefcaseCommandError( """\ Unable to unpack AdoptOpenJDK ZIP file. The download may have been interrupted or corrupted. Delete {jdk_zip_path} and run briefcase again.""".format( jdk_zip_path=jdk_zip_path ) ) jdk_zip_path.unlink() # Zip file no longer needed once unpacked. # The tarball will unpack into ~.briefcase/tools/jdk8u242-b08 # (or whatever name matches the current release). # We turn this into ~.briefcase/tools/java so we have a consistent name. java_unpack_path = command.dot_briefcase_path / "tools" / "jdk{jdk_release}-{jdk_build}".format( jdk_release=jdk_release, jdk_build=jdk_build, ) java_unpack_path.rename(command.dot_briefcase_path / "tools" / "java") print() return java_home
def create_emulator(self): """Create a new Android emulator. :returns: The AVD of the newly created emulator. """ # Get the list of existing emulators emulators = set(self.emulators()) default_avd = 'beePhone' i = 1 # Make sure the default name is unique while default_avd in emulators: i += 1 default_avd = 'beePhone{i}'.format(i=i) # Prompt for a device avd until a valid one is provided. print(""" You need to select a name for your new emulator. This is an identifier that can be used to start the emulator in future. It should follow the same naming conventions as a Python package (i.e., it may only contain letters, numbers, hyphens and underscores). If you don't provide a name, Briefcase will use the a default name '{default_avd}'. """.format(default_avd=default_avd)) avd_is_invalid = True while avd_is_invalid: avd = self.command.input("Emulator name [{default_avd}]: ".format( default_avd=default_avd)) # If the user doesn't provide a name, use the default. if avd == '': avd = default_avd if not PEP508_NAME_RE.match(avd): print(""" '{avd}' is not a valid emulator name. An emulator name may only contain letters, numbers, hyphens and underscores """.format(avd=avd)) elif avd in emulators: print(""" An emulator named '{avd}' already exists. """.format(avd=avd)) print() else: avd_is_invalid = False # TODO: Provide a list of options for device types with matching skins device_type = 'pixel' skin = 'pixel_3a' try: print() print("Creating Android emulator {avd}...".format(avd=avd)) print() self.command.subprocess.check_output( [ str(self.avdmanager_path), "--verbose", "create", "avd", "--name", avd, "--abi", "x86", "--package", 'system-images;android-28;default;x86', "--device", device_type, ], env=self.env, universal_newlines=True, stderr=subprocess.STDOUT, ) except subprocess.CalledProcessError: raise BriefcaseCommandError("Unable to create Android emulator") # Check for a device skin. If it doesn't exist, download it. skin_path = self.root_path / "skins" / skin if skin_path.exists(): print("Device skin '{skin}' already exists".format(skin=skin)) else: print("Obtaining device skin...") skin_url = ( "https://android.googlesource.com/platform/tools/adt/idea/" "+archive/refs/heads/mirror-goog-studio-master-dev/" "artwork/resources/device-art-resources/{skin}.tar.gz".format( skin=skin)) try: skin_tgz_path = self.command.download_url( url=skin_url, download_path=self.root_path, ) except requests_exceptions.ConnectionError: raise NetworkFailure( "download {skin} device skin".format(skin=skin)) # Unpack skin archive try: self.command.shutil.unpack_archive(str(skin_tgz_path), extract_dir=str(skin_path)) except (shutil.ReadError, EOFError): raise BriefcaseCommandError( "Unable to unpack {skin} device skin".format(skin=skin)) # Delete the downloaded file. skin_tgz_path.unlink() print("Adding extra device configuration...") with (self.avd_path / '{avd}.avd'.format(avd=avd) / 'config.ini').open('a') as f: f.write(""" disk.dataPartition.size=4096M hw.keyboard=yes skin.dynamic=yes skin.name={skin} skin.path=skins/{skin} showDeviceFrame=yes """.format(skin=skin)) print(""" Android emulator '{avd}' created. In future, you can specify this device by running: briefcase run android -d @{avd} """.format(avd=avd)) return avd
def verify_android_sdk(command): """ Verify an Android SDK is available. If the ANDROID_SDK_ROOT environment variable is set, that location will be checked for a valid SDK. If the location provided doesn't contain an SDK, or no location is provided, an SDK is downloaded. :param command: The command making the verification request. :returns: An AndroidSDK instance, bound to command. """ sdk_root = command.os.environ.get("ANDROID_SDK_ROOT") if sdk_root: sdk = AndroidSDK(command=command, root_path=Path(sdk_root)) if sdk.exists(): # Ensure licenses have been accepted sdk.verify_license() return sdk else: print(""" ************************************************************************* ** WARNING: ANDROID_SDK_ROOT does not point to an Android SDK ** ************************************************************************* The location pointed to by the ANDROID_SDK_ROOT environment variable: {sdk_root} doesn't appear to contain an Android SDK. Briefcase will use its own SDK instance. ************************************************************************* """.format(sdk_root=sdk_root)) # Build an SDK wrapper for the Briefcase SDK instance. sdk = AndroidSDK(command=command, root_path=command.dot_briefcase_path / "tools" / "android_sdk") if sdk.exists(): # Ensure licenses have been accepted sdk.verify_license() return sdk print("Setting up Android SDK...") try: sdk_zip_path = command.download_url( url=sdk.sdk_url, download_path=command.dot_briefcase_path / "tools", ) except requests_exceptions.ConnectionError: raise NetworkFailure("download Android SDK") try: command.shutil.unpack_archive(str(sdk_zip_path), extract_dir=str(sdk.root_path)) except (shutil.ReadError, EOFError): raise BriefcaseCommandError("""\ Unable to unpack Android SDK ZIP file. The download may have been interrupted or corrupted. Delete {sdk_zip_path} and run briefcase again.""".format( sdk_zip_path=sdk_zip_path)) # Zip file no longer needed once unpacked. sdk_zip_path.unlink() # Python zip unpacking ignores permission metadata. # On non-Windows, we manually fix permissions. if command.host_os != "Windows": for binpath in (sdk.root_path / "tools" / "bin").glob("*"): if not command.os.access(str(binpath), command.os.X_OK): binpath.chmod(0o755) # Licences must be accepted. sdk.verify_license() return sdk
def verify_wix(command): """ Verify that there is a WiX install available. If the WIX environment variable is set, that location will be checked for a valid WiX installation. If the location provided doesn't contain an SDK, or no location is provided, an SDK is downloaded. :param command: The command making the verification request. :returns: A triple containing the paths to the heat, light, and candle executables. """ if command.host_os != 'Windows': raise BriefcaseCommandError(""" A Windows MSI installer can only be created on Windows. """) # Look for the WIX environment variable wix_env = command.os.environ.get("WIX") if wix_env: wix_path = Path(wix_env) # Set up the paths for the WiX executables we will use. wix = WiX(wix_path) if not wix.exists(): raise BriefcaseCommandError(""" The WIX environment variable does not point to an install of the WiX Toolset. Current value: {wix_path!r} """.format(wix_path=wix_path)) else: wix_path = command.dot_briefcase_path / 'tools' / 'wix' wix = WiX(wix_path, bin_install=True) if not wix.exists(): print("Downloading WiX...") try: wix_zip_path = command.download_url( url=WIX_DOWNLOAD_URL, download_path=command.dot_briefcase_path / "tools", ) except requests_exceptions.ConnectionError: raise NetworkFailure("download WiX") try: command.shutil.unpack_archive(str(wix_zip_path), extract_dir=str(wix_path)) except (shutil.ReadError, EOFError): raise BriefcaseCommandError(""" Unable to unpack WiX ZIP file. The download may have been interrupted or corrupted. Delete {wix_zip_path} and run briefcase again.""".format( wix_zip_path=wix_zip_path)) # Zip file no longer needed once unpacked. wix_zip_path.unlink() return wix