support/package-builder/PackageBuilder.py
87815216
 import sys
 import os.path
2820c61a
 from PackageUtils import PackageUtils
 from Logger import Logger
 from ChrootUtils import ChrootUtils
 from ToolChainUtils import ToolChainUtils
 from CommandUtils import CommandUtils
 from constants import constants
45c9260c
 from SpecData import SPECS
56c77555
 import docker
4a93976f
 from distutils.version import LooseVersion
2820c61a
 
56c77555
 class PackageBuilderBase(object):
326d5ca8
     def __init__(self, mapPackageToCycles, pkgBuildType):
f3cc9fa8
         # will be initialized in buildPackageFunction()
87815216
         self.logName = None
         self.logPath = None
         self.logger = None
         self.package = None
2820c61a
         self.mapPackageToCycles = mapPackageToCycles
87815216
         self.listNodepsPackages = ["glibc", "gmp", "zlib", "file", "binutils", "mpfr",
                                    "mpc", "gcc", "ncurses", "util-linux", "groff", "perl",
                                    "texinfo", "rpm", "openssl", "go"]
56c77555
         self.pkgBuildType = pkgBuildType
97a9151c
 
326d5ca8
     def buildPackageFunction(self, package):
         self._buildPackagePrepareFunction(package)
5d822c2d
         versions = self.getNumOfVersions(package)
         if(versions < 1):
             raise Exception("No package exists")
         for version in range(0, versions):
             try:
                 self._buildPackage(version)
             except Exception as e:
                 # TODO: self.logger might be None
                 self.logger.exception(e)
                 raise e
326d5ca8
 
     def _buildPackagePrepareFunction(self, package):
87815216
         self.package = package
         self.logName = "build-" + package
         self.logPath = constants.logPath + "/build-" + package
56c77555
         if not os.path.isdir(self.logPath):
             cmdUtils = CommandUtils()
87815216
             cmdUtils.runCommandInShell("mkdir -p " + self.logPath)
         self.logger = Logger.getLogger(self.logName, self.logPath)
97a9151c
 
326d5ca8
     def _findPackageNameFromRPMFile(self, rpmfile):
87815216
         rpmfile = os.path.basename(rpmfile)
         releaseindex = rpmfile.rfind("-")
2820c61a
         if releaseindex == -1:
87815216
             self.logger.error("Invalid rpm file:" + rpmfile)
2820c61a
             return None
87815216
         versionindex = rpmfile[0:releaseindex].rfind("-")
2820c61a
         if versionindex == -1:
87815216
             self.logger.error("Invalid rpm file:" + rpmfile)
2820c61a
             return None
87815216
         packageName = rpmfile[0:versionindex]
2820c61a
         return packageName
97a9151c
 
326d5ca8
     def _findInstalledPackages(self, instanceID):
56c77555
         pkgUtils = PackageUtils(self.logName, self.logPath)
         if self.pkgBuildType == "chroot":
             listInstalledRPMs = pkgUtils.findInstalledRPMPackages(instanceID)
         elif self.pkgBuildType == "container":
             listInstalledRPMs = pkgUtils.findInstalledRPMPackagesInContainer(instanceID)
87815216
         listInstalledPackages = []
2820c61a
         for installedRPM in listInstalledRPMs:
326d5ca8
             packageName = self._findPackageNameFromRPMFile(installedRPM)
2820c61a
             if packageName is not None:
                 listInstalledPackages.append(packageName)
56c77555
         return listInstalledPackages, listInstalledRPMs
adf248d5
 
5d822c2d
     def _checkIfPackageIsAlreadyBuilt(self, index=0):
87815216
         basePkg = SPECS.getData().getSpecName(self.package)
5d822c2d
         listRPMPackages = SPECS.getData().getRPMPackages(basePkg, index)
87815216
         packageIsAlreadyBuilt = True
         pkgUtils = PackageUtils(self.logName, self.logPath)
adf248d5
         for pkg in listRPMPackages:
4a93976f
             if pkgUtils.findRPMFileForGivenPackage(pkg,"*", index) is None:
87815216
                 packageIsAlreadyBuilt = False
adf248d5
                 break
         return packageIsAlreadyBuilt
 
5d822c2d
     def _findRunTimeRequiredRPMPackages(self, rpmPackage, index=0):
         return SPECS.getData().getRequiresForPackage(rpmPackage, index)
97a9151c
 
5d822c2d
     def _findBuildTimeRequiredPackages(self, index=0):
         return SPECS.getData().getBuildRequiresForPackage(self.package, index)
