PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/master/buildbot/scripts/base.py

https://gitlab.com/murder187ss/buildbot
Python | 260 lines | 181 code | 42 blank | 37 comment | 49 complexity | 7b54ecaba3706a837ef5fd80a76f4a69 MD5 | raw file
  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. from __future__ import print_function
  16. import copy
  17. import os
  18. import stat
  19. import sys
  20. import traceback
  21. from contextlib import contextmanager
  22. from twisted.internet import defer
  23. from twisted.python import runtime
  24. from twisted.python import usage
  25. from buildbot import config as config_module
  26. @contextmanager
  27. def captureErrors(errors, msg):
  28. try:
  29. yield
  30. except errors as e:
  31. print(msg)
  32. print(e)
  33. defer.returnValue(1)
  34. def checkBasedir(config):
  35. if not config['quiet']:
  36. print("checking basedir")
  37. if not isBuildmasterDir(config['basedir']):
  38. return False
  39. if runtime.platformType != 'win32': # no pids on win32
  40. if not config['quiet']:
  41. print("checking for running master")
  42. pidfile = os.path.join(config['basedir'], 'twistd.pid')
  43. if os.path.exists(pidfile):
  44. print("'%s' exists - is this master still running?" % (pidfile,))
  45. return False
  46. tac = getConfigFromTac(config['basedir'])
  47. if tac:
  48. if isinstance(tac.get('rotateLength', 0), str):
  49. print("ERROR: rotateLength is a string, it should be a number")
  50. print("ERROR: Please, edit your buildbot.tac file and run again")
  51. print("ERROR: See http://trac.buildbot.net/ticket/2588 for more details")
  52. return False
  53. if isinstance(tac.get('maxRotatedFiles', 0), str):
  54. print("ERROR: maxRotatedFiles is a string, it should be a number")
  55. print("ERROR: Please, edit your buildbot.tac file and run again")
  56. print("ERROR: See http://trac.buildbot.net/ticket/2588 for more details")
  57. return False
  58. return True
  59. def loadConfig(config, configFileName='master.cfg'):
  60. if not config['quiet']:
  61. print("checking %s" % configFileName)
  62. try:
  63. master_cfg = config_module.MasterConfig.loadConfig(
  64. config['basedir'], configFileName)
  65. except config_module.ConfigErrors as e:
  66. print("Errors loading configuration:")
  67. for msg in e.errors:
  68. print(" " + msg)
  69. return
  70. except Exception:
  71. print("Errors loading configuration:")
  72. traceback.print_exc(file=sys.stdout)
  73. return
  74. return master_cfg
  75. def isBuildmasterDir(dir):
  76. def print_error(error_message):
  77. print("%s\ninvalid buildmaster directory '%s'" % (error_message, dir))
  78. buildbot_tac = os.path.join(dir, "buildbot.tac")
  79. try:
  80. contents = open(buildbot_tac).read()
  81. except IOError as exception:
  82. print_error("error reading '%s': %s" %
  83. (buildbot_tac, exception.strerror))
  84. return False
  85. if "Application('buildmaster')" not in contents:
  86. print_error("unexpected content in '%s'" % buildbot_tac)
  87. return False
  88. return True
  89. def getConfigFromTac(basedir):
  90. tacFile = os.path.join(basedir, 'buildbot.tac')
  91. if os.path.exists(tacFile):
  92. # don't mess with the global namespace, but set __file__ for relocatable buildmasters
  93. tacGlobals = {'__file__': tacFile}
  94. execfile(tacFile, tacGlobals)
  95. return tacGlobals
  96. return None
  97. def getConfigFileFromTac(basedir):
  98. # execute the .tac file to see if its configfile location exists
  99. config = getConfigFromTac(basedir)
  100. if config:
  101. return config.get("configfile", "master.cfg")
  102. else:
  103. return "master.cfg"
  104. class SubcommandOptions(usage.Options):
  105. # subclasses should set this to a list-of-lists in order to source the
  106. # .buildbot/options file. Note that this *only* works with optParameters,
  107. # not optFlags. Example:
  108. # buildbotOptions = [ [ 'optfile-name', 'parameter-name' ], .. ]
  109. buildbotOptions = None
  110. # set this to options that must have non-None values
  111. requiredOptions = []
  112. def __init__(self, *args):
  113. # for options in self.buildbotOptions, optParameters, and the options
  114. # file, change the default in optParameters to the value in the options
  115. # file, call through to the constructor, and then change it back.
  116. # Options uses reflect.accumulateClassList, so this *must* be a class
  117. # attribute; however, we do not want to permanently change the class.
  118. # So we patch it temporarily and restore it after.
  119. cls = self.__class__
  120. if hasattr(cls, 'optParameters'):
  121. old_optParameters = cls.optParameters
  122. cls.optParameters = op = copy.deepcopy(cls.optParameters)
  123. if self.buildbotOptions:
  124. optfile = self.optionsFile = self.loadOptionsFile()
  125. for optfile_name, option_name in self.buildbotOptions:
  126. for i in range(len(op)):
  127. if (op[i][0] == option_name
  128. and optfile_name in optfile):
  129. op[i] = list(op[i])
  130. op[i][2] = optfile[optfile_name]
  131. usage.Options.__init__(self, *args)
  132. if hasattr(cls, 'optParameters'):
  133. cls.optParameters = old_optParameters
  134. def loadOptionsFile(self, _here=None):
  135. """Find the .buildbot/options file. Crawl from the current directory
  136. up towards the root, and also look in ~/.buildbot . The first directory
  137. that's owned by the user and has the file we're looking for wins.
  138. Windows skips the owned-by-user test.
  139. @rtype: dict
  140. @return: a dictionary of names defined in the options file. If no
  141. options file was found, return an empty dict.
  142. """
  143. here = _here or os.path.abspath(os.getcwd())
  144. if runtime.platformType == 'win32':
  145. # never trust env-vars, use the proper API
  146. from win32com.shell import shellcon, shell
  147. appdata = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, 0, 0)
  148. home = os.path.join(appdata, "buildbot")
  149. else:
  150. home = os.path.expanduser("~/.buildbot")
  151. searchpath = []
  152. toomany = 20
  153. while True:
  154. searchpath.append(os.path.join(here, ".buildbot"))
  155. next = os.path.dirname(here)
  156. if next == here:
  157. break # we've hit the root
  158. here = next
  159. toomany -= 1 # just in case
  160. if toomany == 0:
  161. print ("I seem to have wandered up into the infinite glories "
  162. "of the heavens. Oops.")
  163. break
  164. searchpath.append(home)
  165. localDict = {}
  166. for d in searchpath:
  167. if os.path.isdir(d):
  168. if runtime.platformType != 'win32':
  169. if os.stat(d)[stat.ST_UID] != os.getuid():
  170. print("skipping %s because you don't own it" % d)
  171. continue # security, skip other people's directories
  172. optfile = os.path.join(d, "options")
  173. if os.path.exists(optfile):
  174. try:
  175. with open(optfile, "r") as f:
  176. options = f.read()
  177. exec(options, localDict)
  178. except Exception:
  179. print("error while reading %s" % optfile)
  180. raise
  181. break
  182. for k in localDict.keys():
  183. if k.startswith("__"):
  184. del localDict[k]
  185. return localDict
  186. def postOptions(self):
  187. missing = [k for k in self.requiredOptions if self[k] is None]
  188. if missing:
  189. if len(missing) > 1:
  190. msg = 'Required arguments missing: ' + ', '.join(missing)
  191. else:
  192. msg = 'Required argument missing: ' + missing[0]
  193. raise usage.UsageError(msg)
  194. class BasedirMixin(object):
  195. """SubcommandOptions Mixin to handle subcommands that take a basedir
  196. argument"""
  197. # on tab completion, suggest directories as first argument
  198. if hasattr(usage, 'Completions'):
  199. # only set completion suggestion if running with
  200. # twisted version (>=11.1.0) that supports it
  201. compData = usage.Completions(
  202. extraActions=[usage.CompleteDirs(descr="buildbot base directory")])
  203. def parseArgs(self, *args):
  204. if len(args) > 0:
  205. self['basedir'] = args[0]
  206. else:
  207. # Use the current directory if no basedir was specified.
  208. self['basedir'] = os.getcwd()
  209. if len(args) > 1:
  210. raise usage.UsageError("I wasn't expecting so many arguments")
  211. def postOptions(self):
  212. # get an unambiguous, epxnaed basedir, including expanding '~', which
  213. # may be useful in a .buildbot/config file
  214. self['basedir'] = os.path.abspath(os.path.expanduser(self['basedir']))