Beispiel #1
0
    def _check_guts(self, data, last_build):
        if not os.path.exists(self.name):
            logger.info("Rebuilding %s because %s missing", self.tocbasename,
                        os.path.basename(self.name))
            return 1
        if not self.append_pkg and not os.path.exists(self.pkgname):
            logger.info("Rebuilding because %s missing",
                        os.path.basename(self.pkgname))
            return 1

        if Target._check_guts(self, data, last_build):
            return True

        if (data['versrsrc'] or data['resources']) and not is_win:
            # todo: really ignore :-)
            logger.warning(
                'ignoring version, manifest and resources, platform not capable'
            )
        if data['icon'] and not (is_win or is_darwin):
            logger.warning('ignoring icon, platform not capable')

        mtm = data['mtm']
        if mtm != misc.mtime(self.name):
            logger.info("Rebuilding %s because mtimes don't match",
                        self.tocbasename)
            return True
        if mtm < misc.mtime(self.pkg.tocfilename):
            logger.info("Rebuilding %s because pkg is more recent",
                        self.tocbasename)
            return True
        return False
Beispiel #2
0
    def _check_guts(self, data, last_build):
        if not os.path.exists(self.name):
            logger.info("Rebuilding %s because %s missing",
                        self.tocbasename, os.path.basename(self.name))
            return 1
        if not self.append_pkg and not os.path.exists(self.pkgname):
            logger.info("Rebuilding because %s missing",
                        os.path.basename(self.pkgname))
            return 1

        if Target._check_guts(self, data, last_build):
            return True

        if (data['versrsrc'] or data['resources']) and not is_win:
            # todo: really ignore :-)
            logger.warn('ignoring version, manifest and resources, platform not capable')
        if data['icon'] and not (is_win or is_darwin):
            logger.warn('ignoring icon, platform not capable')

        mtm = data['mtm']
        if mtm != misc.mtime(self.name):
            logger.info("Rebuilding %s because mtimes don't match", self.tocbasename)
            return True
        if mtm < misc.mtime(self.pkg.tocfilename):
            logger.info("Rebuilding %s because pkg is more recent", self.tocbasename)
            return True
        return False
Beispiel #3
0
def _check_guts_toc_mtime(_attr, old, _toc, last_build, pyc=0):
    """
    Rebuild is required if mtimes of files listed in old toc are newer than last_build.

    If pyc=1, check for .py files as well.

    Use this for calculated/analysed values read from cache.
    """
    for nm, fnm, typ in old:
        if misc.mtime(fnm) > last_build:
            logger.info("Building because %s changed", fnm)
            return True
        elif pyc and misc.mtime(fnm[:-1]) > last_build:
            logger.info("Building because %s changed", fnm[:-1])
            return True
    return False
Beispiel #4
0
    def __postinit__(self):
        """
        Check if the target need to be rebuild and if so, re-assemble.

        `__postinit__` is to be called at the end of `__init__` of
        every subclass of Target. `__init__` is meant to setup the
        parameters and `__postinit__` is checking if rebuild is
        required and in case calls `assemble()`
        """
        logger.info("checking %s", self.__class__.__name__)
        data = None
        last_build = misc.mtime(self.tocfilename)
        if last_build == 0:
            logger.info("Building %s because %s is non existent",
                        self.__class__.__name__, self.tocbasename)
        else:
            try:
                data = load_py_data_struct(self.tocfilename)
            except:
                logger.info("Building because %s is bad", self.tocbasename)
            else:
                # create a dict for easier access
                data = dict(zip((g[0] for g in self._GUTS), data))
        # assemble if previous data was not found or is outdated
        if not data or self._check_guts(data, last_build):
            self.assemble()
            self._save_guts()
