# Copyright (C) 2012-2015 Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see .
import os
import morphlib
# TODO: Make idempotent when files are hardlinks
# Strip all ELF binary files that are executable or named like a library.
# .so files for C, .cmxs for OCaml and .node for Node.
# The file name and permissions checks are done with the `find` command before
# the ELF header is checked with the shell command, because it is a lot cheaper
# to check the mode and file name first, because it is a metadata check, rather
# than a subprocess and a file read.
# `file` is not used, to keep the dependency requirements down.
_STRIP_COMMAND = r'''find "$DESTDIR" -type f \
'(' -perm -111 -o -name '*.so*' -o -name '*.cmxs' -o -name '*.node' ')' \
-exec sh -ec \
'read -n4 hdr <"$1" # check for elf header
if [ "$hdr" != "$(printf \\x7fELF)" ]; then
exit 0
fi
debugfile="$DESTDIR$PREFIX/lib/debug/$(basename "$1")"
mkdir -p "$(dirname "$debugfile")"
objcopy --only-keep-debug "$1" "$debugfile"
chmod 644 "$debugfile"
strip --remove-section=.comment --remove-section=.note --strip-unneeded "$1"
objcopy --add-gnu-debuglink "$debugfile" "$1"' - {} ';'
'''
class BuildSystem(object):
'''Predefined command sequences for a given build system.
For example, you can have an 'autotools' build system, which runs
'configure', 'make' and 'make install'.
'''
def __init__(self):
self.pre_configure_commands = []
self.configure_commands = []
self.post_configure_commands = []
self.pre_build_commands = []
self.build_commands = []
self.post_build_commands = []
self.pre_test_commands = []
self.test_commands = []
self.post_test_commands = []
self.pre_install_commands = []
self.install_commands = []
self.post_install_commands = []
self.pre_strip_commands = []
self.strip_commands = []
self.post_strip_commands = []
def from_dict(self, name, commands):
self.name = name
self.configure_commands = commands.get('configure-commands', [])
self.build_commands = commands.get('build-commands', [])
self.install_commands = commands.get('install-commands', [])
self.strip_commands = commands.get('strip-commands', [])
def __getitem__(self, key):
key = '_'.join(key.split('-'))
return getattr(self, key)
def get_morphology(self, name):
'''Return the text of an autodetected chunk morphology.'''
return morphlib.morphology.Morphology({
'name': name,
'kind': 'chunk',
'build-system': self.name,
})
class ManualBuildSystem(BuildSystem):
'''A manual build system where the morphology must specify all commands.'''
name = 'manual'
class DummyBuildSystem(BuildSystem):
'''A dummy build system, useful for debugging morphologies.'''
name = 'dummy'
def __init__(self):
BuildSystem.__init__(self)
self.configure_commands = ['echo dummy configure']
self.build_commands = ['echo dummy build']
self.test_commands = ['echo dummy test']
self.install_commands = ['echo dummy install']
self.strip_commands = ['echo dummy strip']
class AutotoolsBuildSystem(BuildSystem):
'''The automake/autoconf/libtool holy trinity.'''
name = 'autotools'
def __init__(self):
BuildSystem.__init__(self)
self.configure_commands = [
'export NOCONFIGURE=1; ' +
'if [ -e autogen ]; then ./autogen; ' +
'elif [ -e autogen.sh ]; then ./autogen.sh; ' +
'elif [ -e bootstrap ]; then ./bootstrap; ' +
'elif [ -e bootstrap.sh ]; then ./bootstrap.sh; ' +
'elif [ ! -e ./configure ]; then autoreconf -ivf; fi',
'./configure --prefix="$PREFIX"',
]
self.build_commands = [
'make',
]
self.test_commands = [
]
self.install_commands = [
'make DESTDIR="$DESTDIR" install',
]
self.strip_commands = [_STRIP_COMMAND]
class PythonDistutilsBuildSystem(BuildSystem):
'''The Python distutils build systems.'''
name = 'python-distutils'
def __init__(self):
BuildSystem.__init__(self)
self.configure_commands = [
]
self.build_commands = [
'python setup.py build',
]
self.test_commands = [
]
self.install_commands = [
'python setup.py install --prefix "$PREFIX" --root "$DESTDIR"',
]
self.strip_commands = [_STRIP_COMMAND]
class ExtUtilsMakeMakerBuildSystem(BuildSystem):
'''The ExtUtils::MakeMaker build system.
To install perl distributions into the correct location in our chroot
we need to set PREFIX to / in the configure-commands.
The mapping between PREFIX and the final installation
directories is complex and depends upon the configuration of perl
see,
https://metacpan.org/pod/distribution/perl/INSTALL#Installation-Directories
and ExtUtil::MakeMaker's documentation for more details.
'''
# This is called the 'cpan' build system for historical reasons,
# back when morph only supported one of the perl build systems.
name = 'cpan'
def __init__(self):
BuildSystem.__init__(self)
self.configure_commands = [
'perl Makefile.PL PREFIX=$DESTDIR$PREFIX',
]
self.build_commands = [
'make',
]
self.test_commands = [
# FIXME: we ought to run tests by default,
# and use chunk morphs to disable for special cases
# 'make test',
]
self.install_commands = [
'make install',
]
self.strip_commands = [_STRIP_COMMAND]
class ModuleBuildBuildSystem(BuildSystem):
'''The Module::Build build system'''
name = 'module-build'
def __init__(self):
super(ModuleBuildBuildSystem, self).__init__()
self.configure_commands = [
# See the comment in ExtUtilsMakeMakerBuildSystem
# to see why --prefix is set to $DESTDIR$PREFIX here,
# (--prefix in Module::Build has the same meaning
# as PREFIX in ExtUtils::MakeMaker)
'perl Build.PL --prefix "$DESTDIR$PREFIX"'
]
self.build_commands = [
'./Build'
]
self.test_commands = [
'./Build test'
]
self.install_commands = [
'./Build install'
]
class CMakeBuildSystem(BuildSystem):
'''The cmake build system.'''
name = 'cmake'
def __init__(self):
BuildSystem.__init__(self)
self.configure_commands = [
'cmake -DCMAKE_INSTALL_PREFIX=/usr'
]
self.build_commands = [
'make',
]
self.test_commands = [
]
self.install_commands = [
'make DESTDIR="$DESTDIR" install',
]
self.strip_commands = [_STRIP_COMMAND]
class QMakeBuildSystem(BuildSystem):
'''The Qt build system.'''
name = 'qmake'
def __init__(self):
BuildSystem.__init__(self)
self.configure_commands = [
'qmake -makefile '
]
self.build_commands = [
'make',
]
self.test_commands = [
]
self.install_commands = [
'make INSTALL_ROOT="$DESTDIR" install',
]
self.strip_commands = [_STRIP_COMMAND]
build_systems = [
ManualBuildSystem(),
AutotoolsBuildSystem(),
PythonDistutilsBuildSystem(),
ExtUtilsMakeMakerBuildSystem(),
ModuleBuildBuildSystem(),
CMakeBuildSystem(),
QMakeBuildSystem(),
DummyBuildSystem(),
]
def lookup_build_system(name):
'''Return build system that corresponds to the name.
If the name does not match any build system, raise ``KeyError``.
'''
for bs in build_systems:
if bs.name == name:
return bs
raise KeyError('Unknown build system: %s' % name)