зеркало из https://github.com/mozilla/gecko-dev.git
174 строки
6.2 KiB
Python
174 строки
6.2 KiB
Python
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
from mozpack.files import (
|
|
FileFinder,
|
|
ExecutableFile,
|
|
BaseFile,
|
|
GeneratedFile,
|
|
)
|
|
from mozpack.executables import (
|
|
MACHO_SIGNATURES,
|
|
strip,
|
|
)
|
|
from mozpack.errors import errors
|
|
from tempfile import mkstemp
|
|
import mozpack.path
|
|
import shutil
|
|
import struct
|
|
import os
|
|
import subprocess
|
|
from collections import OrderedDict
|
|
|
|
|
|
def may_unify_binary(file):
|
|
'''
|
|
Return whether the given BaseFile instance is an ExecutableFile that
|
|
may be unified. Only non-fat Mach-O binaries are to be unified.
|
|
'''
|
|
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):
|
|
'''
|
|
File class for executable and library files that to be unified with 'lipo'.
|
|
'''
|
|
def __init__(self, path1, path2):
|
|
'''
|
|
Initialize a UnifiedExecutableFile with the path to both non-fat Mach-O
|
|
executables to be unified.
|
|
'''
|
|
self.path1 = path1
|
|
self.path2 = path2
|
|
|
|
def copy(self, dest):
|
|
assert isinstance(dest, basestring)
|
|
tmpfiles = []
|
|
try:
|
|
for p in [self.path1, self.path2]:
|
|
fd, f = mkstemp()
|
|
os.close(fd)
|
|
tmpfiles.append(f)
|
|
shutil.copy2(p, f)
|
|
strip(f)
|
|
subprocess.call(['lipo', '-create'] + tmpfiles + ['-output', dest])
|
|
finally:
|
|
for f in tmpfiles:
|
|
os.unlink(f)
|
|
|
|
|
|
class UnifiedFinder(FileFinder):
|
|
'''
|
|
Helper to get unified BaseFile instances from two distinct trees on the
|
|
file system.
|
|
'''
|
|
def __init__(self, base1, base2, sorted=[], **kargs):
|
|
'''
|
|
Initialize a UnifiedFinder. base1 and base2 are the base directories
|
|
for the two trees from which files are picked. UnifiedFinder.find()
|
|
will act as FileFinder.find() but will error out when matches can only
|
|
be found in one of the two trees and not the other. It will also error
|
|
out if matches can be found on both ends but their contents are not
|
|
identical.
|
|
|
|
The sorted argument gives a list of mozpack.path.match patterns. File
|
|
paths matching one of these patterns will have their contents compared
|
|
with their lines sorted.
|
|
'''
|
|
self._base1 = FileFinder(base1, **kargs)
|
|
self._base2 = FileFinder(base2, **kargs)
|
|
self._sorted = sorted
|
|
|
|
def _find(self, path):
|
|
'''
|
|
UnifiedFinder.find() implementation.
|
|
'''
|
|
files1 = OrderedDict()
|
|
for p, f in self._base1.find(path):
|
|
files1[p] = f
|
|
files2 = set()
|
|
for p, f in self._base2.find(path):
|
|
files2.add(p)
|
|
if p in files1:
|
|
if may_unify_binary(files1[p]) and \
|
|
may_unify_binary(f):
|
|
yield p, UnifiedExecutableFile(files1[p].path, f.path)
|
|
else:
|
|
unified = self.unify_file(p, files1[p], f)
|
|
if unified:
|
|
yield p, unified
|
|
else:
|
|
self._report_difference(p, files1[p], f)
|
|
else:
|
|
errors.error('File missing in %s: %s' % (self._base1.base, p))
|
|
for p in [p for p in files1 if not p in files2]:
|
|
errors.error('File missing in %s: %s' % (self._base2.base, p))
|
|
|
|
def _report_difference(self, path, file1, file2):
|
|
'''
|
|
Report differences between files in both trees.
|
|
'''
|
|
errors.error("Can't unify %s: file differs between %s and %s" %
|
|
(path, self._base1.base, self._base2.base))
|
|
if not isinstance(file1, ExecutableFile) and \
|
|
not isinstance(file2, ExecutableFile):
|
|
from difflib import unified_diff
|
|
import sys
|
|
for line in unified_diff(file1.open().readlines(),
|
|
file2.open().readlines(),
|
|
os.path.join(self._base1.base, path),
|
|
os.path.join(self._base2.base, path)):
|
|
errors.out.write(line)
|
|
|
|
def unify_file(self, path, file1, file2):
|
|
'''
|
|
Given two BaseFiles and the path they were found at, check whether
|
|
their content match and return the first BaseFile if they do.
|
|
'''
|
|
content1 = file1.open().readlines()
|
|
content2 = file2.open().readlines()
|
|
if content1 == content2:
|
|
return file1
|
|
for pattern in self._sorted:
|
|
if mozpack.path.match(path, pattern):
|
|
if sorted(content1) == sorted(content2):
|
|
return file1
|
|
break
|
|
return None
|
|
|
|
|
|
class UnifiedBuildFinder(UnifiedFinder):
|
|
'''
|
|
Specialized UnifiedFinder for Mozilla applications packaging. It allows
|
|
"*.manifest" files to differ in their order, and unifies "buildconfig.html"
|
|
files by merging their content.
|
|
'''
|
|
def __init__(self, base1, base2, **kargs):
|
|
UnifiedFinder.__init__(self, base1, base2,
|
|
sorted=['**/*.manifest'], **kargs)
|
|
|
|
def unify_file(self, path, file1, file2):
|
|
'''
|
|
Unify buildconfig.html contents, or defer to UnifiedFinder.unify_file.
|
|
'''
|
|
if mozpack.path.basename(path) == 'buildconfig.html':
|
|
content1 = file1.open().readlines()
|
|
content2 = file2.open().readlines()
|
|
# Copy everything from the first file up to the end of its <body>,
|
|
# insert a <hr> between the two files and copy the second file's
|
|
# content beginning after its leading <h1>.
|
|
return GeneratedFile(''.join(
|
|
content1[:content1.index('</body>\n')] +
|
|
['<hr> </hr>\n'] +
|
|
content2[content2.index('<h1>about:buildconfig</h1>\n') + 1:]
|
|
))
|
|
return UnifiedFinder.unify_file(self, path, file1, file2)
|