97a9151c
 
5d822c2d
     def _findBuildTimeCheckRequiredPackages(self, index=0):
         return SPECS.getData().getCheckBuildRequiresForPackage(self.package, index)
5f40784b
 
4a93976f
     def _findRunTimeRequiredRPMPackagesParseObj(self,rpmPackage):
         listRequiredPackages=SPECS.getData().getRequiresParseObjForPackage(rpmPackage)
         return listRequiredPackages
 
     def _findBuildTimeRequiredPackagesParseObj(self, index):
         listRequiredPackages=SPECS.getData().getBuildRequiresParseObjForPackage(self.package, index)
         return listRequiredPackages
 
     def _findBuildTimeCheckRequiredPackagesParseObj(self, index):
         listRequiredPackages=SPECS.getData().getCheckBuildRequiresParseObjForPackage(self.package, index)
         return listRequiredPackages
 
     def _getProperVersion(self,package,parseSpecObj):
         listOfVersionObjs=self.getSpecObj(package)
         for num in listOfVersionObjs:
                 if parseSpecObj.compare == 'gte':
                        if LooseVersion(num.version) >= LooseVersion(parseSpecObj.version):
                                 return num.version
                 elif parseSpecObj.compare == 'lte':
                         if LooseVersion(num.version) <= LooseVersion(parseSpecObj.version):
                                 return num.version
                 elif parseSpecObj.compare == 'eq':
                         if LooseVersion(num.version) == LooseVersion(parseSpecObj.version):
                                 return num.version
                 elif parseSpecObj.compare == 'lt':
                         if LooseVersion(num.version) < LooseVersion(parseSpecObj.version):
                                 return num.version
                 elif parseSpecObj.compare == 'gt':
                         if LooseVersion(num.version) > LooseVersion(parseSpecObj.version):
                                 return num.version
         return "*"
 
 
     def _installPackage(self, pkgUtils, package,packageVersion, instanceID, destLogPath,
326d5ca8
                         listInstalledPackages, listInstalledRPMs):
87815216
         latestRPM = os.path.basename(
             pkgUtils.findRPMFileForGivenPackage(package)).replace(".rpm", "")
56c77555
         if package in listInstalledPackages and latestRPM in listInstalledRPMs:
518d6a6f
             return
75cb675a
         # mark it as installed -  to avoid cyclic recursion
         listInstalledPackages.append(package)
56c77555
         listInstalledRPMs.append(latestRPM)
326d5ca8
         self._installDependentRunTimePackages(pkgUtils, package, instanceID, destLogPath,
                                               listInstalledPackages, listInstalledRPMs)
87815216
         noDeps = False
326d5ca8
         if (package in self.mapPackageToCycles or
                 package in self.listNodepsPackages or
                 package in constants.noDepsPackageList):
87815216
             noDeps = True
56c77555
         if self.pkgBuildType == "chroot":
4a93976f
             pkgUtils.installRPM(package, packageVersion,instanceID, noDeps, destLogPath)
56c77555
         elif self.pkgBuildType == "container":
4a93976f
             pkgUtils.prepRPMforInstallInContainer(package,packageVersion, instanceID, noDeps, destLogPath)
2820c61a
 
326d5ca8
     def _installDependentRunTimePackages(self, pkgUtils, package, instanceID, destLogPath,
                                          listInstalledPackages, listInstalledRPMs):
         listRunTimeDependentPackages = self._findRunTimeRequiredRPMPackages(package)
4a93976f
         listRunTimeDependentPackagesParseObj=self._findRunTimeRequiredRPMPackagesParseObj(package)
326d5ca8
         if listRunTimeDependentPackages:
2820c61a
             for pkg in listRunTimeDependentPackages:
326d5ca8
                 if pkg in self.mapPackageToCycles:
2820c61a
                     continue
87815216
                 latestPkgRPM = os.path.basename(
                     pkgUtils.findRPMFileForGivenPackage(pkg)).replace(".rpm", "")
56c77555
                 if pkg in listInstalledPackages and latestPkgRPM in listInstalledRPMs:
2820c61a
                     continue
4a93976f
                 flag = False
                 for objName in listRunTimeDependentPackagesParseObj:
                     if objName.package == pkg:
                         properVersion=self._getProperVersion(pkg,objName)
                         self._installPackage(pkgUtils, pkg,properVersion, instanceID, destLogPath,listInstalledPackages, listInstalledRPMs)
                         flag = True
                         break;
                 if flag == False:
                         self._installPackage(pkgUtils, pkg,"*", instanceID, destLogPath,listInstalledPackages, listInstalledRPMs)
