1# This file is part of Buildbot. Buildbot 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# Copyright Buildbot Team Members 15 16 17from contextlib import contextmanager 18 19from twisted.python import deprecate 20from twisted.python import versions 21 22from buildbot import interfaces 23from buildbot import util 24from buildbot.process import buildstep 25from buildbot.process.build import Build 26from buildbot.steps.download_secret_to_worker import DownloadSecretsToWorker 27from buildbot.steps.download_secret_to_worker import RemoveWorkerFileSecret 28from buildbot.steps.shell import Compile 29from buildbot.steps.shell import Configure 30from buildbot.steps.shell import PerlModuleTest 31from buildbot.steps.shell import ShellCommand 32from buildbot.steps.shell import Test 33from buildbot.steps.source.cvs import CVS 34from buildbot.steps.source.svn import SVN 35 36 37# deprecated, use BuildFactory.addStep 38@deprecate.deprecated(versions.Version("buildbot", 0, 8, 6)) 39def s(steptype, **kwargs): 40 # convenience function for master.cfg files, to create step 41 # specification tuples 42 return buildstep.get_factory_from_step_or_factory(steptype(**kwargs)) 43 44 45class BuildFactory(util.ComparableMixin): 46 47 """ 48 @cvar buildClass: class to use when creating builds 49 @type buildClass: L{buildbot.process.build.Build} 50 """ 51 buildClass = Build 52 useProgress = 1 53 workdir = "build" 54 compare_attrs = ('buildClass', 'steps', 'useProgress', 'workdir') 55 56 def __init__(self, steps=None): 57 self.steps = [] 58 if steps: 59 self.addSteps(steps) 60 61 def newBuild(self, requests): 62 """Create a new Build instance. 63 64 @param requests: a list of buildrequest dictionaries describing what is 65 to be built 66 """ 67 b = self.buildClass(requests) 68 b.useProgress = self.useProgress 69 b.workdir = self.workdir 70 b.setStepFactories(self.steps) 71 return b 72 73 def addStep(self, step): 74 if not interfaces.IBuildStep.providedBy(step) and \ 75 not interfaces.IBuildStepFactory.providedBy(step): 76 raise TypeError('step must be an instance of a BuildStep') 77 self.steps.append(buildstep.get_factory_from_step_or_factory(step)) 78 79 def addSteps(self, steps, withSecrets=None): 80 if withSecrets is None: 81 withSecrets = [] 82 if withSecrets: 83 self.addStep(DownloadSecretsToWorker(withSecrets)) 84 for s in steps: 85 self.addStep(s) 86 if withSecrets: 87 self.addStep(RemoveWorkerFileSecret(withSecrets)) 88 89 @contextmanager 90 def withSecrets(self, secrets): 91 self.addStep(DownloadSecretsToWorker(secrets)) 92 yield self 93 self.addStep(RemoveWorkerFileSecret(secrets)) 94 95# BuildFactory subclasses for common build tools 96 97 98class _DefaultCommand: 99 # Used to indicate a default command to the step. 100 pass 101 102 103class GNUAutoconf(BuildFactory): 104 105 def __init__(self, source, configure="./configure", 106 configureEnv=None, 107 configureFlags=None, 108 reconf=None, 109 compile=_DefaultCommand, 110 test=_DefaultCommand, 111 distcheck=_DefaultCommand): 112 if configureEnv is None: 113 configureEnv = {} 114 if configureFlags is None: 115 configureFlags = [] 116 if compile is _DefaultCommand: 117 compile = ["make", "all"] 118 if test is _DefaultCommand: 119 test = ["make", "check"] 120 if distcheck is _DefaultCommand: 121 distcheck = ["make", "distcheck"] 122 123 super().__init__([source]) 124 125 if reconf is True: 126 reconf = ["autoreconf", "-si"] 127 if reconf is not None: 128 self.addStep( 129 ShellCommand(name="autoreconf", command=reconf, env=configureEnv)) 130 131 if configure is not None: 132 # we either need to wind up with a string (which will be 133 # space-split), or with a list of strings (which will not). The 134 # list of strings is the preferred form. 135 if isinstance(configure, str): 136 if configureFlags: 137 assert " " not in configure # please use list instead 138 command = [configure] + configureFlags 139 else: 140 command = configure 141 else: 142 assert isinstance(configure, (list, tuple)) 143 command = configure + configureFlags 144 self.addStep(Configure(command=command, env=configureEnv)) 145 if compile is not None: 146 self.addStep(Compile(command=compile, env=configureEnv)) 147 if test is not None: 148 self.addStep(Test(command=test, env=configureEnv)) 149 if distcheck is not None: 150 self.addStep(Test(command=distcheck, env=configureEnv)) 151 152 153class CPAN(BuildFactory): 154 155 def __init__(self, source, perl="perl"): 156 super().__init__([source]) 157 self.addStep(Configure(command=[perl, "Makefile.PL"])) 158 self.addStep(Compile(command=["make"])) 159 self.addStep(PerlModuleTest(command=["make", "test"])) 160 161 162class Distutils(BuildFactory): 163 164 def __init__(self, source, python="python", test=None): 165 super().__init__([source]) 166 self.addStep(Compile(command=[python, "./setup.py", "build"])) 167 if test is not None: 168 self.addStep(Test(command=test)) 169 170 171class Trial(BuildFactory): 172 173 """Build a python module that uses distutils and trial. Set 'tests' to 174 the module in which the tests can be found, or set useTestCaseNames=True 175 to always have trial figure out which tests to run (based upon which 176 files have been changed). 177 178 See docs/factories.xhtml for usage samples. Not all of the Trial 179 BuildStep options are available here, only the most commonly used ones. 180 To get complete access, you will need to create a custom 181 BuildFactory.""" 182 183 trial = "trial" 184 randomly = False 185 recurse = False 186 187 def __init__(self, source, 188 buildpython=None, trialpython=None, trial=None, 189 testpath=".", randomly=None, recurse=None, 190 tests=None, useTestCaseNames=False, env=None): 191 super().__init__([source]) 192 assert tests or useTestCaseNames, "must use one or the other" 193 if buildpython is None: 194 buildpython = ["python"] 195 if trialpython is None: 196 trialpython = [] 197 if trial is not None: 198 self.trial = trial 199 if randomly is not None: 200 self.randomly = randomly 201 if recurse is not None: 202 self.recurse = recurse 203 204 from buildbot.steps.python_twisted import Trial 205 buildcommand = buildpython + ["./setup.py", "build"] 206 self.addStep(Compile(command=buildcommand, env=env)) 207 self.addStep(Trial( 208 python=trialpython, trial=self.trial, 209 testpath=testpath, 210 tests=tests, testChanges=useTestCaseNames, 211 randomly=self.randomly, 212 recurse=self.recurse, 213 env=env, 214 )) 215 216 217# compatibility classes, will go away. Note that these only offer 218# compatibility at the constructor level: if you have subclassed these 219# factories, your subclasses are unlikely to still work correctly. 220 221ConfigurableBuildFactory = BuildFactory 222 223 224class BasicBuildFactory(GNUAutoconf): 225 # really a "GNU Autoconf-created tarball -in-CVS tree" builder 226 227 def __init__(self, cvsroot, cvsmodule, 228 configure=None, configureEnv=None, 229 compile="make all", 230 test="make check", cvsCopy=False): 231 if configureEnv is None: 232 configureEnv = {} 233 mode = "full" 234 method = "clobber" 235 if cvsCopy: 236 method = "copy" 237 source = CVS( 238 cvsroot=cvsroot, cvsmodule=cvsmodule, mode=mode, method=method) 239 super().__init__(source, 240 configure=configure, configureEnv=configureEnv, 241 compile=compile, 242 test=test) 243 244 245class QuickBuildFactory(BasicBuildFactory): 246 useProgress = False 247 248 def __init__(self, cvsroot, cvsmodule, 249 configure=None, configureEnv=None, 250 compile="make all", 251 test="make check", cvsCopy=False): 252 if configureEnv is None: 253 configureEnv = {} 254 mode = "incremental" 255 source = CVS(cvsroot=cvsroot, cvsmodule=cvsmodule, mode=mode) 256 super().__init__(source, 257 configure=configure, configureEnv=configureEnv, 258 compile=compile, 259 test=test) 260 261 262class BasicSVN(GNUAutoconf): 263 264 def __init__(self, svnurl, 265 configure=None, configureEnv=None, 266 compile="make all", 267 test="make check"): 268 if configureEnv is None: 269 configureEnv = {} 270 source = SVN(svnurl=svnurl, mode="incremental") 271 super().__init__(source, 272 configure=configure, configureEnv=configureEnv, 273 compile=compile, 274 test=test) 275