blob: 30e881d2f4713cf0c9aab688f76273ada32a2e36 [file] [log] [blame]
# Copyright (c) 2014 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import posixpath
import shutil
import stat
import tarfile
from webports import configuration, package, util, error
PAYLOAD_DIR = 'payload'
INSTALL_PREFIX = '/webports-dummydir'
def make_dir_if_needed(filename):
dirname = os.path.dirname(filename)
if not os.path.isdir(dirname):
util.makedirs(dirname)
def filter_out_executables(filenames, root):
"""Filter out ELF binaries in the bin directory.
We don't want NaCl exectuables installed in the toolchain's bin directory
since we add this to the PATH during the build process, and NaCl executables
can't be run on the host system (not without sel_ldr anyway).
"""
rtn = []
for name in filenames:
full_name = os.path.join(root, name)
if os.path.split(name)[0] == 'bin':
if not os.path.splitext(name)[1] and util.is_elf_file(full_name):
continue
rtn.append(name)
return rtn
def install_file(filename, old_root, new_root):
"""Install a single file by moving it into a new location.
Args:
filename: Relative name of file to install.
old_root: The current location of the file.
new_root: The new desired root for the file.
"""
oldname = os.path.join(old_root, filename)
util.log_verbose('install: %s' % filename)
newname = os.path.join(new_root, filename)
dirname = os.path.dirname(newname)
if not os.path.isdir(dirname):
util.makedirs(dirname)
os.rename(oldname, newname)
# When install binaries ELF files into the toolchain direcoties, remove
# the X bit so that they do not found when searching the PATH.
if util.is_elf_file(newname) or util.is_pexe_file(newname):
mode = os.stat(newname).st_mode
mode = mode & ~(stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
os.chmod(newname, mode)
def relocate_file(filename, dest):
"""Perform in-place mutations on file contents to handle new location.
There are a few file types that have absolute pathnames embedded
and need to be modified in some way when being installed to
a particular location. For most file types this method does nothing.
"""
# Only relocate certain file types.
modify = False
# boost build scripts
# TODO(sbc): move this to the boost package metadata
if filename.startswith('build-1'):
modify = True
# pkg_config (.pc) files
if filename.startswith('lib/pkgconfig'):
modify = True
if filename.startswith('share/pkgconfig'):
modify = True
# <foo>-config scripts that live in usr/bin
if filename.startswith('bin') and filename.endswith('-config'):
modify = True
# libtool's .la files which can contain absolute paths to
# dependencies.
if filename.endswith('.la'):
modify = True
# headers can sometimes also contain absolute paths.
if filename.startswith('include/') and filename.endswith('.h'):
modify = True
filename = os.path.join(dest, filename)
if modify:
with open(filename) as f:
data = f.read()
mode = os.stat(filename).st_mode
os.chmod(filename, 0777)
with open(filename, 'r+') as f:
f.write(data.replace(INSTALL_PREFIX, dest))
os.chmod(filename, mode)
class BinaryPackage(package.Package):
"""Representation of binary package packge file.
This class is initialised with the filename of a binary package
and its attributes are set according the file name and contents.
Operations such as installation can be performed on the package.
"""
extra_keys = package.EXTRA_KEYS
def __init__(self, filename):
util.trace('BinaryPackage: %s' % filename)
super(BinaryPackage, self).__init__()
self.filename = filename
self.info = filename
self.verify_archive_format()
info = self.get_pkg_info()
self.parse_info(info)
self.config = configuration.Configuration(self.BUILD_ARCH,
self.BUILD_TOOLCHAIN,
self.BUILD_CONFIG == 'debug')
def verify_archive_format(self):
if not os.path.exists(self.filename):
raise error.Error('package archive not found: %s' % self.filename)
basename, extension = os.path.splitext(os.path.basename(self.filename))
basename = os.path.splitext(basename)[0]
if extension != '.bz2':
raise error.Error('invalid file extension: %s' % extension)
try:
with tarfile.open(self.filename):
pass
except tarfile.TarError as e:
raise error.PkgFormatError(e)
def is_installable(self):
"""Determine if a binary package can be installed in the
currently configured SDK.
Currently only packages built with the same SDK major version
are installable.
"""
return self.BUILD_SDK_VERSION == util.get_sdk_version()
def get_pkg_info(self):
"""Extract the contents of the pkg_info file from the binary package."""
with tarfile.open(self.filename) as tar:
for member in tar:
if member.name != 'pkg_info':
raise error.PkgFormatError('pkg_info not first member in archive')
return tar.extractfile(member).read()
def install(self, force):
"""Install binary package into toolchain directory."""
util.trace('Installing %s' % self.filename)
with util.InstallLock(self.config):
self._install(force)
def _install(self, force):
if self.TOOLCHAIN_INSTALL != '0':
self._install_files(force)
self.write_stamp()
def _install_files(self, force):
dest = util.get_install_root(self.config)
dest_tmp = os.path.join(dest, 'install_tmp')
if os.path.exists(dest_tmp):
shutil.rmtree(dest_tmp)
if self.is_any_version_installed():
raise error.Error('package already installed: %s' % self.info_string())
self.log_status('Installing')
util.log_verbose('installing from: %s' % self.filename)
util.makedirs(dest_tmp)
names = []
try:
with tarfile.open(self.filename) as tar:
for info in tar:
if info.isdir():
continue
name = posixpath.normpath(info.name)
if name == 'pkg_info':
continue
if not name.startswith(PAYLOAD_DIR + '/'):
raise error.PkgFormatError('invalid file in package: %s' % name)
name = name[len(PAYLOAD_DIR) + 1:]
names.append(name)
if not force:
for name in names:
full_name = os.path.join(dest, name)
if os.path.exists(full_name):
raise error.Error('file already exists: %s' % full_name)
tar.extractall(dest_tmp)
payload_tree = os.path.join(dest_tmp, PAYLOAD_DIR)
names = filter_out_executables(names, payload_tree)
for name in names:
install_file(name, payload_tree, dest)
finally:
shutil.rmtree(dest_tmp)
for name in names:
relocate_file(name, dest)
self.write_file_list(names)
def write_stamp(self):
"""Write stamp file containing pkg_info."""
filename = util.get_install_stamp(self.NAME, self.config)
make_dir_if_needed(filename)
util.log_verbose('stamp: %s' % filename)
pkg_info = self.get_pkg_info()
with open(filename, 'w') as f:
f.write(pkg_info)
def write_file_list(self, file_names):
"""Write the file list for this package."""
filename = self.get_list_file()
make_dir_if_needed(filename)
with open(filename, 'w') as f:
for name in file_names:
f.write(name + '\n')