326d5ca8
 
5d822c2d
     def _findDependentPackagesAndInstalledRPM(self, instanceID, index=0):
326d5ca8
         listInstalledPackages, listInstalledRPMs = self._findInstalledPackages(instanceID)
         self.logger.info(listInstalledPackages)
5d822c2d
         listDependentPackages = self._findBuildTimeRequiredPackages(index)
4a93976f
         listDependentPackagesParseObj=self._findBuildTimeRequiredPackagesParseObj(index)
326d5ca8
         if constants.rpmCheck and self.package in constants.testForceRPMS:
5d822c2d
             listDependentPackages.extend(self._findBuildTimeCheckRequiredPackages(index))
4a93976f
             listDependentPackagesParseObj.extend(self._findBuildTimeCheckRequiredPackagesParseObj(index))
326d5ca8
             testPackages = (set(constants.listMakeCheckRPMPkgtoInstall) -
                             set(listInstalledPackages) -
                             set([self.package]))
             listDependentPackages.extend(testPackages)
             listDependentPackages = list(set(listDependentPackages))
4a93976f
             listDependentPackagesParseObj=list(set(listDependentPackagesParseObj))
         return listDependentPackages, listDependentPackagesParseObj,listInstalledPackages, listInstalledRPMs
326d5ca8
 
5d822c2d
     @staticmethod
     def getNumOfVersions(package):
         return SPECS.getData().getNumberOfVersions(package)
326d5ca8
 
4a93976f
     @staticmethod
     def getSpecObj(package):
         return SPECS.getData().getSpecObj(package)
 
326d5ca8
 class PackageBuilderContainer(PackageBuilderBase):
     def __init__(self, mapPackageToCycles, pkgBuildType):
56c77555
         self.buildContainerImage = "photon_build_container:latest"
         self.dockerClient = docker.from_env(version="auto")
 
326d5ca8
         PackageBuilderBase.__init__(self, mapPackageToCycles, pkgBuildType)
56c77555
 
326d5ca8
     def _prepareBuildContainer(self, containerTaskName, packageName,
                                isToolChainPackage=False):
56c77555
         # Prepare an empty chroot environment to let docker use the BUILD folder.
         # This avoids docker using overlayFS which will cause make check failure.
87815216
         chrootName = "build-" + packageName
326d5ca8
         chrUtils = ChrootUtils(self.logName, self.logPath)
87815216
         returnVal, chrootID = chrUtils.createChroot(chrootName)
56c77555
         if not returnVal:
             raise Exception("Unable to prepare build root")
         cmdUtils = CommandUtils()
         cmdUtils.runCommandInShell("mkdir -p " + chrootID + constants.topDirPath)
         cmdUtils.runCommandInShell("mkdir -p " + chrootID + constants.topDirPath + "/BUILD")
 
         containerID = None
         mountVols = {
87815216
             constants.prevPublishRPMRepo: {'bind': '/publishrpms', 'mode': 'ro'},
             constants.prevPublishXRPMRepo: {'bind': '/publishxrpms', 'mode': 'ro'},
             constants.tmpDirPath: {'bind': '/tmp', 'mode': 'rw'},
             constants.rpmPath: {'bind': constants.topDirPath + "/RPMS", 'mode': 'rw'},
             constants.sourceRpmPath: {'bind': constants.topDirPath + "/SRPMS", 'mode': 'rw'},
326d5ca8
             constants.logPath + "/" + self.logName: {'bind': constants.topDirPath + "/LOGS",
                                                      'mode': 'rw'},
87815216
             chrootID + constants.topDirPath + "/BUILD": {'bind': constants.topDirPath + "/BUILD",
                                                          'mode': 'rw'},
             constants.dockerUnixSocket: {'bind': constants.dockerUnixSocket, 'mode': 'rw'}
             }
56c77555
 
         containerName = containerTaskName
         containerName = containerName.replace("+", "p")
         try:
             oldContainerID = self.dockerClient.containers.get(containerName)
             if oldContainerID is not None:
                 oldContainerID.remove(force=True)
         except docker.errors.NotFound:
87815216
             try:
                 sys.exc_clear()
             except:
                 pass
56c77555
 
         try:
326d5ca8
             self.logger.info("BuildContainer-prepareBuildContainer: " +
                              "Starting build container: " + containerName)
