--- /dev/null
+# Copyright 2019 Nokia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+""" Module in charge of building a project """
+import glob
+import logging
+import os
+import pwd
+import shutil
+import subprocess
+from distutils.spawn import find_executable
+
+import datetime
+from rpmbuilder.baseerror import RpmbuilderError
+from rpmbuilder.prettyprinter import Prettyprint
+
+PIGZ_INSTALLED = False
+PBZIP2_INSTALLED = False
+PXZ_INSTALLED = False
+
+class Packagebuilding(object):
+
+ """ Object for building rpm files with mock """
+
+ def __init__(self, masterargs):
+ # Chroothousekeeping cleans chroot in case of mock errors. This should
+ # keep /var/lib/mock from growing too much
+ self.masterargs = masterargs
+ self.logger = logging.getLogger(__name__)
+ self.__check_tool_availability()
+ self.chroot_installed_rpms = []
+
+ if find_executable("pigz"):
+ global PIGZ_INSTALLED
+ PIGZ_INSTALLED = True
+ self.logger.debug("pigz is available")
+ if find_executable("pbzip2"):
+ global PBZIP2_INSTALLED
+ PBZIP2_INSTALLED = True
+ self.logger.debug("pbzip2 is available")
+ if find_executable("pxz"):
+ global PXZ_INSTALLED
+ PXZ_INSTALLED = True
+ self.logger.debug("pxz is available")
+
+ @staticmethod
+ def __check_tool_availability():
+ """ Verify that user belongs to mock group for things to work """
+ username = pwd.getpwuid(os.getuid())[0]
+ cmd = "id " + username + "| grep \\(mock\\) > /dev/null"
+ if os.system(cmd) != 0:
+ raise PackagebuildingError("Mock tool requires user to "
+ "belong to group called mock")
+ return True
+
+ def patch_specfile(self, origspecfile, outputdir, newversion, newrelease):
+ """ Spec file is patched with version information from git describe """
+ Prettyprint().print_heading("Patch spec", 50)
+ self.logger.info("Patching new spec from %s", origspecfile)
+ self.logger.debug(" - Version: %s", newversion)
+ self.logger.debug(" - Release: %s", newrelease)
+
+ specfilebasename = os.path.basename(origspecfile)
+ patchedspecfile = os.path.join(outputdir, specfilebasename)
+ self.logger.debug("Writing new spec file to %s", patchedspecfile)
+
+ with open(origspecfile, 'r') as filepin:
+ filepin_lines = filepin.readlines()
+
+ with open(patchedspecfile, 'w') as filepout:
+ for line in filepin_lines:
+ linestripped = line.strip()
+ if not linestripped.startswith("#"):
+ # Check if version could be patched
+ if linestripped.lower().startswith("version:"):
+ filepout.write("Version: " + newversion + '\n')
+ elif linestripped.lower().startswith("release:"):
+ filepout.write("Release: " + newrelease + '\n')
+ else:
+ filepout.write(line)
+ return patchedspecfile
+
+ def init_mock_chroot(self, resultdir, configdir, root):
+ """
+ Start a mock chroot where build requirements
+ can be installed before building
+ """
+ Prettyprint().print_heading("Mock init in " + root, 50)
+
+ self.clean_directory(resultdir)
+
+ mock_arg_resultdir = "--resultdir=" + resultdir
+
+ mocklogfile = resultdir + '/mock-init-' + root + '.log'
+
+ arguments = [mock_arg_resultdir,
+ "--scrub=all"]
+ self.run_mock_command(arguments, mocklogfile, configdir, root)
+
+ #Allow the builder to run sudo without terminal and without password
+ #This makes it possible to run disk image builder needed by ipa-builder
+ allow_sudo_str = "mockbuild ALL=(ALL) NOPASSWD: ALL"
+ notty_str = "Defaults:mockbuild !requiretty"
+ sudoers_file = "/etc/sudoers"
+ command = "grep \'%s\' %s || echo -e \'%s\n%s\' >> %s" %(allow_sudo_str, sudoers_file, allow_sudo_str, notty_str, sudoers_file)
+ arguments=["--chroot",
+ command ]
+ self.run_mock_command(arguments, mocklogfile, configdir, root)
+
+ return True
+
+ def restore_local_repository(self, localdir, destdir, configdir, root, logfile):
+ """
+ Mock copying local yum repository to mock environment so that it can
+ be used during building of other RPM packages.
+ """
+ Prettyprint().print_heading("Restoring local repository", 50)
+ arguments = ["--copyin",
+ localdir,
+ destdir]
+ self.run_mock_command(arguments, logfile, configdir, root)
+
+ def mock_source_rpm(self, hostsourcedir, specfile, resultdir, configdir, root):
+ """ Mock SRPM file which can be used to build rpm """
+ Prettyprint().print_heading("Mock source rpm in " + root, 50)
+ self.logger.info("Build from:")
+ self.logger.info(" - source directory %s", hostsourcedir)
+ self.logger.info(" - spec %s", specfile)
+
+ self.clean_directory(resultdir)
+
+ mock_arg_resultdir = "--resultdir=" + resultdir
+ mock_arg_spec = "--spec=" + specfile
+ mock_arg_sources = "--sources=" + hostsourcedir
+ arguments = [mock_arg_resultdir,
+ "--no-clean",
+ "--no-cleanup-after",
+ "--buildsrpm",
+ mock_arg_sources,
+ mock_arg_spec]
+
+ mocklogfile = resultdir + '/mock.log'
+ self.run_mock_command(arguments, mocklogfile, configdir, root)
+
+ # Find source rpm and return the path
+ globstring = resultdir + '/*.src.rpm'
+ globmatches = glob.glob(globstring)
+ assert len(globmatches) == 1, "Too many source rpm files"
+
+ return globmatches[0]
+
+ def mock_rpm(self, sourcerpm, resultdir, configdir, root):
+ """ Mock RPM binary file from SRPM """
+ Prettyprint().print_heading("Mock rpm in " + root, 50)
+ self.logger.info("Building from:")
+ self.logger.info(" - source rpm %s", sourcerpm)
+
+ self.clean_directory(resultdir)
+
+ mock_arg_resultdir = "--resultdir=" + resultdir
+ arguments = [mock_arg_resultdir,
+ "--no-clean",
+ "--no-cleanup-after",
+ "--rebuild",
+ sourcerpm]
+
+ mocklogfile = resultdir + '/mock.log'
+ self.run_mock_command(arguments, mocklogfile, configdir, root)
+
+ self.logger.debug("RPM files build to: %s", resultdir)
+ return True
+
+ def mock_rpm_from_archive(self, source_tar_packages, resultdir, configdir, root):
+ """ Mock rpm binary file straight from archive file """
+ self.clean_directory(resultdir)
+
+ # Copy source archive to chroot
+ chroot_sourcedir = "/builddir/build/SOURCES/"
+ self.copy_to_chroot(configdir, root, resultdir, source_tar_packages, chroot_sourcedir)
+
+ # Create rpm from source archive
+ sourcebasename = os.path.basename(source_tar_packages[0])
+ chrootsourcefile = os.path.join(chroot_sourcedir, sourcebasename)
+
+ Prettyprint().print_heading("Mock rpm in " + root, 50)
+ self.logger.info("Building from:")
+ self.logger.info(" - source archive %s", chrootsourcefile)
+
+ mock_arg_resultdir = "--resultdir=" + resultdir
+ rpmbuildcommand = "/usr/bin/rpmbuild --noclean -tb -v "
+ rpmbuildcommand += os.path.join(chroot_sourcedir, chrootsourcefile)
+ arguments = [mock_arg_resultdir,
+ "--chroot",
+ rpmbuildcommand]
+ mocklogfile = resultdir + '/mock-rpmbuild.log'
+ self.run_mock_command(arguments, mocklogfile, configdir, root)
+
+ def mock_rpm_from_filesystem(self, path, spec, resultdir, configdir, root, srpm_resultdir):
+ """ Mock rpm binary file straight from archive file """
+ self.clean_directory(resultdir)
+ # Copy source archive to chroot
+ chroot_sourcedir = "/builddir/build/"
+ self.copy_to_chroot(configdir, root, resultdir, [os.path.join(path, 'SPECS', spec)], os.path.join(chroot_sourcedir, 'SPECS'))
+ self.copy_to_chroot(configdir, root, resultdir, [os.path.join(path, 'SOURCES', f) for f in os.listdir(os.path.join(path, 'SOURCES'))], os.path.join(chroot_sourcedir, 'SOURCES'))
+
+ Prettyprint().print_heading("Mock rpm in " + root, 50)
+ mocklogfile = resultdir + '/mock-rpmbuild.log'
+ mock_arg_resultdir = "--resultdir=" + resultdir
+ arguments = [mock_arg_resultdir,
+ "--chroot",
+ "chown -R root:root "+chroot_sourcedir]
+ self.run_mock_command(arguments, mocklogfile, configdir, root)
+ rpmbuildcommand = "/usr/bin/rpmbuild --noclean -ba -v "
+ rpmbuildcommand += os.path.join(chroot_sourcedir, 'SPECS', spec)
+ arguments = [mock_arg_resultdir,
+ "--chroot",
+ rpmbuildcommand]
+ mocklogfile = resultdir + '/mock-rpmbuild.log'
+ self.run_mock_command(arguments, mocklogfile, configdir, root)
+
+ arguments = ["--copyout",
+ "/builddir/build", resultdir+"/tmp/packages"]
+ mocklogfile = resultdir + '/mock-copyout.log'
+ self.run_mock_command(arguments, mocklogfile, configdir, root)
+
+ for filename in glob.glob(resultdir+"/tmp/packages/RPMS/*"):
+ shutil.move(filename, resultdir)
+
+ for filename in glob.glob(resultdir+"/tmp/packages/SRPMS/*"):
+ shutil.move(filename, srpm_resultdir)
+
+ def mock_wipe_buildroot(self, resultdir, configdir, root):
+ """ Wipe buildroot clean """
+ Prettyprint().print_heading("Wiping buildroot", 50)
+ arguments = ["--chroot",
+ "mkdir -pv /usr/localrepo && " \
+ "cp -v /builddir/build/RPMS/*.rpm /usr/localrepo/. ;" \
+ "rm -rf /builddir/build/{BUILD,RPMS,SOURCES,SPECS,SRPMS}/*"]
+ mocklogfile = resultdir + '/mock-wipe-buildroot.log'
+ self.run_mock_command(arguments, mocklogfile, configdir, root)
+
+ def update_local_repository(self, configdir, root):
+ Prettyprint().print_heading("Update repository " + root, 50)
+
+ arguments = ["--chroot",
+ "mkdir -pv /usr/localrepo && " \
+ "createrepo --update /usr/localrepo && yum clean expire-cache"]
+ self.run_mock_command(arguments, configdir+"/log", configdir, root)
+
+ def copy_to_chroot(self, configdir, root, resultdir, source_files, destination):
+ # Copy source archive to chroot
+ Prettyprint().print_heading("Copy source archive to " + root, 50)
+ self.logger.info(" - Copy from %s", source_files)
+ self.logger.info(" - Copy to %s", destination)
+
+ mock_arg_resultdir = "--resultdir=" + resultdir
+ arguments = [mock_arg_resultdir,
+ "--copyin"]
+ arguments.extend(source_files)
+ arguments.append(destination)
+
+ mocklogfile = resultdir + '/mock-copyin.log'
+ self.run_mock_command(arguments, mocklogfile, configdir, root)
+
+ def scrub_mock_chroot(self, configdir, root):
+ time_start = datetime.datetime.now()
+ Prettyprint().print_heading("Scrub mock chroot " + root, 50)
+ mock_clean_command = ["/usr/bin/mock",
+ "--configdir=" + configdir,
+ "--root=" + root,
+ "--uniqueext=" + self.masterargs.uniqueext,
+ "--orphanskill",
+ "--scrub=chroot"]
+ self.logger.info("Removing mock chroot.")
+ self.logger.debug(" ".join(mock_clean_command))
+ try:
+ subprocess.check_call(mock_clean_command,
+ shell=False,
+ stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError as err:
+ raise PackagebuildingError("Mock chroot removal failed. Error code %s" % (err.returncode))
+ time_delta = datetime.datetime.now() - time_start
+ self.logger.debug('[mock-end] cmd="%s" took=%s (%s sec)', mock_clean_command, time_delta, time_delta.seconds)
+
+ def run_builddep(self, specfile, resultdir, configdir, root):
+ arguments = ["--copyin"]
+ arguments.append(specfile)
+ arguments.append("/builddir/"+os.path.basename(specfile))
+
+ mocklogfile = resultdir + '/mock-builddep.log'
+ self.run_mock_command(arguments, mocklogfile, configdir, root)
+
+ builddepcommand = "/usr/bin/yum-builddep -y "+"/builddir/"+os.path.basename(specfile)
+ arguments = ["--chroot",
+ builddepcommand]
+ mocklogfile = resultdir + '/mock-builddep.log'
+ return self.run_mock_command(arguments, mocklogfile, configdir, root, True) == 0
+
+ def run_mock_command(self, arguments, outputfile, configdir, root, return_error=False):
+ """ Mock binary rpm package """
+ mock_command = ["/usr/bin/mock",
+ "--configdir=" + configdir,
+ "--root=" + root,
+ "--uniqueext=" + self.masterargs.uniqueext,
+ "--verbose",
+ "--old-chroot",
+ "--enable-network"]
+ mock_command.extend(arguments)
+ if self.masterargs.mockarguments:
+ mock_command.extend([self.masterargs.mockarguments])
+ self.logger.info("Running mock. Log goes to %s", outputfile)
+ self.logger.debug('[mock-start] cmd="%s"', mock_command)
+ time_start = datetime.datetime.now()
+ self.logger.debug(" ".join(mock_command))
+ with open(outputfile, 'a') as filep:
+ try:
+ mockproc = subprocess.Popen(mock_command,
+ shell=False,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ for line in iter(mockproc.stdout.readline, b''):
+ if self.masterargs.verbose:
+ self.logger.debug("mock-%s", line.rstrip('\n'))
+ filep.write(line)
+ _, stderr = mockproc.communicate() # wait for the subprocess to exit
+ if return_error:
+ return mockproc.returncode
+ if mockproc.returncode != 0:
+ raise Mockcommanderror(returncode=mockproc.returncode)
+ except Mockcommanderror as err:
+ self.logger.error("There was a failure during mocking")
+ if self.masterargs.scrub:
+ self.scrub_mock_chroot(configdir, root)
+ guidance_message = ""
+ else:
+ mock_shell_command = ["/usr/bin/mock",
+ "--configdir=" + configdir,
+ "--root=" + root,
+ "--uniqueext=" + self.masterargs.uniqueext,
+ "--shell"]
+ guidance_message = ". To open mock shell, run the following: " + " ".join(mock_shell_command)
+ raise PackagebuildingError("Mock exited with value \"%s\". "
+ "Log for debuging: %s %s" % (err.returncode, outputfile, guidance_message))
+ except OSError:
+ raise PackagebuildingError("Mock executable not found. "
+ "Have you installed mock?")
+ except:
+ raise
+ time_delta = datetime.datetime.now() - time_start
+ self.logger.debug('[mock-end] cmd="%s" took=%s (%s sec)', mock_command, time_delta, time_delta.seconds)
+
+ def clean_directory(self, directory):
+ """ Make sure given directory exists and is clean """
+ if os.path.isdir(directory):
+ shutil.rmtree(directory)
+ os.makedirs(directory)
+
+ def tar_filter(self, tarinfo):
+ """ Filter git related and spec files away """
+ if tarinfo.name.endswith('.spec') or tarinfo.name.endswith('.git'):
+ self.logger.debug("Ignore %s", tarinfo.name)
+ return None
+ self.logger.debug("Archiving %s", tarinfo.name)
+ return tarinfo
+
+ def create_source_archive(self,
+ package_name,
+ sourcedir,
+ outputdir,
+ project_changed,
+ archive_file_extension):
+ """
+ Create tar file. Example helloworld-2.4.tar.gz
+ Tar file has naming <name>-<version>.tar.gz
+ """
+ Prettyprint().print_heading("Tar package creation", 50)
+
+ tar_file = package_name + '.' + 'tar'
+ # Directory where tar should be stored.
+ # Example /var/mybuild/workspace/sources
+
+ tarfilefullpath = os.path.join(outputdir, tar_file)
+ if os.path.isfile(tarfilefullpath) and not project_changed:
+ self.logger.info("Using cached %s", tarfilefullpath)
+ return tarfilefullpath
+
+ self.logger.info("Creating tar file %s", tarfilefullpath)
+ # sourcedir = /var/mybuild/helloworld/checkout
+ # sourcedir_dirname = /var/mybuild/helloworld
+ # sourcedir_basename = checkout
+ sourcedir_dirname = os.path.dirname(sourcedir)
+
+ os.chdir(sourcedir_dirname)
+
+ tar_params = ["tar", "cf", tarfilefullpath, "--directory="+os.path.dirname(sourcedir)]
+ tar_params = tar_params+["--exclude-vcs"]
+ tar_params = tar_params+["--transform=s/" + os.path.basename(sourcedir) + "/" + os.path.join(package_name) + "/"]
+ tar_params = tar_params+[os.path.basename(sourcedir)]
+ self.logger.debug("Running: %s", " ".join(tar_params))
+ ret = subprocess.call(tar_params)
+ if ret > 0:
+ raise PackagebuildingError("Tar error: %s", ret)
+
+ git_dir = os.path.join(os.path.basename(sourcedir), '.git')
+ if os.path.exists(git_dir):
+ tar_params = ["tar", "rf", tarfilefullpath, "--directory="+os.path.dirname(sourcedir)]
+ tar_params += ["--transform=s/" + os.path.basename(sourcedir) + "/" + os.path.join(package_name) + "/"]
+ tar_params += ['--dereference', git_dir]
+ self.logger.debug("Running: %s", " ".join(tar_params))
+ ret = subprocess.call(tar_params)
+ if ret > 1:
+ self.logger.warning("Git dir tar failed")
+
+ if archive_file_extension == "tar.gz":
+ if PIGZ_INSTALLED:
+ cmd = ['pigz', '-f']
+ else:
+ cmd = ['gzip', '-f']
+ resultfile = tarfilefullpath + '.gz'
+ else:
+ raise PackagebuildingError("Unknown source archive format: %s" % archive_file_extension)
+ cmd += [tarfilefullpath]
+ self.logger.debug("Running: %s", " ".join(cmd))
+ ret = subprocess.call(cmd)
+ if ret > 0:
+ raise PackagebuildingError("Cmd error: %s", ret)
+
+ return resultfile
+
+class Mockcommanderror(RpmbuilderError):
+ def __init__(self, returncode):
+ self.returncode = returncode
+
+class PackagebuildingError(RpmbuilderError):
+
+ """ Exceptions originating from Builder and main level """
+ pass