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