356 lines
12 KiB
Python
356 lines
12 KiB
Python
#!/usr/bin/python
|
|
# Copyright (C) 2019-2020 Jelmer Vernooij <jelmer@jelmer.uk>
|
|
# encoding: utf-8
|
|
#
|
|
# 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; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# 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, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
|
|
import logging
|
|
import re
|
|
import os
|
|
|
|
from . import shebang_binary
|
|
from .apt import AptManager, UnidentifiedError
|
|
from .fix_build import run_with_build_fixer
|
|
|
|
|
|
class NoBuildToolsFound(Exception):
|
|
"""No supported build tools were found."""
|
|
|
|
|
|
class BuildSystem(object):
|
|
"""A particular buildsystem."""
|
|
|
|
def __init__(self, session):
|
|
self.session = session
|
|
|
|
def dist(self):
|
|
raise NotImplementedError(self.dist)
|
|
|
|
def test(self):
|
|
raise NotImplementedError(self.test)
|
|
|
|
def build(self):
|
|
raise NotImplementedError(self.build)
|
|
|
|
def clean(self):
|
|
raise NotImplementedError(self.clean)
|
|
|
|
def install(self):
|
|
raise NotImplementedError(self.install)
|
|
|
|
|
|
class Pear(BuildSystem):
|
|
|
|
def setup(self):
|
|
apt = AptManager(self.session)
|
|
apt.install(['php-pear'])
|
|
|
|
def dist(self):
|
|
self.setup()
|
|
run_with_build_fixer(self.session, ['pear', 'package'])
|
|
|
|
def test(self):
|
|
self.setup()
|
|
run_with_build_fixer(self.session, ['pear', 'run-tests'])
|
|
|
|
def build(self):
|
|
self.setup()
|
|
run_with_build_fixer(self.session, ['pear', 'build'])
|
|
|
|
def clean(self):
|
|
self.setup()
|
|
# TODO
|
|
|
|
def install(self):
|
|
self.setup()
|
|
run_with_build_fixer(self.session, ['pear', 'install'])
|
|
|
|
|
|
class SetupPy(BuildSystem):
|
|
|
|
def setup(self):
|
|
apt = AptManager(self.session)
|
|
apt.install(['python3', 'python3-pip'])
|
|
with open('setup.py', 'r') as f:
|
|
setup_py_contents = f.read()
|
|
try:
|
|
with open('setup.cfg', 'r') as f:
|
|
setup_cfg_contents = f.read()
|
|
except FileNotFoundError:
|
|
setup_cfg_contents = ''
|
|
if 'setuptools' in setup_py_contents:
|
|
logging.info('Reference to setuptools found, installing.')
|
|
apt.install(['python3-setuptools'])
|
|
if ('setuptools_scm' in setup_py_contents or
|
|
'setuptools_scm' in setup_cfg_contents):
|
|
logging.info('Reference to setuptools-scm found, installing.')
|
|
apt.install(['python3-setuptools-scm', 'git', 'mercurial'])
|
|
|
|
# TODO(jelmer): Install setup_requires
|
|
|
|
def test(self):
|
|
self.setup()
|
|
self._run_setup(['test'])
|
|
|
|
def dist(self):
|
|
self.setup()
|
|
self._run_setup(['sdist'])
|
|
|
|
def clean(self):
|
|
self.setup()
|
|
self._run_setup(['clean'])
|
|
|
|
def install(self):
|
|
self.setup()
|
|
self._run_setup(['install'])
|
|
|
|
def _run_setup(self, args):
|
|
apt = AptManager(self.session)
|
|
interpreter = shebang_binary('setup.py')
|
|
if interpreter is not None:
|
|
if interpreter == 'python3':
|
|
apt.install(['python3'])
|
|
elif interpreter == 'python2':
|
|
apt.install(['python2'])
|
|
elif interpreter == 'python':
|
|
apt.install(['python'])
|
|
else:
|
|
raise ValueError('Unknown interpreter %r' % interpreter)
|
|
apt.install(['python2', 'python3'])
|
|
run_with_build_fixer(
|
|
self.session, ['./setup.py'] + args)
|
|
else:
|
|
# Just assume it's Python 3
|
|
apt.install(['python3'])
|
|
run_with_build_fixer(
|
|
self.session, ['python3', './setup.py'] + args)
|
|
|
|
|
|
class PyProject(BuildSystem):
|
|
|
|
def load_toml(self):
|
|
import toml
|
|
with open('pyproject.toml', 'r') as pf:
|
|
return toml.load(pf)
|
|
|
|
def dist(self):
|
|
apt = AptManager(self.session)
|
|
pyproject = self.load_toml()
|
|
if 'poetry' in pyproject.get('tool', []):
|
|
logging.info(
|
|
'Found pyproject.toml with poetry section, '
|
|
'assuming poetry project.')
|
|
apt.install(['python3-venv', 'python3-pip'])
|
|
self.session.check_call(['pip3', 'install', 'poetry'], user='root')
|
|
self.session.check_call(['poetry', 'build', '-f', 'sdist'])
|
|
return
|
|
raise AssertionError('no supported section in pyproject.toml')
|
|
|
|
|
|
class SetupCfg(BuildSystem):
|
|
|
|
def setup(self):
|
|
apt = AptManager(self.session)
|
|
apt.install(['python3-pep517', 'python3-pip'])
|
|
|
|
def dist(self):
|
|
self.session.check_call(['python3', '-m', 'pep517.build', '-s', '.'])
|
|
|
|
|
|
class NpmPackage(BuildSystem):
|
|
|
|
def setup(self):
|
|
apt = AptManager(self.session)
|
|
apt.install(['npm'])
|
|
|
|
def dist(self):
|
|
self.setup()
|
|
run_with_build_fixer(self.session, ['npm', 'pack'])
|
|
|
|
|
|
class Waf(BuildSystem):
|
|
|
|
def setup(self):
|
|
apt = AptManager(self.session)
|
|
apt.install(['python3'])
|
|
|
|
def dist(self):
|
|
self.setup()
|
|
run_with_build_fixer(self.session, ['./waf', 'dist'])
|
|
|
|
|
|
class Gem(BuildSystem):
|
|
|
|
def setup(self):
|
|
apt = AptManager(self.session)
|
|
apt.install(['gem2deb'])
|
|
|
|
def dist(self):
|
|
self.setup()
|
|
gemfiles = [name for name in os.listdir('.') if name.endswith('.gem')]
|
|
if len(gemfiles) > 1:
|
|
logging.warning('More than one gemfile. Trying the first?')
|
|
run_with_build_fixer(self.session, ['gem2tgz', gemfiles[0]])
|
|
|
|
|
|
class DistInkt(BuildSystem):
|
|
|
|
def setup(self):
|
|
apt = AptManager(self.session)
|
|
apt.install(['libdist-inkt-perl'])
|
|
|
|
def dist(self):
|
|
self.setup()
|
|
apt = AptManager(self.session)
|
|
with open('dist.ini', 'rb') as f:
|
|
for line in f:
|
|
if not line.startswith(b';;'):
|
|
continue
|
|
try:
|
|
(key, value) = line[2:].split(b'=', 1)
|
|
except ValueError:
|
|
continue
|
|
if (key.strip() == b'class' and
|
|
value.strip().startswith(b"'Dist::Inkt")):
|
|
logging.info(
|
|
'Found Dist::Inkt section in dist.ini, '
|
|
'assuming distinkt.')
|
|
# TODO(jelmer): install via apt if possible
|
|
self.session.check_call(
|
|
['cpan', 'install', value.decode().strip("'")],
|
|
user='root')
|
|
run_with_build_fixer(self.session, ['distinkt-dist'])
|
|
return
|
|
# Default to invoking Dist::Zilla
|
|
logging.info('Found dist.ini, assuming dist-zilla.')
|
|
apt.install(['libdist-zilla-perl'])
|
|
run_with_build_fixer(self.session, ['dzil', 'build', '--in', '..'])
|
|
|
|
|
|
class Make(BuildSystem):
|
|
|
|
def setup(self):
|
|
apt = AptManager(self.session)
|
|
if os.path.exists('Makefile.PL') and not os.path.exists('Makefile'):
|
|
apt.install(['perl'])
|
|
run_with_build_fixer(self.session, ['perl', 'Makefile.PL'])
|
|
|
|
if not os.path.exists('Makefile') and not os.path.exists('configure'):
|
|
if os.path.exists('autogen.sh'):
|
|
if shebang_binary('autogen.sh') is None:
|
|
run_with_build_fixer(
|
|
self.session, ['/bin/sh', './autogen.sh'])
|
|
try:
|
|
run_with_build_fixer(
|
|
self.session, ['./autogen.sh'])
|
|
except UnidentifiedError as e:
|
|
if ("Gnulib not yet bootstrapped; "
|
|
"run ./bootstrap instead.\n" in e.lines):
|
|
run_with_build_fixer(self.session, ["./bootstrap"])
|
|
run_with_build_fixer(self.session, ['./autogen.sh'])
|
|
else:
|
|
raise
|
|
|
|
elif (os.path.exists('configure.ac') or
|
|
os.path.exists('configure.in')):
|
|
apt.install([
|
|
'autoconf', 'automake', 'gettext', 'libtool',
|
|
'gnu-standards'])
|
|
run_with_build_fixer(self.session, ['autoreconf', '-i'])
|
|
|
|
if not os.path.exists('Makefile') and os.path.exists('configure'):
|
|
self.session.check_call(['./configure'])
|
|
|
|
def dist(self):
|
|
self.setup()
|
|
apt = AptManager(self.session)
|
|
apt.install(['make'])
|
|
try:
|
|
run_with_build_fixer(self.session, ['make', 'dist'])
|
|
except UnidentifiedError as e:
|
|
if ("make: *** No rule to make target 'dist'. Stop.\n"
|
|
in e.lines):
|
|
pass
|
|
elif ("make[1]: *** No rule to make target 'dist'. Stop.\n"
|
|
in e.lines):
|
|
pass
|
|
elif ("Reconfigure the source tree "
|
|
"(via './config' or 'perl Configure'), please.\n"
|
|
) in e.lines:
|
|
run_with_build_fixer(self.session, ['./config'])
|
|
run_with_build_fixer(self.session, ['make', 'dist'])
|
|
elif (
|
|
"Please try running 'make manifest' and then run "
|
|
"'make dist' again.\n" in e.lines):
|
|
run_with_build_fixer(self.session, ['make', 'manifest'])
|
|
run_with_build_fixer(self.session, ['make', 'dist'])
|
|
elif "Please run ./configure first\n" in e.lines:
|
|
run_with_build_fixer(self.session, ['./configure'])
|
|
run_with_build_fixer(self.session, ['make', 'dist'])
|
|
elif any([re.match(
|
|
r'Makefile:[0-9]+: \*\*\* Missing \'Make.inc\' '
|
|
r'Run \'./configure \[options\]\' and retry. Stop.\n',
|
|
line) for line in e.lines]):
|
|
run_with_build_fixer(self.session, ['./configure'])
|
|
run_with_build_fixer(self.session, ['make', 'dist'])
|
|
elif any([re.match(
|
|
r'Problem opening MANIFEST: No such file or directory '
|
|
r'at .* line [0-9]+\.', line) for line in e.lines]):
|
|
run_with_build_fixer(self.session, ['make', 'manifest'])
|
|
run_with_build_fixer(self.session, ['make', 'dist'])
|
|
else:
|
|
raise
|
|
else:
|
|
return
|
|
|
|
|
|
def detect_buildsystems(session):
|
|
"""Detect build systems."""
|
|
if os.path.exists('package.xml'):
|
|
logging.info('Found package.xml, assuming pear package.')
|
|
yield Pear(session)
|
|
|
|
if os.path.exists('setup.py'):
|
|
logging.info('Found setup.py, assuming python project.')
|
|
yield SetupPy(session)
|
|
|
|
if os.path.exists('pyproject.toml'):
|
|
logging.info('Found pyproject.toml, assuming python project.')
|
|
yield PyProject(session)
|
|
|
|
if os.path.exists('setup.cfg'):
|
|
logging.info('Found setup.cfg, assuming python project.')
|
|
yield SetupCfg(session)
|
|
|
|
if os.path.exists('package.json'):
|
|
logging.info('Found package.json, assuming node package.')
|
|
yield NpmPackage(session)
|
|
|
|
if os.path.exists('waf'):
|
|
logging.info('Found waf, assuming waf package.')
|
|
yield Waf(session)
|
|
|
|
gemfiles = [name for name in os.listdir('.') if name.endswith('.gem')]
|
|
if gemfiles:
|
|
yield Gem(session)
|
|
|
|
if os.path.exists('dist.ini') and not os.path.exists('Makefile.PL'):
|
|
yield DistInkt(session)
|
|
|
|
if any([os.path.exists(p) for p in [
|
|
'Makefile', 'Makefile.PL', 'autogen.sh', 'configure.ac',
|
|
'configure.in']]):
|
|
yield Make(session)
|