Beispiel #5
0
    def __postinit__(self):
        """
        Check if the target need to be rebuild and if so, re-assemble.

        `__postinit__` is to be called at the end of `__init__` of
        every subclass of Target. `__init__` is meant to setup the
        parameters and `__postinit__` is checking if rebuild is
        required and in case calls `assemble()`
        """
        logger.info("checking %s", self.__class__.__name__)
        data = None
        last_build = misc.mtime(self.tocfilename)
        if last_build == 0:
            logger.info("Building %s because %s is non existent", self.__class__.__name__, self.tocbasename)
        else:
            try:
                data = load_py_data_struct(self.tocfilename)
            except:
                logger.info("Building because %s is bad", self.tocbasename)
            else:
                # create a dict for easier access
                data = dict(zip((g[0] for g in self._GUTS), data))
        # assemble if previous data was not found or is outdated
        if not data or self._check_guts(data, last_build):
            self.assemble()
            self._save_guts()
 def _check_guts(self, data, last_build):
     if Target._check_guts(self, data, last_build):
         return True
     # Walk the collected directories as check if they have been changed - which means files have been added or
     # removed. There is no need to check for the files, since `Tree` is only about the directory contents (which is
     # the list of files).
     stack = [data['root']]
     while stack:
         d = stack.pop()
         if misc.mtime(d) > last_build:
             logger.info("Building %s because directory %s changed",
                         self.tocbasename, d)
             return True
         for nm in os.listdir(d):
             path = os.path.join(d, nm)
             if os.path.isdir(path):
                 stack.append(path)
     self[:] = data['data']  # collected files
     return False
Beispiel #7
0
 def _check_guts(self, data, last_build):
     if Target._check_guts(self, data, last_build):
         return True
     # Walk the collected directories as check if they have been
     # changed - which means files have been added or removed.
     # There is no need to check for the files, since `Tree` is
     # only about the directory contents (which is the list of
     # files).
     stack = [data["root"]]
     while stack:
         d = stack.pop()
         if misc.mtime(d) > last_build:
             logger.info("Building %s because directory %s changed", self.tocbasename, d)
             return True
         for nm in os.listdir(d):
             path = os.path.join(d, nm)
             if os.path.isdir(path):
                 stack.append(path)
     self[:] = data["data"]  # collected files
     return False
Beispiel #8
0
    def _check_guts(self, data, last_build):
        if Target._check_guts(self, data, last_build):
            return True
        for fnm in self.inputs:
            if mtime(fnm) > last_build:
                logger.info("Building because %s changed", fnm)
                return True
        # Now we know that none of the input parameters and none of the input files has changed. So take the values
        # calculated resp. analysed in the last run and store them in `self`.
        self.scripts = TOC(data['scripts'])
        self.pure = TOC(data['pure'])
        self.binaries = TOC(data['binaries'])
        self.zipfiles = TOC(data['zipfiles'])
        self.zipped_data = TOC(data['zipped_data'])
        self.datas = TOC(data['datas'])

        # Store previously found binding redirects in CONF for later use by PKG/COLLECT
        from PyInstaller.config import CONF
        self.binding_redirects = CONF['binding_redirects'] = data[
            'binding_redirects']

        return False
