Source code for rebasehelper.build_helper
# -*- coding: utf-8 -*-
#
# This tool helps you to rebase package to the latest version
# Copyright (C) 2013-2014 Red Hat, Inc.
#
# 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
# he 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.
#
# Authors: Petr Hracek <phracek@redhat.com>
# Tomas Hozza <thozza@redhat.com>
import shlex
import shutil
import os
import six
import pkg_resources
from rebasehelper.utils import PathHelper
from rebasehelper.utils import TemporaryEnvironment
from rebasehelper.logger import logger
[docs]class SourcePackageBuildError(RuntimeError):
"""
Error indicating failure to build Source Package.
"""
def __init__(self, *args, **kwargs):
"""
Constructor of SourcePackageBuildError
:param args: tuple of arguments stored in the exception instance
:param kwargs: dictionary containing path to the logfile that contains main errors
"""
self.args = args
self.logfile = kwargs.get('logfile')
[docs]class BinaryPackageBuildError(RuntimeError):
"""
Error indicating failure to build Binary Package
"""
def __init__(self, *args, **kwargs):
"""
Constructor of BinaryPackageBuildError
:param args: tuple of arguments stored in the exception instance
:param kwargs: dictionary containing path to the logfile that contains main errors
"""
self.args = args
self.logfile = kwargs.get('logfile')
[docs]class BuildTemporaryEnvironment(TemporaryEnvironment):
"""
Class representing temporary environment.
"""
TEMPDIR_SOURCES = TemporaryEnvironment.TEMPDIR + '_SOURCES'
TEMPDIR_SPEC = TemporaryEnvironment.TEMPDIR + '_SPEC'
TEMPDIR_SPECS = TemporaryEnvironment.TEMPDIR + '_SPECS'
TEMPDIR_RESULTS = TemporaryEnvironment.TEMPDIR + '_RESULTS'
def __init__(self, sources, patches, spec, results_dir):
super(BuildTemporaryEnvironment, self).__init__(self._build_env_exit_callback)
self._env['results_dir'] = results_dir
self.sources = sources
self.patches = patches
self.spec = spec
def __enter__(self):
obj = super(BuildTemporaryEnvironment, self).__enter__()
log_message = "Copying '%s' to '%s'"
# create the directory structure
self._create_directory_structure()
# copy sources
for source in self.sources:
logger.debug(log_message, source, self._env[self.TEMPDIR_SOURCES])
shutil.copy(source, self._env[self.TEMPDIR_SOURCES])
# copy patches
for patch in self.patches:
logger.debug(log_message, patch, self._env[self.TEMPDIR_SOURCES])
shutil.copy(patch, self._env[self.TEMPDIR_SOURCES])
# copy SPEC file
spec_name = os.path.basename(self.spec)
self._env[self.TEMPDIR_SPEC] = os.path.join(self._env[self.TEMPDIR_SPECS], spec_name)
shutil.copy(self.spec, self._env[self.TEMPDIR_SPEC])
logger.debug(log_message, self.spec, self._env[self.TEMPDIR_SPEC])
return obj
def _create_directory_structure(self):
"""Function creating the directory structure in the TemporaryEnvironment."""
raise NotImplementedError('The create directory function has to be implemented in child class!')
def _build_env_exit_callback(self, results_dir, **kwargs):
"""
The function that is called just before the destruction of the TemporaryEnvironment.
It copies packages and logs into the results directory.
:param results_dir: absolute path to results directory
:return:
"""
os.makedirs(results_dir)
log_message = "Copying '%s' '%s' to '%s'"
# copy logs
for log in PathHelper.find_all_files(kwargs[self.TEMPDIR_RESULTS], '*.log'):
logger.debug(log_message, 'log', log, results_dir)
shutil.copy(log, results_dir)
# copy packages
for package in PathHelper.find_all_files(kwargs[self.TEMPDIR], '*.rpm'):
logger.debug(log_message, 'package', package, results_dir)
shutil.copy(package, results_dir)
[docs]class RpmbuildTemporaryEnvironment(BuildTemporaryEnvironment):
"""
Class representing temporary environment for RpmbuildBuildTool.
"""
TEMPDIR_RPMBUILD = TemporaryEnvironment.TEMPDIR + '_RPMBUILD'
TEMPDIR_BUILD = TemporaryEnvironment.TEMPDIR + '_BUILD'
TEMPDIR_BUILDROOT = TemporaryEnvironment.TEMPDIR + '_BUILDROOT'
TEMPDIR_RPMS = TemporaryEnvironment.TEMPDIR + '_RPMS'
TEMPDIR_SRPMS = TemporaryEnvironment.TEMPDIR + '_SRPMS'
def _create_directory_structure(self):
# create rpmbuild directory structure
for dir_name in ['RESULTS', 'rpmbuild']:
self._env[self.TEMPDIR + '_' + dir_name.upper()] = os.path.join(
self._env[self.TEMPDIR], dir_name)
logger.debug("Creating '%s'",
self._env[self.TEMPDIR + '_' + dir_name.upper()])
os.makedirs(self._env[self.TEMPDIR + '_' + dir_name.upper()])
for dir_name in ['BUILD', 'BUILDROOT', 'RPMS', 'SOURCES', 'SPECS',
'SRPMS']:
self._env[self.TEMPDIR + '_' + dir_name] = os.path.join(
self._env[self.TEMPDIR_RPMBUILD],
dir_name)
logger.debug("Creating '%s'",
self._env[self.TEMPDIR + '_' + dir_name])
os.makedirs(self._env[self.TEMPDIR + '_' + dir_name])
[docs]class BuildToolBase(object):
"""
Base class for various build tools
"""
DEFAULT = False
[docs] @classmethod
def match(cls, cmd=None, *args, **kwargs):
"""Checks if tool name matches the desired one."""
raise NotImplementedError()
[docs] @classmethod
def get_build_tool_name(cls):
"""Returns the name of the build tool."""
raise NotImplementedError()
[docs] @classmethod
def is_default(cls):
"""Checks if the tool is the default build tool."""
raise NotImplementedError()
[docs] @classmethod
def accepts_options(cls):
"""Checks if the tool accepts additional command line options."""
raise NotImplementedError()
[docs] @classmethod
def creates_tasks(cls):
"""Checks if the tool creates build tasks."""
raise NotImplementedError()
[docs] @classmethod
def prepare(cls, spec, conf):
"""
Prepare for building.
:param spec: spec file object
"""
# do nothing by default
pass
[docs] @classmethod
def build(cls, *args, **kwargs):
"""
Build binaries from the sources.
Keyword arguments:
spec -- path to a SPEC file
sources -- list with absolute paths to SOURCES
patches -- list with absolute paths to PATCHES
results_dir -- path to DIR where results should be stored
Returns:
dict with:
'srpm' -> absolute path to SRPM
'rpm' -> list of absolute paths to RPMs
'logs' -> list of absolute paths to logs
"""
raise NotImplementedError()
[docs] @classmethod
def get_logs(cls):
"""
Get logs from previously failed build
Returns:
dict with
'logs' -> list of absolute paths to logs
"""
raise NotImplementedError()
[docs] @classmethod
def wait_for_task(cls, build_dict, results_dir):
"""
Waits until specified task is finished
:param build_dict: build data
:param results_dir: path to DIR where results should be stored
:return: tuple with:
list of absolute paths to RPMs
list of absolute paths to logs
"""
# do nothing by default
return build_dict.get('rpm'), build_dict.get('logs')
[docs] @classmethod
def get_task_info(cls, build_dict):
"""
Gets information about detached remote task
:param build_dict: build data
:return: task info
"""
raise NotImplementedError()
[docs] @classmethod
def get_detached_task(cls, task_id, results_dir):
"""
Gets packages and logs for specified task
:param task_id: detached task id
:param results_dir: path to DIR where results should be stored
:return: tuple with:
list of absolute paths to RPMs
list of absolute paths to logs
"""
raise NotImplementedError()
[docs] @staticmethod
def get_builder_options(**kwargs):
builder_options = kwargs.get('builder_options')
if builder_options:
return shlex.split(builder_options)
return None
[docs] @staticmethod
def get_srpm_builder_options(**kwargs):
srpm_builder_options = kwargs.get('srpm_builder_options')
if srpm_builder_options:
return shlex.split(srpm_builder_options)
return None
@classmethod
def _build_srpm(cls, spec, sources, patches, results_dir, **kwargs):
"""
Builds the SRPM using chosen SRPM builder tool
:param spec: absolute path to the SPEC file.
:param sources: list with absolute paths to SOURCES
:param patches: list with absolute paths to PATCHES
:param results_dir: absolute path to DIR where results should be stored
:return: absolute path to SRPM, list with absolute paths to logs
"""
# build SRPM
srpm_results_dir = os.path.join(results_dir, "SRPM")
with RpmbuildTemporaryEnvironment(sources, patches, spec,
srpm_results_dir) as tmp_env:
srpm_builder_options = cls.get_srpm_builder_options(**kwargs)
srpm_build_tool = cls.get_srpm_buildtool(**kwargs)
env = tmp_env.env()
tmp_dir = tmp_env.path()
tmp_spec = env.get(RpmbuildTemporaryEnvironment.TEMPDIR_SPEC)
tmp_results_dir = env.get(
RpmbuildTemporaryEnvironment.TEMPDIR_RESULTS)
srpm_builder = SRPMBuilder.srpm_build_tools[srpm_build_tool]
srpm = srpm_builder.build_srpm(tmp_spec, tmp_dir, tmp_results_dir, srpm_results_dir,
srpm_builder_options=srpm_builder_options)
logger.info("Building SRPM finished successfully")
# srpm path in results_dir
srpm = os.path.join(srpm_results_dir, os.path.basename(srpm))
logger.debug("Successfully built SRPM: '%s'", str(srpm))
# gather logs
logs = [l for l in PathHelper.find_all_files(srpm_results_dir, '*.log')]
logger.debug("logs: '%s'", str(logs))
return srpm, logs
[docs]class SRPMBuildToolBase(object):
"""
Base class for SRPM builder tools
"""
DEFAULT = False
[docs] @classmethod
def get_build_tool_name(cls):
"""Returns name of the SRPM Build Tool"""
return NotImplementedError()
[docs] @classmethod
def is_default(cls):
"""Returns true if the SRPM Build Tool is set to be default"""
return NotImplementedError()
[docs] @classmethod
def build_srpm(cls, spec, workdir, results_dir, srpm_builder_options):
"""
Build SRPM with chosen SRPM Build Tool
:param spec: abs path to SPEC file inside the rpmbuild/SPECS in workdir.
:param workdir: abs path to working directory with rpmbuild directory
structure, which will be used as HOME dir.
:param results_dir: abs path to dir where the log should be placed.
:param srpm_builder_options: list of additional options to rpmbuild.
:return: If build process ends successfully returns abs path
to built SRPM, otherwise 'None'.
"""
raise NotImplementedError()
[docs]class SRPMBuilder(object):
"""
Builder class for building SRPMs.
"""
srpm_build_tools = {}
[docs] @classmethod
def load_srpm_build_tools(cls):
for entrypoint in pkg_resources.iter_entry_points('rebasehelper.srpm_build_tools'):
try:
srpm_build_tool = entrypoint.load()
except ImportError:
# silently skip broken plugin
continue
try:
cls.srpm_build_tools[srpm_build_tool.get_build_tool_name()] = srpm_build_tool
except (AttributeError, NotImplementedError):
# silently skip broken plugin
continue
[docs] @classmethod
def get_supported_tools(cls):
"""Returns list of supported srpm build tools"""
return cls.srpm_build_tools.keys()
[docs] @classmethod
def get_default_tool(cls):
"""Returns default build tool"""
default = [k for k, v in six.iteritems(cls.srpm_build_tools) if v.is_default()]
return default[0] if default else None
[docs]class Builder(object):
"""
Class representing a process of building binaries from sources.
"""
build_tools = {}
[docs] @classmethod
def load_build_tools(cls):
cls.build_tools = {}
for entrypoint in pkg_resources.iter_entry_points('rebasehelper.build_tools'):
try:
build_tool = entrypoint.load()
except ImportError:
# silently skip broken plugin
continue
try:
cls.build_tools[build_tool.get_build_tool_name()] = build_tool
except (AttributeError, NotImplementedError):
# silently skip broken plugin
continue
def __init__(self, tool=None):
if tool is None:
raise TypeError("Expected argument 'tool' (pos 1) is missing")
self._tool_name = tool
self._tool = None
for build_tool in self.build_tools.values():
if build_tool.match(self._tool_name):
self._tool = build_tool
if self._tool is None:
raise NotImplementedError("Unsupported build tool")
def __str__(self):
return "<Builder tool_name='{_tool_name}' tool='{_tool}'>".format(**vars(self))
[docs] def prepare(self, spec, conf):
"""Prepare for build"""
logger.debug("Preparing for build using '%s'", self._tool_name)
self._tool.prepare(spec, conf)
[docs] def build(self, *args, **kwargs):
"""Build sources."""
logger.debug("Building sources using '%s'", self._tool_name)
return self._tool.build(*args, **kwargs)
[docs] def get_logs(self):
"""Get logs."""
logger.debug("Getting logs '%s'", self._tool_name)
return self._tool.get_logs()
[docs] def wait_for_task(self, build_dict, results_dir):
"""Wait for task"""
logger.debug("Waiting for task using '%s'", self._tool_name)
return self._tool.wait_for_task(build_dict, results_dir)
[docs] def get_task_info(self, build_dict):
"""Get task info"""
logger.debug("Getting task info using '%s'", self._tool_name)
return self._tool.get_task_info(build_dict)
[docs] def get_detached_task(self, task_id, results_dir):
"""Get detached task"""
logger.debug("Getting detached task using '%s'", self._tool_name)
return self._tool.get_detached_task(task_id, results_dir)
[docs] @classmethod
def get_supported_tools(cls):
"""
Returns a list of supported build tools
:return: list of supported build tools
"""
return cls.build_tools.keys()
[docs] @classmethod
def get_default_tool(cls):
"""Returns default build tool"""
default = [k for k, v in six.iteritems(cls.build_tools) if v.is_default()]
return default[0] if default else None
SRPMBuilder.load_srpm_build_tools()
Builder.load_build_tools()