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
2820c61a
 
56c77555
 class PackageBuilderBase(object):
87815216
     def __init__(self, mapPackageToCycles, listAvailableCyclicPackages,
                  listBuildOptionPackages, pkgBuildOptionFile, pkgBuildType):
7f9d2e12
         # will be initialized in buildPackageThreadAPI()
87815216
         self.logName = None
         self.logPath = None
         self.logger = None
         self.package = None
2820c61a
         self.mapPackageToCycles = mapPackageToCycles
         self.listAvailableCyclicPackages = listAvailableCyclicPackages
87815216
         self.listNodepsPackages = ["glibc", "gmp", "zlib", "file", "binutils", "mpfr",
                                    "mpc", "gcc", "ncurses", "util-linux", "groff", "perl",
                                    "texinfo", "rpm", "openssl", "go"]
         self.listBuildOptionPackages = listBuildOptionPackages
         self.pkgBuildOptionFile = pkgBuildOptionFile
56c77555
         self.pkgBuildType = pkgBuildType
97a9151c
 
87815216
     def buildPackageThreadAPIPrepare(self, package, outputMap, threadName):
         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
 
87815216
     def findPackageNameFromRPMFile(self, rpmfile):
         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
 
56c77555
     def findInstalledPackages(self, instanceID):
         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:
87815216
             packageName = self.findPackageNameFromRPMFile(installedRPM)
2820c61a
             if packageName is not None:
                 listInstalledPackages.append(packageName)
56c77555
         return listInstalledPackages, listInstalledRPMs
adf248d5
 
7f9d2e12
     def checkIfPackageIsAlreadyBuilt(self):
87815216
         basePkg = SPECS.getData().getSpecName(self.package)
         listRPMPackages = SPECS.getData().getRPMPackages(basePkg)
         packageIsAlreadyBuilt = True
         pkgUtils = PackageUtils(self.logName, self.logPath)
adf248d5
         for pkg in listRPMPackages:
             if pkgUtils.findRPMFileForGivenPackage(pkg) is None:
87815216
                 packageIsAlreadyBuilt = False
adf248d5
                 break
         return packageIsAlreadyBuilt
 
87815216
     def findRunTimeRequiredRPMPackages(self, rpmPackage):
         listRequiredPackages = SPECS.getData().getRequiresForPackage(rpmPackage)
518d6a6f
         return listRequiredPackages
97a9151c
 
7f9d2e12
     def findBuildTimeRequiredPackages(self):
87815216
         listRequiredPackages = SPECS.getData().getBuildRequiresForPackage(self.package)
518d6a6f
         return listRequiredPackages
97a9151c
 
7f9d2e12
     def findBuildTimeCheckRequiredPackages(self):
87815216
         listRequiredPackages = SPECS.getData().getCheckBuildRequiresForPackage(self.package)
5f40784b
         return listRequiredPackages
 
87815216
     def installPackage(self, pkgUtils, package, instanceID, destLogPath,
                        listInstalledPackages, listInstalledRPMs):
         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)
87815216
         self.installDependentRunTimePackages(pkgUtils, package, instanceID, destLogPath,
                                              listInstalledPackages, listInstalledRPMs)
         noDeps = False
         if package in self.mapPackageToCycles:
2820c61a
             noDeps = True
         if package in self.listNodepsPackages:
87815216
             noDeps = True
a5e4be9f
         if package in constants.noDepsPackageList:
87815216
             noDeps = True
56c77555
         if self.pkgBuildType == "chroot":
87815216
             pkgUtils.installRPM(package, instanceID, noDeps, destLogPath)
56c77555
         elif self.pkgBuildType == "container":
             pkgUtils.prepRPMforInstallInContainer(package, instanceID, noDeps, destLogPath)
2820c61a
 
87815216
     def installDependentRunTimePackages(self, pkgUtils, package, instanceID, destLogPath,
                                         listInstalledPackages, listInstalledRPMs):
         listRunTimeDependentPackages = self.findRunTimeRequiredRPMPackages(package)
2820c61a
         if len(listRunTimeDependentPackages) != 0:
             for pkg in listRunTimeDependentPackages:
87815216
                 if pkg in self.mapPackageToCycles and pkg not in self.listAvailableCyclicPackages:
2820c61a
                     continue
87815216
                 latestPkgRPM = os.path.basename(
                     pkgUtils.findRPMFileForGivenPackage(pkg)).replace(".rpm", "")
56c77555
                 if pkg in listInstalledPackages and latestPkgRPM in listInstalledRPMs:
2820c61a
                     continue
87815216
                 self.installPackage(pkgUtils, pkg, instanceID, destLogPath,
                                     listInstalledPackages, listInstalledRPMs)
56c77555
 
 class PackageBuilderContainer(object):
87815216
     def __init__(self, mapPackageToCycles, listAvailableCyclicPackages, listBuildOptionPackages,
                  pkgBuildOptionFile, pkgBuildType):