Beispiel #9
0
    def assemble(self):
        logger.info("Building EXE from %s", self.tocbasename)
        trash = []
        if os.path.exists(self.name):
            os.remove(self.name)
        if not os.path.exists(os.path.dirname(self.name)):
            os.makedirs(os.path.dirname(self.name))
        exe = self.exefiles[0][1]  # pathname of bootloader
        if not os.path.exists(exe):
            raise SystemExit(_MISSING_BOOTLOADER_ERRORMSG)

        if is_win and (self.icon or self.versrsrc or self.resources):
            tmpnm = tempfile.mktemp()
            self._copyfile(exe, tmpnm)
            os.chmod(tmpnm, 0o755)
            if self.icon:
                icon.CopyIcons(tmpnm, self.icon)
            if self.versrsrc:
                versioninfo.SetVersion(tmpnm, self.versrsrc)
            for res in self.resources:
                res = res.split(",")
                for i in range(1, len(res)):
                    try:
                        res[i] = int(res[i])
                    except ValueError:
                        pass
                resfile = res[0]
                restype = resname = reslang = None
                if len(res) > 1:
                    restype = res[1]
                if len(res) > 2:
                    resname = res[2]
                if len(res) > 3:
                    reslang = res[3]
                try:
                    winresource.UpdateResourcesFromResFile(
                        tmpnm, resfile, [restype or "*"], [resname or "*"],
                        [reslang or "*"])
                except winresource.pywintypes.error as exc:
                    if exc.args[0] != winresource.ERROR_BAD_EXE_FORMAT:
                        logger.error(
                            "Error while updating resources in %s"
                            " from resource file %s",
                            tmpnm,
                            resfile,
                            exc_info=1)
                        continue

                    # Handle the case where the file contains no resources, and is
                    # intended as a single resource to be added to the exe.
                    if not restype or not resname:
                        logger.error("resource type and/or name not specified")
                        continue
                    if "*" in (restype, resname):
                        logger.error("no wildcards allowed for resource type "
                                     "and name when source file does not "
                                     "contain resources")
                        continue
                    try:
                        winresource.UpdateResourcesFromDataFile(
                            tmpnm, resfile, restype, [resname], [reslang or 0])
                    except winresource.pywintypes.error:
                        logger.error(
                            "Error while updating resource %s %s in %s"
                            " from data file %s",
                            restype,
                            resname,
                            tmpnm,
                            resfile,
                            exc_info=1)
            trash.append(tmpnm)
            exe = tmpnm

        # NOTE: Do not look up for bootloader file in the cache because it might
        #       get corrupted by UPX when UPX is available. See #1863 for details.

        if not self.append_pkg:
            logger.info("Copying bootloader exe to %s", self.name)
            self._copyfile(exe, self.name)
            logger.info("Copying archive to %s", self.pkgname)
            self._copyfile(self.pkg.name, self.pkgname)
        elif is_linux:
            self._copyfile(exe, self.name)
            logger.info("Appending archive to ELF section in EXE %s",
                        self.name)
            retcode, stdout, stderr = exec_command_all(
                'objcopy', '--add-section', 'pydata=%s' % self.pkg.name,
                self.name)
            logger.debug("objcopy returned %i", retcode)
            if stdout:
                logger.debug(stdout)
            if stderr:
                logger.debug(stderr)
            if retcode != 0:
                raise SystemError("objcopy Failure: %s" % stderr)
        else:
            # Fall back to just append on end of file
            logger.info("Appending archive to EXE %s", self.name)
            with open(self.name, 'wb') as outf:
                # write the bootloader data
                with open(exe, 'rb') as infh:
                    shutil.copyfileobj(infh, outf, length=64 * 1024)
                # write the archive data
                with open(self.pkg.name, 'rb') as infh:
                    shutil.copyfileobj(infh, outf, length=64 * 1024)

        if is_darwin:
            # Fix Mach-O header for codesigning on OS X.
            logger.info("Fixing EXE for code signing %s", self.name)
            import PyInstaller.utils.osx as osxutils
            osxutils.fix_exe_for_code_signing(self.name)

        os.chmod(self.name, 0o755)
        # get mtime for storing into the guts
        self.mtm = misc.mtime(self.name)
        for item in trash:
            os.remove(item)
        logger.info("Building EXE from %s completed successfully.",
                    self.tocbasename)
Beispiel #10
0
    def assemble(self):
        logger.info("Building EXE from %s", self.tocbasename)
        trash = []
        if not os.path.exists(os.path.dirname(self.name)):
            os.makedirs(os.path.dirname(self.name))
        outf = open(self.name, 'wb')
        exe = self.exefiles[0][1]  # pathname of bootloader
        if not os.path.exists(exe):
            raise SystemExit(_MISSING_BOOTLOADER_ERRORMSG)


        if is_win and (self.icon or self.versrsrc or self.resources):
            tmpnm = tempfile.mktemp()
            shutil.copy2(exe, tmpnm)
            os.chmod(tmpnm, 0o755)
            if self.icon:
                icon.CopyIcons(tmpnm, self.icon)
            if self.versrsrc:
                versioninfo.SetVersion(tmpnm, self.versrsrc)
            for res in self.resources:
                res = res.split(",")
                for i in range(1, len(res)):
                    try:
                        res[i] = int(res[i])
                    except ValueError:
                        pass
                resfile = res[0]
                restype = resname = reslang = None
                if len(res) > 1:
                    restype = res[1]
                if len(res) > 2:
                    resname = res[2]
                if len(res) > 3:
                    reslang = res[3]
                try:
                    winresource.UpdateResourcesFromResFile(tmpnm, resfile,
                                                        [restype or "*"],
                                                        [resname or "*"],
                                                        [reslang or "*"])
                except winresource.pywintypes.error as exc:
                    if exc.args[0] != winresource.ERROR_BAD_EXE_FORMAT:
                        logger.exception(exc)
                        continue
                    if not restype or not resname:
                        logger.error("resource type and/or name not specified")
                        continue
                    if "*" in (restype, resname):
                        logger.error("no wildcards allowed for resource type "
                                     "and name when source file does not "
                                     "contain resources")
                        continue
                    try:
                        winresource.UpdateResourcesFromDataFile(tmpnm,
                                                             resfile,
                                                             restype,
                                                             [resname],
                                                             [reslang or 0])
                    except winresource.pywintypes.error as exc:
                        logger.exception(exc)
            trash.append(tmpnm)
            exe = tmpnm
        exe = checkCache(exe, strip=self.strip, upx=self.upx)
        self.copy(exe, outf)
        if self.append_pkg:
            logger.info("Appending archive to EXE %s", self.name)
            self.copy(self.pkg.name, outf)
        else:
            logger.info("Copying archive to %s", self.pkgname)
            shutil.copy2(self.pkg.name, self.pkgname)
        outf.close()

        if is_darwin:
            # Fix Mach-O header for codesigning on OS X.
            logger.info("Fixing EXE for code signing %s", self.name)
            import PyInstaller.utils.osx as osxutils
            osxutils.fix_exe_for_code_signing(self.name)
            pass

        os.chmod(self.name, 0o755)
        # get mtime for storing into the guts
        self.mtm = misc.mtime(self.name)
        for item in trash:
            os.remove(item)
