82671430e6
We were restricted to older versions of pip due to some import changes, and code changes the pip folks did, but this changes that so that we can now use the newer versions of pip and all the features/additions they have performed there. Change-Id: Ia4bff0229acf7823c8fe9e0e95bf3ac02f9a6ce1
848 lines
28 KiB
Python
Executable File
848 lines
28 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
|
|
from pip import utils as pip_util
|
|
import six
|
|
|
|
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)
|
|
|
|
# NOTE(harlowja): try not to use info["version"] to get the version, since
|
|
# currently that is getting normalized by setuptools to be a normalized
|
|
# version which can be different from the actual version...
|
|
try:
|
|
version = setup_py_one_line(source_dir, "--version")
|
|
except Exception:
|
|
version = info['version']
|
|
logger.warning("Failed extracting version, falling back to '%s'",
|
|
version, exc_info=True)
|
|
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()
|