1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this 3# file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5from __future__ import absolute_import, print_function, unicode_literals 6 7import os 8import struct 9import subprocess 10from mozpack.errors import errors 11 12MACHO_SIGNATURES = [ 13 0xFEEDFACE, # mach-o 32-bits big endian 14 0xCEFAEDFE, # mach-o 32-bits little endian 15 0xFEEDFACF, # mach-o 64-bits big endian 16 0xCFFAEDFE, # mach-o 64-bits little endian 17] 18 19FAT_SIGNATURE = 0xCAFEBABE # mach-o FAT binary 20 21ELF_SIGNATURE = 0x7F454C46 # Elf binary 22 23UNKNOWN = 0 24MACHO = 1 25ELF = 2 26 27 28def get_type(path): 29 """ 30 Check the signature of the give file and returns what kind of executable 31 matches. 32 """ 33 with open(path, "rb") as f: 34 signature = f.read(4) 35 if len(signature) < 4: 36 return UNKNOWN 37 signature = struct.unpack(">L", signature)[0] 38 if signature == ELF_SIGNATURE: 39 return ELF 40 if signature in MACHO_SIGNATURES: 41 return MACHO 42 if signature != FAT_SIGNATURE: 43 return UNKNOWN 44 # We have to sanity check the second four bytes, because Java class 45 # files use the same magic number as Mach-O fat binaries. 46 # This logic is adapted from file(1), which says that Mach-O uses 47 # these bytes to count the number of architectures within, while 48 # Java uses it for a version number. Conveniently, there are only 49 # 18 labelled Mach-O architectures, and Java's first released 50 # class format used the version 43.0. 51 num = f.read(4) 52 if len(num) < 4: 53 return UNKNOWN 54 num = struct.unpack(">L", num)[0] 55 if num < 20: 56 return MACHO 57 return UNKNOWN 58 59 60def is_executable(path): 61 """ 62 Return whether a given file path points to an executable or a library, 63 where an executable or library is identified by: 64 - the file extension on OS/2 and WINNT 65 - the file signature on OS/X and ELF systems (GNU/Linux, Android, BSD, 66 Solaris) 67 68 As this function is intended for use to choose between the ExecutableFile 69 and File classes in FileFinder, and choosing ExecutableFile only matters 70 on OS/2, OS/X, ELF and WINNT (in GCC build) systems, we don't bother 71 detecting other kind of executables. 72 """ 73 from buildconfig import substs 74 75 if not os.path.exists(path): 76 return False 77 78 if substs["OS_ARCH"] == "WINNT": 79 return path.lower().endswith((substs["DLL_SUFFIX"], substs["BIN_SUFFIX"])) 80 81 return get_type(path) != UNKNOWN 82 83 84def may_strip(path): 85 """ 86 Return whether strip() should be called 87 """ 88 from buildconfig import substs 89 90 # Bug 1658632: clang-11-based strip complains about d3dcompiler_47.dll. 91 # It's not clear why this happens, but as a quick fix just avoid stripping 92 # this DLL. It's not from our build anyway. 93 if "d3dcompiler" in path: 94 return False 95 return bool(substs.get("PKG_STRIP")) 96 97 98def strip(path): 99 """ 100 Execute the STRIP command with STRIP_FLAGS on the given path. 101 """ 102 from buildconfig import substs 103 104 strip = substs["STRIP"] 105 flags = substs.get("STRIP_FLAGS", []) 106 cmd = [strip] + flags + [path] 107 if subprocess.call(cmd) != 0: 108 errors.fatal("Error executing " + " ".join(cmd)) 109 110 111def may_elfhack(path): 112 """ 113 Return whether elfhack() should be called 114 """ 115 # elfhack only supports libraries. We should check the ELF header for 116 # the right flag, but checking the file extension works too. 117 from buildconfig import substs 118 119 return ( 120 "USE_ELF_HACK" in substs 121 and substs["USE_ELF_HACK"] 122 and path.endswith(substs["DLL_SUFFIX"]) 123 and "COMPILE_ENVIRONMENT" in substs 124 and substs["COMPILE_ENVIRONMENT"] 125 ) 126 127 128def elfhack(path): 129 """ 130 Execute the elfhack command on the given path. 131 """ 132 from buildconfig import topobjdir 133 134 cmd = [os.path.join(topobjdir, "build/unix/elfhack/elfhack"), path] 135 if subprocess.call(cmd) != 0: 136 errors.fatal("Error executing " + " ".join(cmd)) 137 138 139def xz_compress(path): 140 """ 141 Execute xz to compress the given path. 142 """ 143 if open(path, "rb").read(5)[1:] == "7zXZ": 144 print("%s is already compressed" % path) 145 return 146 147 from buildconfig import substs 148 149 xz = substs.get("XZ") 150 cmd = [xz, "-zkf", path] 151 152 # For now, the mozglue XZStream ELF loader can only support xz files 153 # with a single stream that contains a single block. In xz, there is no 154 # explicit option to set the max block count. Instead, we force xz to use 155 # single thread mode, which results in a single block. 156 cmd.extend(["--threads=1"]) 157 158 bcj = None 159 if substs.get("MOZ_THUMB2"): 160 bcj = "--armthumb" 161 elif substs.get("CPU_ARCH") == "arm": 162 bcj = "--arm" 163 elif substs.get("CPU_ARCH") == "x86": 164 bcj = "--x86" 165 166 if bcj: 167 cmd.extend([bcj]) 168 169 # We need to explicitly specify the LZMA filter chain to ensure consistent builds 170 # across platforms. Note that the dict size must be less then 16MiB per the hardcoded 171 # value in mozglue/linker/XZStream.cpp. This is the default LZMA filter chain for for 172 # xz-utils version 5.0. See: 173 # https://github.com/xz-mirror/xz/blob/v5.0.0/src/liblzma/lzma/lzma_encoder_presets.c 174 # https://github.com/xz-mirror/xz/blob/v5.0.0/src/liblzma/api/lzma/container.h#L31 175 cmd.extend(["--lzma2=dict=8MiB,lc=3,lp=0,pb=2,mode=normal,nice=64,mf=bt4,depth=0"]) 176 print("xz-compressing %s with %s" % (path, " ".join(cmd))) 177 178 if subprocess.call(cmd) != 0: 179 errors.fatal("Error executing " + " ".join(cmd)) 180 return 181 182 os.rename(path + ".xz", path) 183