Beispiel #11
0
    def assemble(self):
        logger.info("Building EXE from %s", self.tocbasename)
        trash = []
        if os.path.exists(self.name):
            os.remove(self.name)
        if not os.path.exists(os.path.dirname(self.name)):
            os.makedirs(os.path.dirname(self.name))
        exe = self.exefiles[0][1]  # pathname of bootloader
        if not os.path.exists(exe):
            raise SystemExit(_MISSING_BOOTLOADER_ERRORMSG)


        if is_win and (self.icon or self.versrsrc or self.resources):
            tmpnm = tempfile.mktemp()
            self._copyfile(exe, tmpnm)
            os.chmod(tmpnm, 0o755)
            if self.icon:
                icon.CopyIcons(tmpnm, self.icon)
            if self.versrsrc:
                versioninfo.SetVersion(tmpnm, self.versrsrc)
            for res in self.resources:
                res = res.split(",")
                for i in range(1, len(res)):
                    try:
                        res[i] = int(res[i])
                    except ValueError:
                        pass
                resfile = res[0]
                restype = resname = reslang = None
                if len(res) > 1:
                    restype = res[1]
                if len(res) > 2:
                    resname = res[2]
                if len(res) > 3:
                    reslang = res[3]
                try:
                    winresource.UpdateResourcesFromResFile(tmpnm, resfile,
                                                        [restype or "*"],
                                                        [resname or "*"],
                                                        [reslang or "*"])
                except winresource.pywintypes.error as exc:
                    if exc.args[0] != winresource.ERROR_BAD_EXE_FORMAT:
                        logger.error("Error while updating resources in %s"
                                     " from resource file %s", tmpnm, resfile, exc_info=1)
                        continue

                    # Handle the case where the file contains no resources, and is
                    # intended as a single resource to be added to the exe.
                    if not restype or not resname:
                        logger.error("resource type and/or name not specified")
                        continue
                    if "*" in (restype, resname):
                        logger.error("no wildcards allowed for resource type "
                                     "and name when source file does not "
                                     "contain resources")
                        continue
                    try:
                        winresource.UpdateResourcesFromDataFile(tmpnm,
                                                             resfile,
                                                             restype,
                                                             [resname],
                                                             [reslang or 0])
                    except winresource.pywintypes.error:
                        logger.error("Error while updating resource %s %s in %s"
                                     " from data file %s",
                                     restype, resname, tmpnm, resfile, exc_info=1)
            trash.append(tmpnm)
            exe = tmpnm

        # NOTE: Do not look up for bootloader file in the cache because it might
        #       get corrupted by UPX when UPX is available. See #1863 for details.

        if not self.append_pkg:
            logger.info("Copying bootloader exe to %s", self.name)
            self._copyfile(exe, self.name)
            logger.info("Copying archive to %s", self.pkgname)
            self._copyfile(self.pkg.name, self.pkgname)
        elif is_linux:
            self._copyfile(exe, self.name)
            logger.info("Appending archive to ELF section in EXE %s", self.name)
            retcode, stdout, stderr = exec_command_all(
                'objcopy', '--add-section', 'pydata=%s' % self.pkg.name,
                self.name)
            logger.debug("objcopy returned %i", retcode)
            if stdout:
                logger.debug(stdout)
            if stderr:
                logger.debug(stderr)
            if retcode != 0:
                raise SystemError("objcopy Failure: %s" % stderr)
        else:
            # Fall back to just append on end of file
            logger.info("Appending archive to EXE %s", self.name)
            with open(self.name, 'wb') as outf:
                # write the bootloader data
                with open(exe, 'rb') as infh:
                    shutil.copyfileobj(infh, outf, length=64*1024)
                # write the archive data
                with open(self.pkg.name, 'rb') as infh:
                    shutil.copyfileobj(infh, outf, length=64*1024)

        if is_darwin:
            # Fix Mach-O header for codesigning on OS X.
            logger.info("Fixing EXE for code signing %s", self.name)
            import PyInstaller.utils.osx as osxutils
            osxutils.fix_exe_for_code_signing(self.name)

        os.chmod(self.name, 0o755)
        # get mtime for storing into the guts
        self.mtm = misc.mtime(self.name)
        for item in trash:
            os.remove(item)
        logger.info("Building EXE from %s completed successfully.",
                    self.tocbasename)