56c77555
             #TODO: Is init=True equivalent of --sig-proxy?
             privilegedDocker = False
             cap_list = ['SYS_PTRACE']
             if packageName in constants.listReqPrivilegedDockerForTest:
                 privilegedDocker = True
 
             containerID = self.dockerClient.containers.run(self.buildContainerImage,
87815216
                                                            detach=True,
                                                            cap_add=cap_list,
                                                            privileged=privilegedDocker,
                                                            name=containerName,
                                                            network_mode="host",
                                                            volumes=mountVols,
                                                            command="/bin/bash -l -c /wait.sh")
 
326d5ca8
             self.logger.debug("Started Photon build container for task " + containerTaskName +
                               " ID: " + containerID.short_id)
56c77555
             if not containerID:
87815216
                 raise Exception("Unable to start Photon build container for task " +
                                 containerTaskName)
56c77555
         except Exception as e:
326d5ca8
             self.logger.debug("Unable to start Photon build container for task " +
                               containerTaskName)
56c77555
             raise e
         return containerID, chrootID
b5e09fac
 
5d822c2d
     def _buildPackage(self, index=0):
56c77555
         #do not build if RPM is already built
         #test only if the package is in the testForceRPMS with rpmCheck
         #build only if the package is not in the testForceRPMS with rpmCheck
5d822c2d
         if self._checkIfPackageIsAlreadyBuilt(index):
56c77555
             if not constants.rpmCheck:
326d5ca8
                 self.logger.info("Skipping building the package:" + self.package)
56c77555
                 return
326d5ca8
             elif constants.rpmCheck and self.package not in constants.testForceRPMS:
                 self.logger.info("Skipping testing the package:" + self.package)
56c77555
                 return
 
         #should initialize a logger based on package name
326d5ca8
         containerTaskName = "build-" + self.package
56c77555
         containerID = None
         chrootID = None
         isToolChainPackage = False
326d5ca8
         if self.package in constants.listToolChainPackages:
56c77555
             isToolChainPackage = True
326d5ca8
         destLogPath = constants.logPath + "/build-" + self.package
56c77555
         try:
326d5ca8
             containerID, chrootID = self._prepareBuildContainer(
                 containerTaskName, self.package, isToolChainPackage)
56c77555
 
326d5ca8
             tcUtils = ToolChainUtils(self.logName, self.logPath)
             if self.package in constants.perPackageToolChain:
                 self.logger.debug(constants.perPackageToolChain[self.package])
87815216
                 tcUtils.installCustomToolChainRPMSinContainer(
                     containerID,
8a2c2c4e
                     constants.perPackageToolChain[self.package].get(platform.machine(), []),
326d5ca8
                     self.package)
 
4a93976f
             listDependentPackages,listDependentPackagesParseObj, listInstalledPackages, listInstalledRPMs = (
5d822c2d
                 self._findDependentPackagesAndInstalledRPM(containerID, index))
326d5ca8
 
             pkgUtils = PackageUtils(self.logName, self.logPath)
             if listDependentPackages:
                 self.logger.info("BuildContainer-buildPackage: " +
                                  "Installing dependent packages..")
                 self.logger.info(listDependentPackages)
56c77555
                 for pkg in listDependentPackages:
4a93976f
                     flag = False
                     for objName in listDependentPackagesParseObj:
                         if objName.package == pkg:
                                 properVersion=self._getProperVersion(pkg,objName)
                                 self._installPackage(pkgUtils, pkg,properVersion, containerID, destLogPath,listInstalledPackages, listInstalledRPMs)
                                 flag = True
                                 break;
                     if flag == False:
                         self._installPackage(pkgUtils, pkg,"*", containerID, destLogPath,listInstalledPackages, listInstalledRPMs)
56c77555
                 pkgUtils.installRPMSInAOneShotInContainer(containerID, destLogPath)
326d5ca8
                 self.logger.info("Finished installing the build time dependent packages....")
56c77555
 
326d5ca8
             self.logger.info("BuildContainer-buildPackage: Start building the package: " +
                              self.package)
5d822c2d
             pkgUtils.adjustGCCSpecsInContainer(self.package, containerID, destLogPath, index)
56c77555
             pkgUtils.buildRPMSForGivenPackageInContainer(
326d5ca8
                 self.package,
87815216
                 containerID,
5d822c2d
                 destLogPath,
                 index)
326d5ca8
             self.logger.info("BuildContainer-buildPackage: Successfully built the package: " +
                              self.package)
56c77555
         except Exception as e:
326d5ca8
             self.logger.error("Failed while building package:" + self.package)
56c77555
             if containerID is not None:
326d5ca8
                 self.logger.debug("Container " + containerID.short_id +
                                   " retained for debugging.")
             logFileName = os.path.join(destLogPath, self.package + ".log")
