def packDistFolderToOnefileBootstrap(onefile_output_filename, dist_dir): postprocessing_logger.info( "Creating single file from dist folder, this may take a while.") onefile_logger.info("Running bootstrap binary compilation via Scons.") # Now need to append to payload it, potentially compressing it. compressor_python = getCompressorPython() # First need to create the bootstrap binary for unpacking. _runOnefileScons( quiet=not Options.isShowScons(), onefile_compression=compressor_python is not None, ) if isWin32Windows(): executePostProcessingResources(manifest=None, onefile=True) Plugins.onBootstrapBinary(onefile_output_filename) if isMacOS(): addMacOSCodeSignature(filenames=[onefile_output_filename]) runOnefileCompressor( compressor_python=compressor_python, dist_dir=dist_dir, onefile_output_filename=onefile_output_filename, start_binary=getResultFullpath(onefile=False), )
def packDistFolderToOnefileWindows(dist_dir): general.warning("Onefile mode is experimental on '%s'." % getOS()) postprocessing_logger.info( "Creating single file from dist folder, this may take a while.") onefile_output_filename = getResultFullpath(onefile=True) # First need to create the bootstrap binary for unpacking. _runOnefileScons(quiet=not Options.isShowScons()) # Make sure to copy the resources from the created binary to the bootstrap binary, these # are icons and version information. copyResourcesFromFileToFile( source_filename=getResultFullpath(onefile=False), target_filename=onefile_output_filename, resource_kinds=(RT_ICON, RT_GROUP_ICON, RT_VERSION), ) # Now need to append to payload it, potentially compressing it. compression_indicator, compressor = _pickCompressor() with open(onefile_output_filename, "ab") as output_file: # Seeking to end of file seems necessary on Python2 at least, maybe it's # just that tell reports wrong value initially. output_file.seek(0, 2) start_pos = output_file.tell() output_file.write(b"KA" + compression_indicator) # Move the binary to start immediately to the start position start_binary = getResultFullpath(onefile=False) file_list = getFileList(dist_dir) file_list.remove(start_binary) file_list.insert(0, start_binary) for filename_full in file_list: filename_relative = os.path.relpath(filename_full, dist_dir) filename_encoded = filename_relative.encode("utf-16le") + b"\0\0" output_file.write(filename_encoded) with open(filename_full, "rb") as input_file: compressed = compressor(input_file.read()) output_file.write(struct.pack("Q", len(compressed))) output_file.write(compressed) # Using empty filename as a terminator. output_file.write(b"\0\0") output_file.write(struct.pack("Q", start_pos))
def packDistFolderToOnefileBootstrap(onefile_output_filename, dist_dir): postprocessing_logger.info( "Creating single file from dist folder, this may take a while." ) general.info("Running bootstrap binary compilation via Scons.") # First need to create the bootstrap binary for unpacking. _runOnefileScons(quiet=not Options.isShowScons()) executePostProcessingResources(manifest=None, onefile=True) # Now need to append to payload it, potentially compressing it. compression_indicator, compressor = _pickCompressor() with open(onefile_output_filename, "ab") as output_file: # Seeking to end of file seems necessary on Python2 at least, maybe it's # just that tell reports wrong value initially. output_file.seek(0, 2) start_pos = output_file.tell() output_file.write(b"KA" + compression_indicator) # Move the binary to start immediately to the start position start_binary = getResultFullpath(onefile=False) file_list = getFileList(dist_dir, normalize=False) file_list.remove(start_binary) file_list.insert(0, start_binary) for filename_full in file_list: filename_relative = os.path.relpath(filename_full, dist_dir) filename_encoded = filename_relative.encode("utf-16le") + b"\0\0" output_file.write(filename_encoded) with open(filename_full, "rb") as input_file: compressed = compressor(input_file.read()) output_file.write(struct.pack("Q", len(compressed))) output_file.write(compressed) # Using empty filename as a terminator. output_file.write(b"\0\0") output_file.write(struct.pack("Q", start_pos))
def addWindowsIconFromIcons(): icon_group = 1 image_id = 1 images = [] result_filename = OutputDirectories.getResultFullpath() for icon_spec in Options.getIconPaths(): if "#" in icon_spec: icon_path, icon_index = icon_spec.rsplit("#", 1) icon_index = int(icon_index) else: icon_path = icon_spec icon_index = None with open(icon_path, "rb") as icon_file: # Read header and icon entries. header = readFromFile(icon_file, IconDirectoryHeader) icons = [ readFromFile(icon_file, IconDirectoryEntry) for _i in range(header.count) ] if icon_index is not None: if icon_index > len(icons): postprocessing_logger.sysexit( "Error, referenced icon index %d in file %r with only %d icons." % (icon_index, icon_path, len(icons))) icons[:] = icons[icon_index:icon_index + 1] postprocessing_logger.info("Adding %d icon(s) from icon file %r." % (len(icons), icon_spec)) # Image data are to be scanned from places specified icon entries for icon in icons: icon_file.seek(icon.image_offset, 0) images.append(icon_file.read(icon.image_size)) parts = [convertStructureToBytes(header)] for icon in icons: parts.append( convertStructureToBytes( IconGroupDirectoryEntry( width=icon.width, height=icon.height, colors=icon.colors, reserved=icon.reserved, planes=icon.planes, bit_count=icon.bit_count, image_size=icon.image_size, id=image_id, ))) image_id += 1 addResourceToFile( target_filename=result_filename, data=b"".join(parts), resource_kind=RT_GROUP_ICON, lang_id=0, res_name=icon_group, logger=postprocessing_logger, ) for count, image in enumerate(images, 1): addResourceToFile( target_filename=result_filename, data=image, resource_kind=RT_ICON, lang_id=0, res_name=count, logger=postprocessing_logger, )
def packDistFolderToOnefileLinux(onefile_output_filename, dist_dir, binary_filename): """Pack to onefile binary on Linux. Notes: This is mostly a wrapper around AppImage, which does all the heavy lifting. """ if not locateDLL("fuse"): postprocessing_logger.sysexit( """\ Error, the fuse library (libfuse.so.x from fuse2, *not* fuse3) must be installed for onefile creation to work on Linux.""" ) # This might be possible to avoid being done with --runtime-file. apprun_filename = os.path.join(dist_dir, "AppRun") with open(apprun_filename, "w") as output_file: output_file.write( """\ #!/bin/bash exec -a $ARGV0 $APPDIR/%s $@""" % os.path.basename(binary_filename) ) addFileExecutablePermission(apprun_filename) binary_basename = os.path.basename(getResultBasepath()) icon_paths = getIconPaths() assert icon_paths extension = os.path.splitext(icon_paths[0])[1].lower() shutil.copyfile(icon_paths[0], getResultBasepath() + extension) with open(getResultBasepath() + ".desktop", "w") as output_file: output_file.write( """\ [Desktop Entry] Name=%(binary_basename)s Exec=%(binary_filename)s Icon=%(binary_basename)s Type=Application Categories=Utility;""" % { "binary_basename": binary_basename, "binary_filename": os.path.basename(binary_filename), } ) postprocessing_logger.info( "Creating single file from dist folder, this may take a while." ) stdout_filename = binary_filename + ".appimage.stdout.txt" stderr_filename = binary_filename + ".appimage.stderr.txt" stdout_file = open(stdout_filename, "wb") stderr_file = open(stderr_filename, "wb") # Starting the process while locked, so file handles are not duplicated. appimagetool_process = subprocess.Popen( ( _getAppImageToolPath( for_operation=True, assume_yes_for_downloads=assumeYesForDownloads() ), dist_dir, "--comp", "xz", "-n", onefile_output_filename, ), shell=False, stdin=getNullInput(), stdout=stdout_file, stderr=stderr_file, ) result = appimagetool_process.wait() stdout_file.close() stderr_file.close() if not os.path.exists(onefile_output_filename): postprocessing_logger.sysexit( "Error, expected output file %r not created by AppImage, check its outputs %r and %r." % (onefile_output_filename, stdout_filename, stderr_filename) ) if result != 0: # Useless now. os.unlink(onefile_output_filename) if b"Text file busy" in getFileContents(stderr_filename, mode="rb"): postprocessing_logger.sysexit( "Error, error exit from AppImage because target file is locked." ) postprocessing_logger.sysexit( "Error, error exit from AppImage, check its outputs %r and %r." % (stdout_filename, stderr_filename) ) os.unlink(stdout_filename) os.unlink(stderr_filename) postprocessing_logger.info("Completed onefile creation.")
def packDistFolderToOnefileLinux(onefile_output_filename, dist_dir, binary_filename): """Pack to onefile binary on Linux. Notes: This is mostly a wrapper around AppImage, which does all the heavy lifting. """ # This might be possible to avoid being done with --runtime-file. apprun_filename = os.path.join(dist_dir, "AppRun") with open(apprun_filename, "w") as output_file: output_file.write("""\ #!/bin/sh exec $APPDIR/%s $@""" % os.path.basename(binary_filename)) addFileExecutablePermission(apprun_filename) binary_basename = os.path.basename(getResultBasepath()) icon_paths = getIconPaths() assert icon_paths extension = os.path.splitext(icon_paths[0])[1].lower() shutil.copyfile(icon_paths[0], getResultBasepath() + extension) with open(getResultBasepath() + ".desktop", "w") as output_file: output_file.write( """\ [Desktop Entry] Name=%(binary_basename)s Exec=%(binary_filename)s Icon=%(binary_basename)s Type=Application Categories=Utility;""" % { "binary_basename": binary_basename, "binary_filename": os.path.basename(binary_filename), }) postprocessing_logger.info( "Creating single file from dist folder, this may take a while.") # Starting the process while locked, so file handles are not duplicated. appimagetool_process = subprocess.Popen( ( getAppImageToolPath(), dist_dir, "--comp", "xz", "-n", onefile_output_filename, ), shell=False, stderr=getNullOutput(), stdout=getNullOutput(), ) # TODO: Exit code should be checked. result = appimagetool_process.wait() if not os.path.exists(onefile_output_filename): postprocessing_logger.sysexit( "Error, expected output file %s not created by AppImage." % onefile_output_filename) postprocessing_logger.info("Completed onefile creation.") assert result == 0, result
def _addWindowsIconFromIcons(onefile): # Relatively detailed handling, pylint: disable=too-many-locals icon_group = 1 image_id = 1 images = [] result_filename = OutputDirectories.getResultFullpath(onefile=onefile) for icon_spec in Options.getIconPaths(): if "#" in icon_spec: icon_path, icon_index = icon_spec.rsplit("#", 1) icon_index = int(icon_index) else: icon_path = icon_spec icon_index = None icon_path = os.path.normcase(icon_path) if not icon_path.endswith(".ico"): postprocessing_logger.info( "File '%s' is not in Windows icon format, converting to it." % icon_path) if icon_index is not None: postprocessing_logger.sysexit( "Cannot specify indexes with non-ico format files in '%s'." % icon_spec) icon_build_path = os.path.join( OutputDirectories.getSourceDirectoryPath(onefile=onefile), "icons", ) makePath(icon_build_path) converted_icon_path = os.path.join( icon_build_path, "icon-%d.ico" % image_id, ) convertImageToIconFormat( logger=postprocessing_logger, image_filename=icon_spec, icon_filename=converted_icon_path, ) icon_path = converted_icon_path with open(icon_path, "rb") as icon_file: # Read header and icon entries. header = readFromFile(icon_file, IconDirectoryHeader) icons = [ readFromFile(icon_file, IconDirectoryEntry) for _i in range(header.count) ] if icon_index is not None: if icon_index > len(icons): postprocessing_logger.sysexit( "Error, referenced icon index %d in file '%s' with only %d icons." % (icon_index, icon_path, len(icons))) icons[:] = icons[icon_index:icon_index + 1] postprocessing_logger.info( "Adding %d icon(s) from icon file '%s'." % (len(icons), icon_spec)) # Image data are to be scanned from places specified icon entries for icon in icons: icon_file.seek(icon.image_offset, 0) images.append(icon_file.read(icon.image_size)) parts = [convertStructureToBytes(header)] for icon in icons: parts.append( convertStructureToBytes( IconGroupDirectoryEntry( width=icon.width, height=icon.height, colors=icon.colors, reserved=icon.reserved, planes=icon.planes, bit_count=icon.bit_count, image_size=icon.image_size, id=image_id, ))) image_id += 1 addResourceToFile( target_filename=result_filename, data=b"".join(parts), resource_kind=RT_GROUP_ICON, lang_id=0, res_name=icon_group, logger=postprocessing_logger, ) for count, image in enumerate(images, 1): addResourceToFile( target_filename=result_filename, data=image, resource_kind=RT_ICON, lang_id=0, res_name=count, logger=postprocessing_logger, )
def packDistFolderToOnefileLinux(onefile_output_filename, dist_dir, binary_filename): """Pack to onefile binary on Linux. Notes: This is mostly a wrapper around AppImage, which does all the heavy lifting. """ if not locateDLL("fuse"): postprocessing_logger.sysexit("""\ Error, the fuse library (libfuse.so.x from fuse2, *not* fuse3) must be installed for onefile creation to work on Linux.""") # This might be possible to avoid being done with --runtime-file. apprun_filename = os.path.join(dist_dir, "AppRun") putTextFileContents( apprun_filename, contents="""\ #!/bin/bash exec -a $ARGV0 $APPDIR/%s \"$@\"""" % os.path.basename(binary_filename), ) addFileExecutablePermission(apprun_filename) binary_basename = os.path.basename(getResultBasepath()) icon_paths = getIconPaths() assert icon_paths extension = os.path.splitext(icon_paths[0])[1].lower() copyFile(icon_paths[0], getResultBasepath() + extension) putTextFileContents( getResultBasepath() + ".desktop", contents="""\ [Desktop Entry] Name=%(binary_basename)s Exec=%(binary_filename)s Icon=%(binary_basename)s Type=Application Categories=Utility;""" % { "binary_basename": binary_basename, "binary_filename": os.path.basename(binary_filename), }, ) postprocessing_logger.info( "Creating single file from dist folder, this may take a while.") stdout_filename = binary_filename + ".appimage.stdout.txt" stderr_filename = binary_filename + ".appimage.stderr.txt" stdout_file = openTextFile(stdout_filename, "wb") stderr_file = openTextFile(stderr_filename, "wb") command = ( _getAppImageToolPath(for_operation=True, assume_yes_for_downloads=assumeYesForDownloads()), dist_dir, "--comp", getAppImageCompression(), "-n", onefile_output_filename, ) stderr_file.write(b"Executed %r\n" % " ".join(command)) # Starting the process while locked, so file handles are not duplicated, we # need fine grained control over process here, therefore we cannot use the # Execution.executeProcess() function without making it too complex and not # all Python versions allow using with, pylint: disable=consider-using-with # pylint: disable appimagetool_process = subprocess.Popen( command, shell=False, stdin=getNullInput(), stdout=stdout_file, stderr=stderr_file, ) result = appimagetool_process.wait() stdout_file.close() stderr_file.close() if result != 0: # Useless result if there were errors, so now remove it. deleteFile(onefile_output_filename, must_exist=False) stderr = getFileContents(stderr_filename, mode="rb") if b"Text file busy" in stderr: postprocessing_logger.sysexit( "Error, error exit from AppImage because target file is locked." ) if b"modprobe fuse" in stderr: postprocessing_logger.sysexit( "Error, error exit from AppImage because fuse kernel module was not loaded." ) postprocessing_logger.sysexit( "Error, error exit from AppImage, check its outputs '%s' and '%s'." % (stdout_filename, stderr_filename)) if not os.path.exists(onefile_output_filename): postprocessing_logger.sysexit( "Error, expected output file %r not created by AppImage, check its outputs '%s' and '%s'." % (onefile_output_filename, stdout_filename, stderr_filename)) deleteFile(stdout_filename, must_exist=True) deleteFile(stderr_filename, must_exist=True) postprocessing_logger.info("Completed onefile creation.")