Beispiel #12
0
    def assemble(self):
        from PyInstaller.config import CONF
        logger.info("Building EXE from %s", self.tocbasename)
        if os.path.exists(self.name):
            os.remove(self.name)
        if not os.path.exists(os.path.dirname(self.name)):
            os.makedirs(os.path.dirname(self.name))
        exe = self.exefiles[0][1]  # pathname of bootloader
        if not os.path.exists(exe):
            raise SystemExit(_MISSING_BOOTLOADER_ERRORMSG)

        # Step 1: copy the bootloader file, and perform any operations that need to be done prior to appending the PKG.
        logger.info("Copying bootloader EXE to %s", self.name)
        self._copyfile(exe, self.name)
        os.chmod(self.name, 0o755)

        if is_win:
            # First, remove all resources from the file. This ensures that no manifest is embedded, even if bootloader
            # was compiled with a toolchain that forcibly embeds a default manifest (e.g., mingw toolchain from msys2).
            winresource.RemoveAllResources(self.name)
            # Embed icon.
            if self.icon != "NONE":
                logger.info("Copying icon to EXE")
                icon.CopyIcons(self.name, self.icon)
            # Embed version info.
            if self.versrsrc:
                logger.info("Copying version information to EXE")
                versioninfo.SetVersion(self.name, self.versrsrc)
            # Embed other resources.
            logger.info("Copying %d resources to EXE", len(self.resources))
            for res in self.resources:
                res = res.split(",")
                for i in range(1, len(res)):
                    try:
                        res[i] = int(res[i])
                    except ValueError:
                        pass
                resfile = res[0]
                if not os.path.isabs(resfile):
                    resfile = os.path.join(CONF['specpath'], resfile)
                restype = resname = reslang = None
                if len(res) > 1:
                    restype = res[1]
                if len(res) > 2:
                    resname = res[2]
                if len(res) > 3:
                    reslang = res[3]
                try:
                    winresource.UpdateResourcesFromResFile(
                        self.name, resfile, [restype or "*"], [resname or "*"], [reslang or "*"]
                    )
                except winresource.pywintypes.error as exc:
                    if exc.args[0] != winresource.ERROR_BAD_EXE_FORMAT:
                        logger.error(
                            "Error while updating resources in %s from resource file %s!",
                            self.name,
                            resfile,
                            exc_info=1
                        )
                        continue

                    # Handle the case where the file contains no resources, and is intended as a single resource to be
                    # added to the exe.
                    if not restype or not resname:
                        logger.error("Resource type and/or name not specified!")
                        continue
                    if "*" in (restype, resname):
                        logger.error(
                            "No wildcards allowed for resource type and name when the source file does not contain "
                            "any resources!"
                        )
                        continue
                    try:
                        winresource.UpdateResourcesFromDataFile(self.name, resfile, restype, [resname], [reslang or 0])
                    except winresource.pywintypes.error:
                        logger.error(
                            "Error while updating resource %s %s in %s from data file %s!",
                            restype,
                            resname,
                            self.name,
                            resfile,
                            exc_info=1
                        )
            # Embed the manifest into the executable.
            if self.embed_manifest:
                logger.info("Emedding manifest in EXE")
                self.manifest.update_resources(self.name, [1])
        elif is_darwin:
            # Convert bootloader to the target arch
            logger.info("Converting EXE to target arch (%s)", self.target_arch)
            osxutils.binary_to_target_arch(self.name, self.target_arch, display_name='Bootloader EXE')

        # Step 2: append the PKG, if necessary
        if not self.append_pkg:
            # In onefile mode, copy the stand-alone pkg next to the executable. In onedir, this will be done by the
            # COLLECT() target.
            if not self.exclude_binaries:
                pkg_dst = os.path.join(os.path.dirname(self.name), os.path.basename(self.pkgname))
                logger.info("Copying stand-alone PKG archive from %s to %s", self.pkg.name, pkg_dst)
                self._copyfile(self.pkg.name, pkg_dst)
            else:
                logger.info("Stand-alone PKG archive will be handled by COLLECT")
        elif is_linux:
            # Linux: append PKG into ELF section using objcopy
            logger.info("Appending PKG archive to ELF section in EXE")
            retcode, stdout, stderr = exec_command_all(
                'objcopy', '--add-section', 'pydata=%s' % self.pkg.name, self.name
            )
            logger.debug("objcopy returned %i", retcode)
            if stdout:
                logger.debug(stdout)
            if stderr:
                logger.debug(stderr)
            if retcode != 0:
                raise SystemError("objcopy Failure: %s" % stderr)
        elif is_darwin:
            # macOS: remove signature, append PKG, and fix-up headers so that PKG appears to be part of the executable.

            # Strip signatures from all arch slices. Strictly speaking, we need to remove signature (if present) from
            # the last slice, because we will be appending data to it. When building universal2 bootloaders natively on
            # macOS, only arm64 slices have a (dummy) signature. However, when cross-compiling with osxcross, we seem to
            # get dummy signatures on both x86_64 and arm64 slices. While the former should not have any impact, it does
            # seem to cause issues with further binary signing using real identity. Therefore, we remove all signatures
            # and re-sign the binary using dummy signature once the data is appended.
            logger.info("Removing signature(s) from EXE")
            osxutils.remove_signature_from_binary(self.name)

            # Append the PKG data
            logger.info("Appending PKG archive to EXE")
            with open(self.name, 'ab') as outf:
                with open(self.pkg.name, 'rb') as inf:
                    shutil.copyfileobj(inf, outf, length=64 * 1024)

            # Fix Mach-O header for code signing
            logger.info("Fixing EXE headers for code signing")
            osxutils.fix_exe_for_code_signing(self.name)
        else:
            # Fall back to just appending PKG at the end of the file
            logger.info("Appending PKG archive to EXE")
            with open(self.name, 'ab') as outf:
                with open(self.pkg.name, 'rb') as inf:
                    shutil.copyfileobj(inf, outf, length=64 * 1024)

        # Step 3: post-processing
        if is_win:
            # Set checksum to appease antiviral software.
            set_exe_checksum(self.name)
        elif is_darwin:
            # If the version of macOS SDK used to build bootloader exceeds that of macOS SDK used to built Python
            # library (and, by extension, bundled Tcl/Tk libraries), force the version declared by the frozen executable
            # to match that of the Python library.
            # Having macOS attempt to enable new features (based on SDK version) for frozen application has no benefit
            # if the Python library does not support them as well.
            # On the other hand, there seem to be UI issues in tkinter due to failed or partial enablement of dark mode
            # (i.e., the bootloader executable being built against SDK 10.14 or later, which causes macOS to enable dark
            # mode, and Tk libraries being built against an earlier SDK version that does not support the dark mode).
            # With python.org Intel macOS installers, this manifests as black Tk windows and UI elements (see issue
            # #5827), while in Anaconda python, it may result in white text on bright background.
            pylib_version = osxutils.get_macos_sdk_version(bindepend.get_python_library_path())
            exe_version = osxutils.get_macos_sdk_version(self.name)
            if pylib_version < exe_version:
                logger.info(
                    "Rewriting the executable's macOS SDK version (%d.%d.%d) to match the SDK version of the Python "
                    "library (%d.%d.%d) in order to avoid inconsistent behavior and potential UI issues in the "
                    "frozen application.", *exe_version, *pylib_version
                )
                osxutils.set_macos_sdk_version(self.name, *pylib_version)

            # Re-sign the binary (either ad-hoc or using real identity, if provided).
            logger.info("Re-signing the EXE")
            osxutils.sign_binary(self.name, self.codesign_identity, self.entitlements_file)

        # Ensure executable flag is set
        os.chmod(self.name, 0o755)
        # Get mtime for storing into the guts
        self.mtm = misc.mtime(self.name)
        logger.info("Building EXE from %s completed successfully.", self.tocbasename)