56c77555
             fileLog = os.popen('tail -n 20 ' + logFileName).read()
326d5ca8
             self.logger.debug(fileLog)
56c77555
             raise e
 
         # Remove the container
         if containerID is not None:
             containerID.remove(force=True)
         # Remove the dummy chroot
         if chrootID is not None:
326d5ca8
             chrUtils = ChrootUtils(self.logName, self.logPath)
56c77555
             chrUtils.destroyChroot(chrootID)
 
326d5ca8
 class PackageBuilderChroot(PackageBuilderBase):
     def __init__(self, mapPackageToCycles, pkgBuildType):
         PackageBuilderBase.__init__(self, mapPackageToCycles, pkgBuildType)
56c77555
 
326d5ca8
     def _prepareBuildRoot(self):
87815216
         chrootID = None
326d5ca8
         chrootName = "build-" + self.package
56c77555
         try:
326d5ca8
             chrUtils = ChrootUtils(self.logName, self.logPath)
87815216
             returnVal, chrootID = chrUtils.createChroot(chrootName)
326d5ca8
             self.logger.debug("Created new chroot: " + chrootID)
56c77555
             if not returnVal:
                 raise Exception("Unable to prepare build root")
326d5ca8
             tUtils = ToolChainUtils(self.logName, self.logPath)
             tUtils.installToolChainRPMS(chrootID, self.package, self.logPath)
56c77555
         except Exception as e:
             if chrootID is not None:
326d5ca8
                 self.logger.debug("Deleting chroot: " + chrootID)
56c77555
                 chrUtils.destroyChroot(chrootID)
             raise e
         return chrootID
 
5d822c2d
     def _buildPackage(self, index=0):
56c77555
         #do not build if RPM is already built
         #test only if the package is in the testForceRPMS with rpmCheck
         #build only if the package is not in the testForceRPMS with rpmCheck
5d822c2d
         if self._checkIfPackageIsAlreadyBuilt(index):
56c77555
             if not constants.rpmCheck:
326d5ca8
                 self.logger.info("Skipping building the package:" + self.package)
56c77555
                 return
326d5ca8
             elif constants.rpmCheck and self.package not in constants.testForceRPMS:
                 self.logger.info("Skipping testing the package:" + self.package)
56c77555
                 return
 
326d5ca8
         chrUtils = ChrootUtils(self.logName, self.logPath)
87815216
         chrootID = None
56c77555
         try:
326d5ca8
             chrootID = self._prepareBuildRoot()
4a93976f
             listDependentPackages,listDependentPackagesParseObj, listInstalledPackages, listInstalledRPMs = (
5d822c2d
                 self._findDependentPackagesAndInstalledRPM(chrootID, index))
326d5ca8
 
             pkgUtils = PackageUtils(self.logName, self.logPath)
5d822c2d
 
326d5ca8
             if listDependentPackages:
                 self.logger.info("Installing the build time dependent packages......")
56c77555
                 for pkg in listDependentPackages:
4a93976f
                     flag = False
                     for objName in listDependentPackagesParseObj:
                         if objName.package == pkg:
                                 properVersion=self._getProperVersion(pkg,objName)
                                 self._installPackage(pkgUtils, pkg,properVersion, chrootID, self.logPath,listInstalledPackages, listInstalledRPMs)
                                 flag = True
                                 break;
                     if flag == False:
                         self._installPackage(pkgUtils, pkg,"*", chrootID, self.logPath,listInstalledPackages, listInstalledRPMs)
326d5ca8
                 pkgUtils.installRPMSInAOneShot(chrootID, self.logPath)
                 self.logger.info("Finished installing the build time dependent packages....")
 
5d822c2d
             pkgUtils.adjustGCCSpecs(self.package, chrootID, self.logPath, index)
326d5ca8
             pkgUtils.buildRPMSForGivenPackage(self.package, chrootID,
5d822c2d
                                               self.logPath, index)
326d5ca8
             self.logger.info("Successfully built the package:" + self.package)
56c77555
         except Exception as e:
326d5ca8
             self.logger.error("Failed while building package:" + self.package)
             self.logger.debug("Chroot with ID: " + chrootID +
                               " not deleted for debugging.")
             logFileName = os.path.join(self.logPath, self.package + ".log")
56c77555
             fileLog = os.popen('tail -n 100 ' + logFileName).read()
326d5ca8
             self.logger.debug(fileLog)
56c77555
             raise e
         if chrootID is not None:
             chrUtils.destroyChroot(chrootID)