56c77555
         self.buildContainerImage = "photon_build_container:latest"
         self.dockerClient = docker.from_env(version="auto")
 
         self.base = PackageBuilderBase(mapPackageToCycles, listAvailableCyclicPackages,
                                        listBuildOptionPackages, pkgBuildOptionFile, pkgBuildType)
 
     def buildPackageThreadAPI(self, package, outputMap, threadName):
         self.base.buildPackageThreadAPIPrepare(package, outputMap, threadName)
         try:
             self.buildPackage()
87815216
             outputMap[threadName] = True
56c77555
         except Exception as e:
             # TODO: self.logger might be None
b8ab7fb4
             self.base.logger.exception(e)
87815216
             outputMap[threadName] = False
56c77555
             raise e
 
87815216
     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
56c77555
         chrUtils = ChrootUtils(self.base.logName, self.base.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'},
             constants.logPath + "/" + self.base.logName: {'bind': constants.topDirPath + "/LOGS",
                                                           'mode': 'rw'},
             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:
87815216
             self.base.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")
 
             self.base.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:
87815216
             self.base.logger.debug("Unable to start Photon build container for task " +
                                    containerTaskName)
56c77555
             raise e
         return containerID, chrootID
b5e09fac
 
56c77555
     def buildPackage(self):
         #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
         if self.base.checkIfPackageIsAlreadyBuilt():
             if not constants.rpmCheck:
                 self.base.logger.info("Skipping building the package:" + self.base.package)
                 return
             elif constants.rpmCheck and self.base.package not in constants.testForceRPMS:
                 self.base.logger.info("Skipping testing the package:" + self.base.package)
                 return
 
         #should initialize a logger based on package name
         containerTaskName = "build-" + self.base.package
         containerID = None
         chrootID = None
         isToolChainPackage = False
         if self.base.package in constants.listToolChainPackages:
             isToolChainPackage = True
         destLogPath = constants.logPath + "/build-" + self.base.package
         try:
87815216
             containerID, chrootID = self.prepareBuildContainer(
                 containerTaskName, self.base.package, isToolChainPackage)
56c77555
 
             tcUtils = ToolChainUtils(self.base.logName, self.base.logPath)
             if self.base.package in constants.perPackageToolChain:
                 self.base.logger.debug(constants.perPackageToolChain[self.base.package])
87815216
                 tcUtils.installCustomToolChainRPMSinContainer(
                     containerID,
                     constants.perPackageToolChain[self.base.package],
                     self.base.package)
56c77555
 
             listInstalledPackages, listInstalledRPMs = self.base.findInstalledPackages(containerID)
             self.base.logger.info(listInstalledPackages)
             listDependentPackages = self.base.findBuildTimeRequiredPackages()
             if constants.rpmCheck and self.base.package in constants.testForceRPMS:
                 listDependentPackages.extend(self.base.findBuildTimeCheckRequiredPackages())
87815216
                 testPackages = (set(constants.listMakeCheckRPMPkgtoInstall) -
                                 set(listInstalledPackages) -
                                 set([self.base.package]))
56c77555
                 listDependentPackages.extend(testPackages)
87815216
                 listDependentPackages = list(set(listDependentPackages))
56c77555
 
             pkgUtils = PackageUtils(self.base.logName, self.base.logPath)
             if len(listDependentPackages) != 0:
87815216
                 self.base.logger.info("BuildContainer-buildPackage: " +
                                       "Installing dependent packages..")
56c77555
                 self.base.logger.info(listDependentPackages)
                 for pkg in listDependentPackages:
87815216
                     self.base.installPackage(pkgUtils, pkg, containerID, destLogPath,
                                              listInstalledPackages, listInstalledRPMs)
56c77555
                 pkgUtils.installRPMSInAOneShotInContainer(containerID, destLogPath)
87815216
                 self.base.logger.info("Finished installing the build time dependent packages....")
56c77555
 
87815216
             self.base.logger.info("BuildContainer-buildPackage: Start building the package: " +
                                   self.base.package)
56c77555
             pkgUtils.adjustGCCSpecsInContainer(self.base.package, containerID, destLogPath)
             pkgUtils.buildRPMSForGivenPackageInContainer(
87815216
                 self.base.package,
                 containerID,
                 self.base.listBuildOptionPackages,
                 self.base.pkgBuildOptionFile,
                 destLogPath)
             self.base.logger.info("BuildContainer-buildPackage: Successfully built the package: " +
                                   self.base.package)
56c77555
         except Exception as e:
             self.base.logger.error("Failed while building package:" + self.base.package)
             if containerID is not None:
87815216
                 self.base.logger.debug("Container " + containerID.short_id +
                                        " retained for debugging.")
56c77555
             logFileName = os.path.join(destLogPath, self.base.package + ".log")
             fileLog = os.popen('tail -n 20 ' + logFileName).read()
             self.base.logger.debug(fileLog)
             raise e
 
         # Remove the container
         if containerID is not None:
             containerID.remove(force=True)
         # Remove the dummy chroot
         if chrootID is not None:
             chrUtils = ChrootUtils(self.base.logName, self.base.logPath)
             chrUtils.destroyChroot(chrootID)
 
 class PackageBuilderChroot(object):
87815216
     def __init__(self, mapPackageToCycles, listAvailableCyclicPackages, listBuildOptionPackages,
                  pkgBuildOptionFile, pkgBuildType):
56c77555
         self.base = PackageBuilderBase(mapPackageToCycles, listAvailableCyclicPackages,
                                        listBuildOptionPackages, pkgBuildOptionFile, pkgBuildType)
 
     def buildPackageThreadAPI(self, package, outputMap, threadName):
87815216
         self.base.buildPackageThreadAPIPrepare(package, outputMap, threadName)
56c77555
         try:
             self.buildPackage()
87815216
             outputMap[threadName] = True
56c77555
         except Exception as e:
             # TODO: self.logger might be None
b8ab7fb4
             self.base.logger.exception(e)
87815216
             outputMap[threadName] = False
56c77555
             raise e
 
     def prepareBuildRoot(self):
87815216
         chrootID = None
         chrootName = "build-" + self.base.package
56c77555
         try:
87815216
             chrUtils = ChrootUtils(self.base.logName, self.base.logPath)
             returnVal, chrootID = chrUtils.createChroot(chrootName)
56c77555
             self.base.logger.debug("Created new chroot: " + chrootID)
             if not returnVal:
                 raise Exception("Unable to prepare build root")
87815216
             tUtils = ToolChainUtils(self.base.logName, self.base.logPath)
56c77555
             tUtils.installToolChainRPMS(chrootID, self.base.package, self.base.logPath)
         except Exception as e:
             if chrootID is not None:
                 self.base.logger.debug("Deleting chroot: " + chrootID)
                 chrUtils.destroyChroot(chrootID)
             raise e
         return chrootID
 
     def buildPackage(self):
         #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
         if self.base.checkIfPackageIsAlreadyBuilt():
             if not constants.rpmCheck:
                 self.base.logger.info("Skipping building the package:" + self.base.package)
                 return
             elif constants.rpmCheck and self.base.package not in constants.testForceRPMS:
                 self.base.logger.info("Skipping testing the package:" + self.base.package)
                 return
 
87815216
         chrUtils = ChrootUtils(self.base.logName, self.base.logPath)
         chrootID = None
56c77555
         try:
             chrootID = self.prepareBuildRoot()
             listInstalledPackages, listInstalledRPMs = self.base.findInstalledPackages(chrootID)
87815216
             listDependentPackages = self.base.findBuildTimeRequiredPackages()
56c77555
             if constants.rpmCheck and self.base.package in constants.testForceRPMS:
                 listDependentPackages.extend(self.base.findBuildTimeCheckRequiredPackages())
87815216
                 testPackages = (set(constants.listMakeCheckRPMPkgtoInstall) -
                                 set(listInstalledPackages) -
                                 set([self.base.package]))
56c77555
                 listDependentPackages.extend(testPackages)
87815216
                 listDependentPackages = list(set(listDependentPackages))
56c77555
 
87815216
             pkgUtils = PackageUtils(self.base.logName, self.base.logPath)
56c77555
             if len(listDependentPackages) != 0:
                 self.base.logger.info("Installing the build time dependent packages......")
                 for pkg in listDependentPackages:
87815216
                     self.base.installPackage(pkgUtils, pkg, chrootID, self.base.logPath,
                                              listInstalledPackages, listInstalledRPMs)
56c77555
                 pkgUtils.installRPMSInAOneShot(chrootID, self.base.logPath)
87815216
                 self.base.logger.info("Finished installing the build time dependent packages....")
56c77555
 
             pkgUtils.adjustGCCSpecs(self.base.package, chrootID, self.base.logPath)
87815216
             pkgUtils.buildRPMSForGivenPackage(self.base.package, chrootID,
                                               self.base.listBuildOptionPackages,
                                               self.base.pkgBuildOptionFile,
                                               self.base.logPath)
56c77555
             self.base.logger.info("Successfully built the package:" + self.base.package)
         except Exception as e:
             self.base.logger.error("Failed while building package:" + self.base.package)
87815216
             self.base.logger.debug("Chroot with ID: " + chrootID +
                                    " not deleted for debugging.")
56c77555
             logFileName = os.path.join(self.base.logPath, self.base.package + ".log")
             fileLog = os.popen('tail -n 100 ' + logFileName).read()
             self.base.logger.debug(fileLog)
             raise e
         if chrootID is not None:
             chrUtils.destroyChroot(chrootID)