1# This program is free software; you can 2# redistribute it and/or modify it under the terms of the GNU General Public 3# License as published by the Free Software Foundation, version 2. 4# 5# This program is distributed in the hope that it will be useful, but WITHOUT 6# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 7# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 8# details. 9# 10# You should have received a copy of the GNU General Public License along with 11# this program; if not, write to the Free Software Foundation, Inc., 51 12# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 13# 14# Portions Copyright Buildbot Team Members 15# Portions Copyright Marius Rieder <marius.rieder@durchmesser.ch> 16""" 17Steps and objects related to pbuilder 18""" 19 20 21import re 22import stat 23import time 24 25from twisted.internet import defer 26from twisted.python import log 27 28from buildbot import config 29from buildbot.process import logobserver 30from buildbot.process import remotecommand 31from buildbot.process import results 32from buildbot.steps.shell import WarningCountingShellCommand 33 34 35class DebPbuilder(WarningCountingShellCommand): 36 37 """Build a debian package with pbuilder inside of a chroot.""" 38 name = "pbuilder" 39 40 haltOnFailure = 1 41 flunkOnFailure = 1 42 description = ["building"] 43 descriptionDone = ["built"] 44 45 warningPattern = r".*(warning[: ]|\sW: ).*" 46 47 architecture = None 48 distribution = 'stable' 49 basetgz = None 50 _default_basetgz = "/var/cache/pbuilder/{distribution}-{architecture}-buildbot.tgz" 51 mirror = "http://cdn.debian.net/debian/" 52 othermirror = "" 53 extrapackages = [] 54 keyring = None 55 components = None 56 57 maxAge = 60 * 60 * 24 * 7 58 pbuilder = '/usr/sbin/pbuilder' 59 baseOption = '--basetgz' 60 61 renderables = ['architecture', 'distribution', 'basetgz', 'mirror', 'othermirror', 62 'extrapackages', 'keyring', 'components'] 63 64 def __init__(self, 65 architecture=None, 66 distribution=None, 67 basetgz=None, 68 mirror=None, 69 othermirror=None, 70 extrapackages=None, 71 keyring=None, 72 components=None, 73 **kwargs): 74 super().__init__(**kwargs) 75 76 if architecture: 77 self.architecture = architecture 78 if distribution: 79 self.distribution = distribution 80 if mirror: 81 self.mirror = mirror 82 if othermirror: 83 self.othermirror = "|".join(othermirror) 84 if extrapackages: 85 self.extrapackages = extrapackages 86 if keyring: 87 self.keyring = keyring 88 if components: 89 self.components = components 90 if basetgz: 91 self.basetgz = basetgz 92 93 if not self.distribution: 94 config.error("You must specify a distribution.") 95 96 self.suppressions.append( 97 (None, re.compile(r"\.pbuilderrc does not exist"), None, None)) 98 99 self.addLogObserver( 100 'stdio', logobserver.LineConsumerLogObserver(self.logConsumer)) 101 102 @defer.inlineCallbacks 103 def run(self): 104 if self.basetgz is None: 105 self.basetgz = self._default_basetgz 106 kwargs = {} 107 if self.architecture: 108 kwargs['architecture'] = self.architecture 109 else: 110 kwargs['architecture'] = 'local' 111 kwargs['distribution'] = self.distribution 112 self.basetgz = self.basetgz.format(**kwargs) 113 114 self.command = ['pdebuild', '--buildresult', '.', '--pbuilder', self.pbuilder] 115 if self.architecture: 116 self.command += ['--architecture', self.architecture] 117 self.command += ['--', '--buildresult', '.', self.baseOption, self.basetgz] 118 if self.extrapackages: 119 self.command += ['--extrapackages', " ".join(self.extrapackages)] 120 121 res = yield self.checkBasetgz() 122 if res != results.SUCCESS: 123 return res 124 125 res = yield super().run() 126 return res 127 128 @defer.inlineCallbacks 129 def checkBasetgz(self): 130 cmd = remotecommand.RemoteCommand('stat', {'file': self.basetgz}) 131 yield self.runCommand(cmd) 132 133 if cmd.rc != 0: 134 log.msg("basetgz not found, initializing it.") 135 136 command = ['sudo', self.pbuilder, '--create', self.baseOption, 137 self.basetgz, '--distribution', self.distribution, 138 '--mirror', self.mirror] 139 if self.othermirror: 140 command += ['--othermirror', self.othermirror] 141 if self.architecture: 142 command += ['--architecture', self.architecture] 143 if self.extrapackages: 144 command += ['--extrapackages', " ".join(self.extrapackages)] 145 if self.keyring: 146 command += ['--debootstrapopts', "--keyring={}".format(self.keyring)] 147 if self.components: 148 command += ['--components', self.components] 149 150 cmd = remotecommand.RemoteShellCommand(self.workdir, command) 151 152 stdio_log = yield self.addLog("pbuilder") 153 cmd.useLog(stdio_log, True, "stdio") 154 155 self.description = ["PBuilder", "create."] 156 yield self.updateSummary() 157 158 yield self.runCommand(cmd) 159 if cmd.rc != 0: 160 log.msg("Failure when running {}.".format(cmd)) 161 return results.FAILURE 162 return results.SUCCESS 163 164 s = cmd.updates["stat"][-1] 165 # basetgz will be a file when running in pbuilder 166 # and a directory in case of cowbuilder 167 if stat.S_ISREG(s[stat.ST_MODE]) or stat.S_ISDIR(s[stat.ST_MODE]): 168 log.msg("{} found.".format(self.basetgz)) 169 age = time.time() - s[stat.ST_MTIME] 170 if age >= self.maxAge: 171 log.msg("basetgz outdated, updating") 172 command = ['sudo', self.pbuilder, '--update', 173 self.baseOption, self.basetgz] 174 175 cmd = remotecommand.RemoteShellCommand(self.workdir, command) 176 stdio_log = yield self.addLog("pbuilder") 177 cmd.useLog(stdio_log, True, "stdio") 178 179 yield self.runCommand(cmd) 180 if cmd.rc != 0: 181 log.msg("Failure when running {}.".format(cmd)) 182 return results.FAILURE 183 return results.SUCCESS 184 185 log.msg("{} is not a file or a directory.".format(self.basetgz)) 186 return results.FAILURE 187 188 def logConsumer(self): 189 r = re.compile(r"dpkg-genchanges >\.\./(.+\.changes)") 190 while True: 191 stream, line = yield 192 mo = r.search(line) 193 if mo: 194 self.setProperty("deb-changes", mo.group(1), "DebPbuilder") 195 196 197class DebCowbuilder(DebPbuilder): 198 199 """Build a debian package with cowbuilder inside of a chroot.""" 200 name = "cowbuilder" 201 202 _default_basetgz = "/var/cache/pbuilder/{distribution}-{architecture}-buildbot.cow/" 203 204 pbuilder = '/usr/sbin/cowbuilder' 205 baseOption = '--basepath' 206 207 208class UbuPbuilder(DebPbuilder): 209 210 """Build a Ubuntu package with pbuilder inside of a chroot.""" 211 distribution = None 212 mirror = "http://archive.ubuntu.com/ubuntu/" 213 214 components = "main universe" 215 216 217class UbuCowbuilder(DebCowbuilder): 218 219 """Build a Ubuntu package with cowbuilder inside of a chroot.""" 220 distribution = None 221 mirror = "http://archive.ubuntu.com/ubuntu/" 222 223 components = "main universe" 224