import os
import re
import struct
import subprocess
from collections import OrderedDict
from tempfile import mkstemp
import buildconfig
import mozpack.path as mozpath
from mozbuild.util import hexdump
from mozpack.errors import errors
from mozpack.executables import MACHO_SIGNATURES
from mozpack.files import BaseFile, BaseFinder, ExecutableFile, GeneratedFile
FIND_TARGET_PLATFORM = re.compile(
r"""
<(?P<ns>[-._0-9A-Za-z]+:)?targetPlatform> (?P<platform>[^<]*) </(?P=ns)?targetPlatform> """,
re.X,
)
FIND_TARGET_PLATFORM_ATTR = re.compile(
r"""
(?P<tag><(?:[-._0-9A-Za-z]+:)?Description) (?P<attrs>[^>]*?)\s+ (?P<ns>[-._0-9A-Za-z]+:)?targetPlatform= [\'"](?P<platform>[^\'"]+)[\'"] (?P<otherattrs>[^>]*?>) """,
re.X,
)
def may_unify_binary(file):
if isinstance(file, ExecutableFile):
signature = file.open().read(4)
if len(signature) < 4:
return False
signature = struct.unpack(">L", signature)[0]
if signature in MACHO_SIGNATURES:
return True
return False
class UnifiedExecutableFile(BaseFile):
def __init__(self, executable1, executable2):
assert isinstance(executable1, ExecutableFile)
assert isinstance(executable2, ExecutableFile)
self._executables = (executable1, executable2)
def copy(self, dest, skip_if_older=True):
assert isinstance(dest, str)
tmpfiles = []
try:
for e in self._executables:
fd, f = mkstemp()
os.close(fd)
tmpfiles.append(f)
e.copy(f, skip_if_older=False)
lipo = buildconfig.substs.get("LIPO") or "lipo"
subprocess.check_call([lipo, "-create"] + tmpfiles + ["-output", dest])
except Exception as e:
errors.error(
"Failed to unify %s and %s: %s"
% (self._executables[0].path, self._executables[1].path, str(e))
)
finally:
for f in tmpfiles:
os.unlink(f)
class UnifiedFinder(BaseFinder):
def __init__(self, finder1, finder2, sorted=[], **kargs):
assert isinstance(finder1, BaseFinder)
assert isinstance(finder2, BaseFinder)
self._finder1 = finder1
self._finder2 = finder2
self._sorted = sorted
BaseFinder.__init__(self, finder1.base, **kargs)
def _find(self, path):
all_paths = OrderedDict()
files1 = OrderedDict()
for p, f in self._finder1.find(path):
files1[p] = f
all_paths[p] = True
files2 = OrderedDict()
for p, f in self._finder2.find(path):
files2[p] = f
all_paths[p] = True
for p in all_paths:
err = errors.count
unified = self.unify_file(p, files1.get(p), files2.get(p))
if unified:
yield p, unified
elif err == errors.count: self._report_difference(p, files1.get(p), files2.get(p))
def _report_difference(self, path, file1, file2):
if not file1:
errors.error("File missing in %s: %s" % (self._finder1.base, path))
return
if not file2:
errors.error("File missing in %s: %s" % (self._finder2.base, path))
return
errors.error(
"Can't unify %s: file differs between %s and %s"
% (path, self._finder1.base, self._finder2.base)
)
if not isinstance(file1, ExecutableFile) and not isinstance(
file2, ExecutableFile
):
from difflib import unified_diff
try:
lines1 = [l.decode("utf-8") for l in file1.open().readlines()]
lines2 = [l.decode("utf-8") for l in file2.open().readlines()]
except UnicodeDecodeError:
lines1 = hexdump(file1.open().read())
lines2 = hexdump(file2.open().read())
for line in unified_diff(
lines1,
lines2,
os.path.join(self._finder1.base, path),
os.path.join(self._finder2.base, path),
):
errors.out.write(line)
def unify_file(self, path, file1, file2):
if not file1 or not file2:
return None
if may_unify_binary(file1) and may_unify_binary(file2):
return UnifiedExecutableFile(file1, file2)
content1 = file1.open().readlines()
content2 = file2.open().readlines()
if content1 == content2:
return file1
for pattern in self._sorted:
if mozpath.match(path, pattern):
if sorted(content1) == sorted(content2):
return file1
break
return None
class UnifiedBuildFinder(UnifiedFinder):
def __init__(self, finder1, finder2, **kargs):
UnifiedFinder.__init__(
self, finder1, finder2, sorted=["**/*.manifest"], **kargs
)
def unify_file(self, path, file1, file2):
basename = mozpath.basename(path)
if file1 and file2 and basename == "buildconfig.html":
content1 = file1.open().readlines()
content2 = file2.open().readlines()
return GeneratedFile(
b"".join(
content1[: content1.index(b" </div>\n")]
+ [b" <hr> </hr>\n"]
+ content2[
content2.index(b" <h1>Build Configuration</h1>\n") + 1 :
]
)
)
elif file1 and file2 and basename == "install.rdf":
content1, content2 = (
FIND_TARGET_PLATFORM_ATTR.sub(
lambda m: m.group("tag")
+ m.group("attrs")
+ m.group("otherattrs")
+ "<%stargetPlatform>%s</%stargetPlatform>"
% (m.group("ns") or "", m.group("platform"), m.group("ns") or ""),
f.open().read().decode("utf-8"),
)
for f in (file1, file2)
)
platform2 = FIND_TARGET_PLATFORM.search(content2)
return GeneratedFile(
FIND_TARGET_PLATFORM.sub(
lambda m: m.group(0) + platform2.group(0) if platform2 else "",
content1,
)
)
return UnifiedFinder.unify_file(self, path, file1, file2)