
- Use the more well known print function. - Write the spec file to a buffer (and show the buffer in debug mode) before writing it to a file, instead of writing it directly to the file. - Add stage logging that shows at which stage the program is in (when debugging is enabled). Change-Id: I580791ff34e75bc56ff58332bfa3c08e686b6b36
839 lines
27 KiB
Python
Executable File
839 lines
27 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
from __future__ import print_function
|
|
|
|
import collections
|
|
import distutils.spawn
|
|
import email.parser
|
|
import logging
|
|
import os
|
|
import os.path
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
import argparse
|
|
import six
|
|
|
|
import pip.util
|
|
import pkg_resources
|
|
|
|
|
|
logger = logging.getLogger()
|
|
|
|
package_map = {}
|
|
arch_dependent = set()
|
|
epoch_map = {}
|
|
|
|
requirements_section_re = re.compile(r'\[(.*?)\]')
|
|
setup_py = "setup.py"
|
|
|
|
CAND_MAP = {
|
|
'final-': 'f',
|
|
'@': 'd',
|
|
}
|
|
DEFAULT_SCRIPTS = {
|
|
"prep":
|
|
"""%setup -n %{pkg_name}-%{unmangled_version} -n %{pkg_name}-%{unmangled_version}""",
|
|
"build":
|
|
"""%{__python} setup.py build""",
|
|
"install":
|
|
"""%{__python} setup.py install -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES
|
|
abspath_installed_files=$(readlink -f INSTALLED_FILES)
|
|
(
|
|
cd $RPM_BUILD_ROOT
|
|
for i in usr/*/python*/site-packages/* usr/bin/*; do
|
|
if [ -e "$i" ]; then
|
|
sed -i "s@/$i/@DELETE_ME@" "$abspath_installed_files"
|
|
echo "/$i"
|
|
fi
|
|
done
|
|
if [ -d usr/man ]; then
|
|
rm -rf usr/share/man
|
|
mkdir -p usr/share
|
|
mv usr/man usr/share/
|
|
fi
|
|
sed -i "s@/usr/man/@DELETE_ME@" "$abspath_installed_files"
|
|
sed -i "s@/usr/share/man/@DELETE_ME@" "$abspath_installed_files"
|
|
for i in usr/share/man/*; do
|
|
if [ -d "$i" ]; then
|
|
echo "%%doc /$i/*"
|
|
fi
|
|
done
|
|
) >> GATHERED_FILES
|
|
sed '/^DELETE_ME/d' INSTALLED_FILES >> GATHERED_FILES
|
|
sort -u GATHERED_FILES > INSTALLED_FILES
|
|
""",
|
|
"clean":
|
|
"""rm -rf $RPM_BUILD_ROOT""",
|
|
}
|
|
|
|
DEFAULT_TESTS_SCRIPTS = {
|
|
"install": """
|
|
install -d -m 755 %{buildroot}%{tests_data_dir}
|
|
tar -cf "%{buildroot}%{tests_data_dir}/test_env.tar" \
|
|
--exclude-vcs --exclude ./%{pkg_path} \
|
|
--exclude './build*' --exclude './bin' --exclude './smoketest*' \
|
|
.
|
|
if [ -d "./%{pkg_path}/tests" ]; then
|
|
tar -rf "%{buildroot}%{tests_data_dir}/test_env.tar" \
|
|
./%{pkg_path}/tests
|
|
fi
|
|
if [ -r "./%{pkg_path}/test.py" ]; then
|
|
tar -rf "%{buildroot}%{tests_data_dir}/test_env.tar" \
|
|
./%{pkg_path}/test.py
|
|
fi
|
|
gzip -9 "%{buildroot}%{tests_data_dir}/test_env.tar"
|
|
|
|
|
|
# Make simple test runner
|
|
install -d -m 755 %{buildroot}%{_bindir}
|
|
cat > %{buildroot}%{_bindir}/%{pkg_name}-make-test-env <<"EOF"
|
|
#!/bin/bash
|
|
|
|
set -e
|
|
|
|
if [ -z "$1" ] || [ "$1" == "--help" ] ; then
|
|
echo "Usage: $0 [dir]"
|
|
echo " $0 --tmpdir"
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$1" == "--tmpdir" ]; then
|
|
target_dir=$(mktemp -dt "${0##*/}.XXXXXXXX")
|
|
echo "Created temporary directory: $target_dir"
|
|
else
|
|
target_dir="$1"
|
|
fi
|
|
|
|
cd "$target_dir"
|
|
tar -xzf "%{tests_data_dir}/test_env.tar.gz"
|
|
cp -a %{python_sitelib}/%{pkg_path} `dirname %{pkg_path}`
|
|
ln -s /usr/bin ./bin
|
|
|
|
EOF
|
|
chmod 0755 %{buildroot}%{_bindir}/%{pkg_name}-make-test-env
|
|
"""
|
|
}
|
|
|
|
EGG_INFO_SCRIPT_TPL = """
|
|
__file__ = __SETUP_PY__
|
|
|
|
from setuptools.command import egg_info
|
|
import pkg_resources
|
|
import os
|
|
|
|
# This is a fixed up run method that works better, it will be activated
|
|
# when the following (or equivalent) is ran $ python setup.py egg_info
|
|
def replacement_run(self):
|
|
self.mkpath(self.egg_info)
|
|
installer = self.distribution.fetch_build_egg
|
|
if self.distribution.has_ext_modules():
|
|
# TODO(harlowja): does this need to be better??
|
|
ext_modules_path = os.path.join(self.egg_info, 'ext_modules.txt')
|
|
self.write_file("extension modules", ext_modules_path, "")
|
|
for ep in pkg_resources.iter_entry_points('egg_info.writers'):
|
|
writer = ep.load(require=False)
|
|
if writer:
|
|
writer(self, ep.name, os.path.join(self.egg_info, ep.name))
|
|
self.find_sources()
|
|
if self.distribution.tests_require:
|
|
test_requires_path = os.path.join(self.egg_info, 'test-requires.txt')
|
|
test_requires = []
|
|
for line in pkg_resources.yield_lines(self.distribution.tests_require):
|
|
test_requires.append(line)
|
|
self.write_file("test requirements", test_requires_path,
|
|
'\\n'.join(test_requires))
|
|
|
|
egg_info.egg_info.run = replacement_run
|
|
exec(compile(open(__file__).read().replace('\\r\\n', '\\n'), __file__, 'exec'))
|
|
"""
|
|
|
|
|
|
class Buffer(six.StringIO):
|
|
def write_nl(self, blob=''):
|
|
self.write(blob)
|
|
self.write("\n")
|
|
|
|
|
|
class InstallationError(Exception):
|
|
pass
|
|
|
|
|
|
def python_name_to_key(name):
|
|
return pkg_resources.Requirement.parse(name).key
|
|
|
|
|
|
def python_key_to_rpm(python_name):
|
|
python_name = python_name.lower()
|
|
try:
|
|
return package_map[python_name]
|
|
except KeyError:
|
|
pass
|
|
python_name = python_name.replace("_", "-").replace(".", "-")
|
|
if python_name.startswith("python-"):
|
|
prefixed_name = python_name
|
|
else:
|
|
prefixed_name = "python-%s" % python_name
|
|
return prefixed_name
|
|
|
|
|
|
def egg_info_path(source_dir, filename):
|
|
base = os.path.join(source_dir, "pip-egg-info")
|
|
filenames = os.listdir(base)
|
|
if not filenames:
|
|
raise InstallationError("No files/directories in %s (from %s)"
|
|
% (base, filename))
|
|
|
|
# if we have more than one match, we pick the toplevel one.
|
|
if len(filenames) > 1:
|
|
filenames.sort(key=lambda x: x.count(os.path.sep) +
|
|
(os.path.altsep and
|
|
x.count(os.path.altsep) or 0))
|
|
return os.path.join(base, filenames[0], filename)
|
|
|
|
|
|
def egg_info_lines(source_dir, filename):
|
|
filename = egg_info_path(source_dir, filename)
|
|
if not os.path.exists(filename):
|
|
return []
|
|
with open(filename, "r") as f:
|
|
return f.readlines()
|
|
|
|
|
|
def egg_info_requirements(source_dir, extras=(), filename='requires.txt'):
|
|
in_extra = None
|
|
for line in egg_info_lines(source_dir, filename):
|
|
match = requirements_section_re.match(line.lower())
|
|
if match:
|
|
in_extra = match.group(1)
|
|
continue
|
|
if in_extra and in_extra not in extras:
|
|
# Skip requirement for an extra we aren't requiring
|
|
continue
|
|
yield line
|
|
|
|
|
|
def pkg_info(source_dir):
|
|
p = email.parser.FeedParser()
|
|
filename = egg_info_path(source_dir, "PKG-INFO")
|
|
if not os.path.exists(filename):
|
|
logger.warn('No PKG-INFO file found in %s' % source_dir)
|
|
else:
|
|
with open(filename, "r") as f:
|
|
for line in f.readlines():
|
|
# NOTE(aababilov): d2to1 has bad PKG-INFO
|
|
# that is fixed this way:
|
|
if line and not line[0].isspace() and not ":" in line:
|
|
line = " " + line
|
|
p.feed(line)
|
|
return p.close()
|
|
|
|
|
|
def setup_py_one_line(source_dir, command):
|
|
"""Run `python setup.py $command` and return the last line.
|
|
|
|
python ldap is so clever that is prints extra stuff
|
|
before package name or version. Lets return the last line
|
|
"""
|
|
return call_subprocess(
|
|
[sys.executable, setup_py, command],
|
|
cwd=source_dir, show_stdout=False)[0].splitlines()[-1].strip()
|
|
|
|
|
|
def create_parser():
|
|
parser = argparse.ArgumentParser()
|
|
|
|
rpm_base = os.path.expanduser("~/rpmbuild")
|
|
source_dir = os.getcwd()
|
|
|
|
rpmbuild_executable = (distutils.spawn.find_executable("rpmbuild") or
|
|
distutils.spawn.find_executable("rpm"))
|
|
parser.add_argument(
|
|
"--pip-verbose", "-f",
|
|
action="store_true",
|
|
default=False,
|
|
help="Show pip stdout")
|
|
parser.add_argument(
|
|
"--debug", "-d",
|
|
action="store_true",
|
|
default=False,
|
|
help="Print debug information")
|
|
parser.add_argument(
|
|
"--source-only", "-s",
|
|
action="store_true",
|
|
default=False,
|
|
help="Only generate source RPM")
|
|
parser.add_argument(
|
|
"--binary-only", "-b",
|
|
action="store_true",
|
|
default=False,
|
|
help="Only generate binary RPM")
|
|
parser.add_argument(
|
|
"--rpm-base",
|
|
metavar="<dir>",
|
|
default=rpm_base,
|
|
help="rpmbuild directory (default: %s)" % rpm_base)
|
|
parser.add_argument(
|
|
"--rpmbuild",
|
|
metavar="<dir>",
|
|
default=rpmbuild_executable,
|
|
help="rpmbuild executable (default: %s)" % rpmbuild_executable)
|
|
parser.add_argument(
|
|
"--convert", "-c",
|
|
dest="convert",
|
|
metavar="<name>",
|
|
nargs="+",
|
|
default=[],
|
|
help="Python requirement name to be converted to RPM package names")
|
|
parser.add_argument(
|
|
dest="sources",
|
|
metavar="<dir or archive>",
|
|
nargs="*",
|
|
default=[source_dir],
|
|
help="Source directories of packages (default: current directory)")
|
|
parser.add_argument(
|
|
"--scripts-dir",
|
|
metavar="<dir>",
|
|
default=None,
|
|
help="Specify a directory with scripts for packages")
|
|
parser.add_argument(
|
|
"--arch-dependent", "-a",
|
|
metavar="<Python package name>",
|
|
nargs="+",
|
|
default=[],
|
|
help="Known architecture dependent packages")
|
|
parser.add_argument(
|
|
"--epoch", "-e",
|
|
metavar="<number>",
|
|
type=int,
|
|
default=None,
|
|
help="RPM epoch for generated packages")
|
|
parser.add_argument(
|
|
"--epoch-map", "-x",
|
|
metavar="<Python package name == epoch number>",
|
|
nargs="+",
|
|
default=[],
|
|
help="Forced RPM epochs for packages")
|
|
parser.add_argument(
|
|
"--release", "-r",
|
|
metavar="<number>",
|
|
default="0%{?dist}",
|
|
help="RPM release for generated packages")
|
|
parser.add_argument(
|
|
"--package-map", "-p",
|
|
metavar="<Python package name == RPM name>",
|
|
nargs="+",
|
|
default=[],
|
|
help="Correspondence between Python and RPM package names")
|
|
parser.add_argument(
|
|
"--build-options",
|
|
metavar="<Python package name == Build option>",
|
|
nargs="+",
|
|
default=[],
|
|
help="Correspondence between Python and specific RPM package build options")
|
|
parser.add_argument(
|
|
"--with-tests",
|
|
action="store_true",
|
|
default=False,
|
|
help="Add subpackage with tests")
|
|
return parser
|
|
|
|
|
|
def call_subprocess(cmd, cwd=None, show_stdout=True, raise_on_returncode=True):
|
|
if show_stdout:
|
|
stdout = None
|
|
else:
|
|
stdout = subprocess.PIPE
|
|
cwd = cwd or os.getcwd()
|
|
proc = subprocess.Popen(cmd, cwd=cwd,
|
|
stderr=None, stdin=None, stdout=stdout)
|
|
ret = proc.communicate()
|
|
if proc.returncode:
|
|
command_desc = " ".join(cmd)
|
|
if raise_on_returncode:
|
|
raise InstallationError(
|
|
"Command %s failed with error code %s in %s"
|
|
% (command_desc, proc.returncode, cwd))
|
|
else:
|
|
logger.warn(
|
|
"Command %s had error code %s in %s"
|
|
% (command_desc, proc.returncode, cwd))
|
|
return ret
|
|
|
|
|
|
def setup_logging(options):
|
|
level = logging.DEBUG if options.debug else logging.WARNING
|
|
handler = logging.StreamHandler(sys.stderr)
|
|
logger.addHandler(handler)
|
|
logger.setLevel(level)
|
|
|
|
|
|
def truncate(text, max_len=77):
|
|
if max_len <= 0:
|
|
return ''
|
|
if len(text) < max_len:
|
|
return text
|
|
text = text[0:max_len] + "..."
|
|
return text
|
|
|
|
|
|
def build_map(arguments):
|
|
result = {}
|
|
for arg in arguments:
|
|
try:
|
|
(key, value) = arg.split("==")
|
|
key = python_name_to_key(key)
|
|
value = value.strip()
|
|
assert value
|
|
except (IndexError, ValueError, AssertionError):
|
|
raise InstallationError("Bad specifier: `%s'" % arg)
|
|
else:
|
|
result[key] = value
|
|
return result
|
|
|
|
|
|
def build_map_many(arguments):
|
|
result = {}
|
|
for arg in arguments:
|
|
try:
|
|
(key, value) = arg.split("==")
|
|
key = python_name_to_key(key)
|
|
value = value.strip()
|
|
assert value
|
|
except (IndexError, ValueError, AssertionError):
|
|
raise InstallationError("Bad specifier: `%s'" % arg)
|
|
else:
|
|
if key in result:
|
|
result[key].append(value)
|
|
else:
|
|
result[key] = [value]
|
|
return result
|
|
|
|
|
|
def run_egg_info(source_dir, options):
|
|
script = EGG_INFO_SCRIPT_TPL.replace('__SETUP_PY__', "'setup.py'")
|
|
egg_info_dir = os.path.join(source_dir, 'pip-egg-info')
|
|
if not os.path.exists(egg_info_dir):
|
|
os.makedirs(egg_info_dir)
|
|
egg_base_option = ['--egg-base', 'pip-egg-info']
|
|
call_subprocess(
|
|
[sys.executable, '-c', script, 'egg_info'] + egg_base_option,
|
|
cwd=source_dir,
|
|
show_stdout=options.pip_verbose)
|
|
|
|
|
|
def trim_zeroes(version):
|
|
"""Trim zeroes from the end of a version."""
|
|
version = version.split(".")
|
|
while len(version):
|
|
try:
|
|
v = int(version[-1])
|
|
if v == 0:
|
|
version.pop()
|
|
else:
|
|
break
|
|
except ValueError:
|
|
break
|
|
if not version:
|
|
# Nothing left :(
|
|
return (1, '0')
|
|
else:
|
|
return (len(version), ".".join(version))
|
|
|
|
|
|
def fill_zeros(version, zero_fill):
|
|
"""Fills zeroes from the end of a version."""
|
|
version = version.split(".")
|
|
while len(version) < zero_fill:
|
|
version.append("0")
|
|
return ".".join(version)
|
|
|
|
|
|
def version_release(version):
|
|
|
|
# Unformats the parsed versions zero fill.
|
|
def undo_zfill(piece):
|
|
piece = piece.lstrip("0")
|
|
if not piece:
|
|
piece = '0'
|
|
return piece
|
|
|
|
# Translate usage of pre-release versions into
|
|
# version and release since rpm will have conflicts
|
|
# when trying to compare against pre-release versions.
|
|
parsed_version = pkg_resources.parse_version(version)
|
|
cand_start = -1
|
|
for i, piece in enumerate(parsed_version):
|
|
if piece.startswith("*"):
|
|
cand_start = i
|
|
break
|
|
if cand_start == -1:
|
|
return (version, None)
|
|
version = []
|
|
for v in list(parsed_version)[0:cand_start]:
|
|
version.append(undo_zfill(v))
|
|
version = ".".join(version)
|
|
if not version:
|
|
version = "0"
|
|
release = []
|
|
candidates = collections.deque(parsed_version[cand_start:])
|
|
while len(candidates):
|
|
v = candidates.popleft()
|
|
if v == '*final':
|
|
break
|
|
# TODO(harlowja): this will likely require some more work as the
|
|
# way python and rpm compare release versions is not the same, but the
|
|
# usage of these types is limited so should not be a major problem.
|
|
piece = []
|
|
if v.startswith("*"):
|
|
v = v[1:]
|
|
v = CAND_MAP.get(v, v)
|
|
piece.append(v)
|
|
while len(candidates):
|
|
v = candidates.popleft()
|
|
if not v.isdigit():
|
|
candidates.appendleft(v)
|
|
break
|
|
else:
|
|
piece.append(undo_zfill(v))
|
|
release.append("".join(piece))
|
|
release = ".".join(release)
|
|
return (version, release)
|
|
|
|
|
|
def format_version(req, version):
|
|
version, release = version_release(version)
|
|
# NOTE(imelnikov): rpm and pip compare versions differently, and
|
|
# this used to lead to lots problems, pain and sorrows. The most
|
|
# visible outcome of the difference is that from rpm's point of
|
|
# view version '2' != '2.0', as well as '2.0' != '2.0.0', but for
|
|
# pip it's same version.
|
|
#
|
|
# Current workaround for this works as follows: if python module
|
|
# requires module of some version, (like 2.0.0), the actual rpm
|
|
# version of the module will have the same non-zero beginning and
|
|
# some '.0's at the end ('2', '2.0', '2.0.0', '2.0.0.0' ...). Thus,
|
|
# we can calculate lower bound for requirement by trimming '.0'
|
|
# from the version (we get '2'), and then set upper bound to lower
|
|
# bound + tail of '.0' repeated several times. Luckily, '2.0' and
|
|
# '2.00' is the same version for rpm.
|
|
pieces, lower_version = trim_zeroes(version)
|
|
upper_version = fill_zeros(lower_version, max(pieces + 1, 3))
|
|
try:
|
|
epoch = epoch_map[req.key]
|
|
lower_version = "%s:%s" % (epoch, lower_version)
|
|
upper_version = "%s:%s" % (epoch, upper_version)
|
|
except KeyError:
|
|
pass
|
|
# Attach on the release (if any)
|
|
if release:
|
|
version = version + "-" + release
|
|
lower_version = lower_version + "-" + release
|
|
upper_version = upper_version + "-" + release
|
|
return (version, lower_version, upper_version)
|
|
|
|
|
|
def requires_and_conflicts(req_list, skip_req_names=()):
|
|
rpm_requires = []
|
|
rpm_conflicts = []
|
|
rpm_mapping = {}
|
|
for line in req_list:
|
|
try:
|
|
req = pkg_resources.Requirement.parse(line)
|
|
except Exception:
|
|
continue
|
|
if req.key in skip_req_names:
|
|
continue
|
|
rpm_name = python_key_to_rpm(req.key)
|
|
if not req.specs:
|
|
rpm_requires.append(rpm_name)
|
|
rpm_mapping[rpm_name] = req
|
|
continue
|
|
for kind, version in req.specs:
|
|
version, lower_version, upper_version = format_version(req, version)
|
|
if kind == "!=":
|
|
# NOTE(imelnikov): we can't conflict with ranges, so we
|
|
# put version as is and with trimmed zeroes just in case
|
|
rpm_conflicts.append('%s = %s' % (rpm_name, version))
|
|
rpm_mapping[rpm_conflicts[-1]] = req
|
|
if version != lower_version:
|
|
rpm_conflicts.append('%s = %s' % (rpm_name, lower_version))
|
|
rpm_mapping[rpm_conflicts[-1]] = req
|
|
elif kind == '==':
|
|
rpm_requires.extend((
|
|
'%s >= %s' % (rpm_name, lower_version),
|
|
'%s <= %s' % (rpm_name, upper_version)
|
|
))
|
|
rpm_mapping[rpm_requires[-1]] = req
|
|
rpm_mapping[rpm_requires[-2]] = req
|
|
elif kind in ('<=', '<'):
|
|
rpm_requires.append('%s <= %s' % (rpm_name, upper_version))
|
|
rpm_mapping[rpm_requires[-1]] = req
|
|
elif kind in ('>=', '>'):
|
|
rpm_requires.append('%s >= %s' % (rpm_name, lower_version))
|
|
rpm_mapping[rpm_requires[-1]] = req
|
|
else:
|
|
raise ValueError('Invalid requirement kind: %r' % kind)
|
|
|
|
rpm_requires_str = six.StringIO()
|
|
for req in rpm_requires:
|
|
rpm_requires_str.write("# Source: %s\n" % (rpm_mapping[req]))
|
|
rpm_requires_str.write("Requires: %s\n\n" % (req))
|
|
|
|
rpm_conflicts_str = six.StringIO()
|
|
for req in rpm_conflicts:
|
|
rpm_conflicts_str.write("# Source: %s\n" % (rpm_mapping[req]))
|
|
rpm_conflicts_str.write("Conflicts: %s\n\n" % (req))
|
|
rpm_conflicts_str.write("\n\n")
|
|
|
|
return rpm_requires_str.getvalue(), rpm_conflicts_str.getvalue()
|
|
|
|
|
|
def one_line(line, max_len=80):
|
|
line = line.replace("\n", " ")
|
|
if max_len > 0:
|
|
return line[:max_len]
|
|
return line
|
|
|
|
|
|
def show_stage(header, details=''):
|
|
logger.debug("-" * len(header))
|
|
logger.debug(header)
|
|
logger.debug("-" * len(header))
|
|
if details:
|
|
logger.debug(details)
|
|
|
|
|
|
def build_rpm_spec(pkg_key, pkg_name, options, **kwargs):
|
|
buf = Buffer()
|
|
defines = kwargs.get('defines', {})
|
|
defines_added = 0
|
|
for n in ['pkg_name', 'pkg_path', 'rpm_name',
|
|
'version', 'release', 'unmangled_version',
|
|
'tests_data_dir']:
|
|
if n in defines:
|
|
buf.write("%define")
|
|
buf.write_nl(" %s %s" % (n, defines[n]))
|
|
defines_added += 1
|
|
if defines_added:
|
|
buf.write_nl()
|
|
build_options = kwargs.get('build_options', {})
|
|
pkg_build_options = build_options.get(pkg_key, [])
|
|
if pkg_build_options:
|
|
for build_option in pkg_build_options:
|
|
buf.write_nl(build_option)
|
|
buf.write_nl()
|
|
tags = kwargs.get('tags', [])
|
|
if tags:
|
|
for tag_name, tag_value in tags:
|
|
if not tag_value:
|
|
tag_value = 'Unknown'
|
|
buf.write_nl("%s: %s" % (tag_name,
|
|
one_line(tag_value, max_len=-1)))
|
|
buf.write_nl()
|
|
for blob in [kwargs.get('rpm_requires'), kwargs.get('rpm_conflicts')]:
|
|
if blob:
|
|
buf.write_nl(blob)
|
|
buf.write_nl()
|
|
description = kwargs.get('description', '')
|
|
buf.write_nl('%description')
|
|
if description:
|
|
# Fix how these blobs aren't always formatted so great...
|
|
tmp_buf = Buffer()
|
|
for line in description.splitlines():
|
|
tmp_buf.write_nl(line.strip())
|
|
description = tmp_buf.getvalue().strip()
|
|
if description:
|
|
buf.write_nl()
|
|
buf.write_nl(description)
|
|
buf.write_nl()
|
|
if options.with_tests:
|
|
buf.write_nl()
|
|
buf.write_nl("%package tests")
|
|
buf.write_nl("Group: Development/Libraries")
|
|
buf.write_nl("Summary: tests for %{name}")
|
|
for blob in [kwargs.get('test_rpm_requires'),
|
|
kwargs.get('test_rpm_conflicts')]:
|
|
if blob:
|
|
buf.write_nl(blob)
|
|
buf.write_nl()
|
|
buf.write_nl("%description tests")
|
|
buf.write_nl("Tests for %{name}")
|
|
buf.write_nl()
|
|
for script in ["prep", "build", "install", "clean"]:
|
|
buf.write_nl("%" + script)
|
|
buf.write_nl()
|
|
use_defaults = True
|
|
if options.scripts_dir:
|
|
script_filename = "%s-%s.sh" % (pkg_key, script)
|
|
script_path = os.path.join(options.scripts_dir, script_filename)
|
|
if os.path.isfile(script_path):
|
|
with open(script_path) as f_in:
|
|
buf.write_nl(f_in.read())
|
|
use_defaults = False
|
|
if use_defaults:
|
|
buf.write_nl(DEFAULT_SCRIPTS[script].strip())
|
|
if options.with_tests and script in DEFAULT_TESTS_SCRIPTS:
|
|
buf.write_nl(DEFAULT_TESTS_SCRIPTS[script].strip())
|
|
buf.write_nl()
|
|
buf.write_nl("""%files -f INSTALLED_FILES
|
|
|
|
%defattr(-,root,root)
|
|
""")
|
|
if options.with_tests:
|
|
buf.write_nl("""%files tests
|
|
%{_bindir}/%{pkg_name}-make-test-env
|
|
%{tests_data_dir}
|
|
""")
|
|
buf.write_nl()
|
|
return buf.getvalue().strip()
|
|
|
|
|
|
def build_rpm(options, filename, build_options):
|
|
if os.path.isfile(filename):
|
|
temp_dir = tempfile.mkdtemp('-unpack', 'py2rpm-')
|
|
pip.util.unpack_file(filename, temp_dir, None, None)
|
|
source_dir = temp_dir
|
|
archive_name = filename
|
|
elif os.path.isdir(filename):
|
|
temp_dir = None
|
|
archive_name = None
|
|
source_dir = filename
|
|
else:
|
|
raise InstallationError(
|
|
"`%s' is not a regular file nor a directory" % filename)
|
|
|
|
show_stage("Pre-analyzing path %s" % filename)
|
|
run_egg_info(source_dir, options)
|
|
info = pkg_info(source_dir)
|
|
rpm_requires, rpm_conflicts = requires_and_conflicts(
|
|
egg_info_requirements(source_dir))
|
|
test_rpm_requires, test_rpm_conflicts = requires_and_conflicts(
|
|
egg_info_requirements(source_dir, filename="test-requires.txt"),
|
|
skip_req_names=('sphinx', 'setuptools', 'setuptools-git', 'docutils'))
|
|
# NOTE(aababilov): do not use info["name"] to get the name - it is
|
|
# the key (e.g., "nose-plugin"), not the name ("nose_plugin")
|
|
pkg_name = setup_py_one_line(source_dir, "--name")
|
|
pkg_key = python_name_to_key(pkg_name)
|
|
build_dir = options.rpm_base
|
|
rpm_name = python_key_to_rpm(pkg_key)
|
|
version = one_line(info["version"])
|
|
cleaned_version = version.replace('-', '_')
|
|
spec_name = os.path.join(build_dir, "SPECS", "%s.spec" % rpm_name)
|
|
|
|
for path in (os.path.join(build_dir, "SPECS"),
|
|
os.path.join(build_dir, "SOURCES")):
|
|
if not os.path.isdir(path):
|
|
os.makedirs(path)
|
|
if not archive_name:
|
|
cmdline = [
|
|
sys.executable, setup_py, "sdist",
|
|
]
|
|
call_subprocess(cmdline, cwd=source_dir, raise_on_returncode=False)
|
|
archive_name = "%s/dist/%s-%s.tar.gz" % (source_dir, pkg_name, version)
|
|
shutil.copy(archive_name, os.path.join(build_dir, "SOURCES"))
|
|
|
|
# Make a spec file blob...
|
|
defines = {
|
|
'pkg_name': pkg_name,
|
|
'pkg_path': os.path.join(*pkg_name.split('.')),
|
|
'rpm_name': rpm_name,
|
|
'version': cleaned_version,
|
|
'release': options.release,
|
|
'unmangled_version': version,
|
|
}
|
|
if options.with_tests:
|
|
defines['tests_data_dir'] = "%{_datarootdir}/%{pkg_name}-tests"
|
|
tags = []
|
|
tags.append(("Name", "%{rpm_name}"))
|
|
epoch = epoch_map.get(pkg_key, options.epoch)
|
|
if epoch is not None:
|
|
tags.append(("Epoch", epoch))
|
|
tags.append(("Version", "%{version}"))
|
|
tags.append(("Release", "%{release}"))
|
|
tags.append(("Summary", info["summary"]))
|
|
archive_name = os.path.basename(archive_name)
|
|
if archive_name == ("%s-%s.tar.gz" % (pkg_name, version)):
|
|
tags.append(("Source0", "%{pkg_name}-%{unmangled_version}.tar.gz"))
|
|
else:
|
|
tags.append(("Source0", archive_name))
|
|
tags.append(("License", info["license"]))
|
|
tags.append(("Group", "Development/Libraries"))
|
|
tags.append(("BuildRoot", "%{_tmppath}/%{pkg_name}-%{unmangled_version}-%{release}-buildroot"))
|
|
tags.append(("Prefix", "%{_prefix}"))
|
|
if pkg_key not in arch_dependent:
|
|
if not os.path.exists(egg_info_path(source_dir, "ext_modules.txt")):
|
|
tags.append(("BuildArch", "noarch"))
|
|
tags.append(("Vendor", "%s <%s>" % (info["author"], info["author-email"])))
|
|
tags.append(("Url", info["home-page"]))
|
|
spec_blob = build_rpm_spec(pkg_key, pkg_name, options,
|
|
defines=defines,
|
|
build_options=build_options,
|
|
tags=tags,
|
|
description=info['description'],
|
|
rpm_requires=rpm_requires,
|
|
rpm_conflicts=rpm_conflicts,
|
|
test_rpm_requires=test_rpm_requires,
|
|
test_rpm_conflicts=test_rpm_conflicts)
|
|
show_stage("Writing spec file", details=spec_blob)
|
|
with open(spec_name, "w") as spec_file:
|
|
spec_file.write(spec_blob)
|
|
if not spec_blob.endswith("\n"):
|
|
spec_file.write("\n")
|
|
|
|
show_stage("Building")
|
|
if options.source_only:
|
|
rpmbuild_what = "-bs"
|
|
elif options.binary_only:
|
|
rpmbuild_what = "-bb"
|
|
else:
|
|
rpmbuild_what = "-ba"
|
|
call_subprocess(
|
|
[options.rpmbuild, rpmbuild_what,
|
|
"--define", "_topdir %s" % build_dir,
|
|
spec_name])
|
|
if temp_dir:
|
|
shutil.rmtree(temp_dir)
|
|
|
|
|
|
def main():
|
|
parser = create_parser()
|
|
options = parser.parse_args()
|
|
setup_logging(options)
|
|
global arch_dependent
|
|
global package_map
|
|
global epoch_map
|
|
arch_dependent = set(python_name_to_key(pkg)
|
|
for pkg in options.arch_dependent)
|
|
package_map = build_map(options.package_map)
|
|
epoch_map = build_map(options.epoch_map)
|
|
build_options = build_map_many(options.build_options)
|
|
|
|
if options.convert:
|
|
rpm_requires, rpm_conflicts = requires_and_conflicts(options.convert)
|
|
if rpm_requires:
|
|
print(rpm_requires.strip())
|
|
if rpm_conflicts:
|
|
print(rpm_conflicts.strip())
|
|
return
|
|
|
|
failed_pkgs = []
|
|
for src in (os.path.abspath(sdir) for sdir in options.sources):
|
|
try:
|
|
build_rpm(options, src, build_options)
|
|
except Exception as ex:
|
|
failed_pkgs.append((src, ex))
|
|
print(ex, file=sys.stderr)
|
|
if failed_pkgs:
|
|
print("These packages failed to build:", file=sys.stderr)
|
|
for descr in failed_pkgs:
|
|
print("%s:\n\t%s" % descr, file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|