1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

# This file is part of Buildbot.  Buildbot is free software: you can 

# redistribute it and/or modify it under the terms of the GNU General Public 

# License as published by the Free Software Foundation, version 2. 

# 

# This program is distributed in the hope that it will be useful, but WITHOUT 

# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 

# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 

# details. 

# 

# You should have received a copy of the GNU General Public License along with 

# this program; if not, write to the Free Software Foundation, Inc., 51 

# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 

# 

# Copyright Buildbot Team Members 

 

import os, types, re 

from twisted.python import runtime 

from twisted.internet import reactor 

from buildbot.process.buildstep import BuildStep 

from buildbot.process.buildstep import SUCCESS, FAILURE 

from twisted.internet import error 

from twisted.internet.protocol import ProcessProtocol 

 

class MasterShellCommand(BuildStep): 

    """ 

    Run a shell command locally - on the buildmaster.  The shell command 

    COMMAND is specified just as for a RemoteShellCommand.  Note that extra 

    logfiles are not supported. 

    """ 

    name='MasterShellCommand' 

    description='Running' 

    descriptionDone='Ran' 

    descriptionSuffix = None 

    renderables = [ 'command', 'env', 'description', 'descriptionDone', 'descriptionSuffix' ] 

    haltOnFailure = True 

    flunkOnFailure = True 

 

    def __init__(self, command, 

                 description=None, descriptionDone=None, descriptionSuffix=None, 

                 env=None, path=None, usePTY=0, interruptSignal="KILL", 

                 **kwargs): 

        BuildStep.__init__(self, **kwargs) 

 

        self.command=command 

        if description: 

            self.description = description 

        if isinstance(self.description, str): 

            self.description = [self.description] 

        if descriptionDone: 

            self.descriptionDone = descriptionDone 

        if isinstance(self.descriptionDone, str): 

            self.descriptionDone = [self.descriptionDone] 

        if descriptionSuffix: 

            self.descriptionSuffix = descriptionSuffix 

        if isinstance(self.descriptionSuffix, str): 

            self.descriptionSuffix = [self.descriptionSuffix] 

        self.env=env 

        self.path=path 

        self.usePTY=usePTY 

        self.interruptSignal = interruptSignal 

 

    class LocalPP(ProcessProtocol): 

        def __init__(self, step): 

            self.step = step 

 

        def outReceived(self, data): 

            self.step.stdio_log.addStdout(data) 

 

        def errReceived(self, data): 

            self.step.stdio_log.addStderr(data) 

 

        def processEnded(self, status_object): 

            if status_object.value.exitCode is not None: 

                self.step.stdio_log.addHeader("exit status %d\n" % status_object.value.exitCode) 

            if status_object.value.signal is not None: 

                self.step.stdio_log.addHeader("signal %s\n" % status_object.value.signal) 

            self.step.processEnded(status_object) 

 

    def start(self): 

        # render properties 

        command = self.command 

        # set up argv 

        if type(command) in types.StringTypes: 

            if runtime.platformType  == 'win32': 

                argv = os.environ['COMSPEC'].split() # allow %COMSPEC% to have args 

                if '/c' not in argv: argv += ['/c'] 

                argv += [command] 

            else: 

                # for posix, use /bin/sh. for other non-posix, well, doesn't 

                # hurt to try 

                argv = ['/bin/sh', '-c', command] 

        else: 

            if runtime.platformType  == 'win32': 

                argv = os.environ['COMSPEC'].split() # allow %COMSPEC% to have args 

                if '/c' not in argv: argv += ['/c'] 

                argv += list(command) 

            else: 

                argv = command 

 

        self.stdio_log = stdio_log = self.addLog("stdio") 

 

        if type(command) in types.StringTypes: 

            stdio_log.addHeader(command.strip() + "\n\n") 

        else: 

            stdio_log.addHeader(" ".join(command) + "\n\n") 

        stdio_log.addHeader("** RUNNING ON BUILDMASTER **\n") 

        stdio_log.addHeader(" in dir %s\n" % os.getcwd()) 

        stdio_log.addHeader(" argv: %s\n" % (argv,)) 

        self.step_status.setText(self.describe()) 

 

        if self.env is None: 

            env = os.environ 

        else: 

            assert isinstance(self.env, dict) 

            env = self.env 

 

            # do substitution on variable values matching pattern: ${name} 

            p = re.compile('\${([0-9a-zA-Z_]*)}') 

            def subst(match): 

                return os.environ.get(match.group(1), "") 

            newenv = {} 

            for key in env.keys(): 

                if env[key] is not None: 

                    newenv[key] = p.sub(subst, env[key]) 

            env = newenv 

        stdio_log.addHeader(" env: %r\n" % (env,)) 

 

        # TODO add a timeout? 

        self.process = reactor.spawnProcess(self.LocalPP(self), argv[0], argv, 

                path=self.path, usePTY=self.usePTY, env=env ) 

        # (the LocalPP object will call processEnded for us) 

 

    def processEnded(self, status_object): 

        if status_object.value.signal is not None: 

            self.descriptionDone = ["killed (%s)" % status_object.value.signal] 

            self.step_status.setText(self.describe(done=True)) 

            self.finished(FAILURE) 

        elif status_object.value.exitCode != 0: 

            self.descriptionDone = ["failed (%d)" % status_object.value.exitCode] 

            self.step_status.setText(self.describe(done=True)) 

            self.finished(FAILURE) 

        else: 

            self.step_status.setText(self.describe(done=True)) 

            self.finished(SUCCESS) 

 

    def describe(self, done=False): 

        desc = self.descriptionDone if done else self.description 

        if self.descriptionSuffix: 

            desc = desc[:] 

            desc.extend(self.descriptionSuffix) 

        return desc 

 

    def interrupt(self, reason): 

        try: 

            self.process.signalProcess(self.interruptSignal) 

        except KeyError: # Process not started yet 

            pass 

        except error.ProcessExitedAlready: 

            pass 

        BuildStep.interrupt(self, reason)