Beispiel #13
0
    def assemble(self):
        from PyInstaller.config import CONF
        logger.info("Building EXE from %s", self.tocbasename)
        trash = []
        if os.path.exists(self.name):
            os.remove(self.name)
        if not os.path.exists(os.path.dirname(self.name)):
            os.makedirs(os.path.dirname(self.name))
        exe = self.exefiles[0][1]  # pathname of bootloader
        if not os.path.exists(exe):
            raise SystemExit(_MISSING_BOOTLOADER_ERRORMSG)

        if is_win:
            fd, tmpnm = tempfile.mkstemp(prefix=os.path.basename(exe) + ".",
                                         dir=CONF['workpath'])
            # need to close the file, otherwise copying resources will fail
            # with "the file [...] is being used by another process"
            os.close(fd)
            self._copyfile(exe, tmpnm)
            os.chmod(tmpnm, 0o755)
            if self.icon != "NONE":
                icon.CopyIcons(tmpnm, self.icon)
            if self.versrsrc:
                versioninfo.SetVersion(tmpnm, self.versrsrc)
            for res in self.resources:
                res = res.split(",")
                for i in range(1, len(res)):
                    try:
                        res[i] = int(res[i])
                    except ValueError:
                        pass
                resfile = res[0]
                if not os.path.isabs(resfile):
                    resfile = os.path.join(CONF['specpath'], resfile)
                restype = resname = reslang = None
                if len(res) > 1:
                    restype = res[1]
                if len(res) > 2:
                    resname = res[2]
                if len(res) > 3:
                    reslang = res[3]
                try:
                    winresource.UpdateResourcesFromResFile(
                        tmpnm, resfile, [restype or "*"], [resname or "*"],
                        [reslang or "*"])
                except winresource.pywintypes.error as exc:
                    if exc.args[0] != winresource.ERROR_BAD_EXE_FORMAT:
                        logger.error(
                            "Error while updating resources in %s"
                            " from resource file %s",
                            tmpnm,
                            resfile,
                            exc_info=1)
                        continue

                    # Handle the case where the file contains no resources, and is
                    # intended as a single resource to be added to the exe.
                    if not restype or not resname:
                        logger.error("resource type and/or name not specified")
                        continue
                    if "*" in (restype, resname):
                        logger.error("no wildcards allowed for resource type "
                                     "and name when source file does not "
                                     "contain resources")
                        continue
                    try:
                        winresource.UpdateResourcesFromDataFile(
                            tmpnm, resfile, restype, [resname], [reslang or 0])
                    except winresource.pywintypes.error:
                        logger.error(
                            "Error while updating resource %s %s in %s"
                            " from data file %s",
                            restype,
                            resname,
                            tmpnm,
                            resfile,
                            exc_info=1)
            if self.manifest and not self.exclude_binaries:
                self.manifest.update_resources(tmpnm, [1])
            trash.append(tmpnm)
            exe = tmpnm

        # NOTE: Do not look up for bootloader file in the cache because it might
        #       get corrupted by UPX when UPX is available. See #1863 for details.

        if not self.append_pkg:
            logger.info("Copying bootloader exe to %s", self.name)
            self._copyfile(exe, self.name)
            logger.info("Copying archive to %s", self.pkgname)
            self._copyfile(self.pkg.name, self.pkgname)
        elif is_linux:
            self._copyfile(exe, self.name)
            logger.info("Appending archive to ELF section in EXE %s",
                        self.name)
            retcode, stdout, stderr = exec_command_all(
                'objcopy', '--add-section', 'pydata=%s' % self.pkg.name,
                self.name)
            logger.debug("objcopy returned %i", retcode)
            if stdout:
                logger.debug(stdout)
            if stderr:
                logger.debug(stderr)
            if retcode != 0:
                raise SystemError("objcopy Failure: %s" % stderr)
        elif is_darwin:
            import PyInstaller.utils.osx as osxutils

            # Copy bootloader
            logger.info("Copying bootloader exe to %s", self.name)
            with open(self.name, 'wb') as outf:
                with open(exe, 'rb') as inf:
                    shutil.copyfileobj(inf, outf, length=64 * 1024)

            # Convert bootloader to target arch
            logger.info("Converting EXE to target arch (%s)", self.target_arch)
            osxutils.binary_to_target_arch(self.name,
                                           self.target_arch,
                                           display_name='Bootloader EXE')

            # Strip signatures from all arch slices. Strictly speaking,
            # we need to remove signature (if present) from the last
            # slice, because we will be appending data to it. When
            # building universal2 bootloaders natively on macOS, only
            # arm64 slices have a (dummy) signature. However, when
            # cross-compiling with osxcross, we seem to get dummy
            # signatures on both x86_64 and arm64 slices. While the former
            # should not have any impact, it does seem to cause issues
            # with further binary signing using real identity. Therefore,
            # we remove all signatures and re-sign the binary using
            # dummy signature once the data is appended.
            logger.info("Removing signature(s) from EXE")
            osxutils.remove_signature_from_binary(self.name)

            # Append the data
            with open(self.name, 'ab') as outf:
                with open(self.pkg.name, 'rb') as inf:
                    shutil.copyfileobj(inf, outf, length=64 * 1024)

            # If the version of macOS SDK used to build bootloader exceeds
            # that of macOS SDK used to built Python library (and, by
            # extension, bundled Tcl/Tk libraries), force the version
            # declared by the frozen executable to match that of the Python
            # library.
            # Having macOS attempt to enable new features (based on SDK
            # version) for frozen application has no benefit if the Python
            # library does not support them as well.
            # On the other hand, there seem to be UI issues in tkinter
            # due to failed or partial enablement of dark mode (i.e., the
            # bootloader executable being built against SDK 10.14 or later,
            # which causes macOS to enable dark mode, and Tk libraries being
            # built against an earlier SDK version that does not support the
            # dark mode). With python.org Intel macOS installers, this
            # manifests as black Tk windows and UI elements (see issue #5827),
            # while in Anaconda python, it may result in white text on bright
            # background.
            pylib_version = osxutils.get_macos_sdk_version(
                bindepend.get_python_library_path())
            exe_version = osxutils.get_macos_sdk_version(self.name)
            if pylib_version < exe_version:
                logger.info(
                    "Rewriting executable's macOS SDK version (%d.%d.%d) to "
                    "match the SDK version of the Python library (%d.%d.%d) "
                    "in order to avoid inconsistent behavior and potential UI "
                    "issues in the frozen application.", *exe_version,
                    *pylib_version)
                osxutils.set_macos_sdk_version(self.name, *pylib_version)

            # Fix Mach-O header for codesigning on OS X.
            logger.info("Fixing EXE for code signing %s", self.name)
            osxutils.fix_exe_for_code_signing(self.name)

            # Re-sign the binary (either ad-hoc or using real identity,
            # if provided)
            logger.info("Re-signing the EXE")
            osxutils.sign_binary(self.name, self.codesign_identity,
                                 self.entitlements_file)
        else:
            # Fall back to just append on end of file
            logger.info("Appending archive to EXE %s", self.name)
            with open(self.name, 'wb') as outf:
                # write the bootloader data
                with open(exe, 'rb') as infh:
                    shutil.copyfileobj(infh, outf, length=64 * 1024)
                # write the archive data
                with open(self.pkg.name, 'rb') as infh:
                    shutil.copyfileobj(infh, outf, length=64 * 1024)

        if is_win:
            # Set checksum to appease antiviral software.
            from PyInstaller.utils.win32.winutils import set_exe_checksum
            set_exe_checksum(self.name)

        os.chmod(self.name, 0o755)
        # get mtime for storing into the guts
        self.mtm = misc.mtime(self.name)
        for item in trash:
            os.remove(item)
        logger.info("Building EXE from %s completed successfully.",
                    self.tocbasename)