builder.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. #
  2. # builder.py - PJSIP test scenarios builder
  3. #
  4. # Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program; if not, write to the Free Software
  18. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  19. #
  20. import ccdash
  21. import os
  22. import platform
  23. import re
  24. import subprocess
  25. import sys
  26. import time
  27. class Operation:
  28. """\
  29. The Operation class describes the individual ccdash operation to be
  30. performed.
  31. """
  32. # Types:
  33. UPDATE = "update" # Update operation
  34. CONFIGURE = "configure" # Configure operation
  35. BUILD = "build" # Build operation
  36. TEST = "test" # Unit test operation
  37. def __init__(self, type, cmdline, name="", wdir=""):
  38. self.type = type
  39. self.cmdline = cmdline
  40. self.name = name
  41. self.wdir = wdir
  42. if self.type==self.TEST and not self.name:
  43. raise "name required for tests"
  44. def encode(self, base_dir):
  45. s = [self.type]
  46. if self.type == self.TEST:
  47. s.append(self.name)
  48. if self.type != self.UPDATE:
  49. s.append(self.cmdline)
  50. s.append("-w")
  51. if self.wdir:
  52. s.append(base_dir + "/" + self.wdir)
  53. else:
  54. s.append(base_dir)
  55. return s
  56. #
  57. # Update operation
  58. #
  59. update_ops = [Operation(Operation.UPDATE, "")]
  60. #
  61. # The standard library tests (e.g. pjlib-test, pjsip-test, etc.)
  62. #
  63. std_test_ops= [
  64. Operation(Operation.TEST, "./pjlib-test$SUFFIX", name="pjlib test",
  65. wdir="pjlib/bin"),
  66. Operation(Operation.TEST, "./pjlib-util-test$SUFFIX",
  67. name="pjlib-util test", wdir="pjlib-util/bin"),
  68. Operation(Operation.TEST, "./pjnath-test$SUFFIX", name="pjnath test",
  69. wdir="pjnath/bin"),
  70. Operation(Operation.TEST, "./pjmedia-test$SUFFIX", name="pjmedia test",
  71. wdir="pjmedia/bin"),
  72. Operation(Operation.TEST, "./pjsip-test$SUFFIX", name="pjsip test",
  73. wdir="pjsip/bin")
  74. ]
  75. #
  76. # These are pjsua Python based unit test operations
  77. #
  78. def build_pjsua_test_ops(pjsua_exe=""):
  79. ops = []
  80. if pjsua_exe:
  81. exe = " -e ../../pjsip-apps/bin/" + pjsua_exe
  82. else:
  83. exe = ""
  84. cwd = os.getcwd()
  85. os.chdir("../pjsua")
  86. os.system("python runall.py --list > list")
  87. f = open("list", "r")
  88. for e in f:
  89. e = e.rstrip("\r\n ")
  90. (mod,param) = e.split(None,2)
  91. name = mod[4:mod.find(".py")] + "_" + \
  92. param[param.find("/")+1:param.find(".py")]
  93. ops.append(Operation(Operation.TEST, "python run.py" + exe + " " + \
  94. e, name=name, wdir="tests/pjsua"))
  95. f.close()
  96. os.remove("list")
  97. os.chdir(cwd)
  98. return ops
  99. #
  100. # Get gcc version
  101. #
  102. def gcc_version(gcc):
  103. proc = subprocess.Popen(gcc + " -v", stdout=subprocess.PIPE,
  104. stderr=subprocess.STDOUT, shell=True)
  105. ver = ""
  106. while True:
  107. s = proc.stdout.readline()
  108. if not s:
  109. break
  110. if s.find("gcc version") >= 0:
  111. ver = s.split(None, 3)[2]
  112. break
  113. proc.wait()
  114. return "gcc-" + ver
  115. #
  116. # Get Visual Studio version
  117. #
  118. def vs_get_version():
  119. proc = subprocess.Popen("cl", stdout=subprocess.PIPE,
  120. stderr=subprocess.STDOUT)
  121. while True:
  122. s = proc.stdout.readline()
  123. if s=="":
  124. break
  125. pos = s.find("Version")
  126. if pos > 0:
  127. proc.wait()
  128. s = s[pos+8:]
  129. ver = s.split(None, 1)[0]
  130. major = ver[0:2]
  131. if major=="12":
  132. return "vs6"
  133. elif major=="13":
  134. return "vs2003"
  135. elif major=="14":
  136. return "vs2005"
  137. elif major=="15":
  138. return "vs2008"
  139. else:
  140. return "vs-" + major
  141. proc.wait()
  142. return "vs-unknown"
  143. #
  144. # Test config
  145. #
  146. class BaseConfig:
  147. def __init__(self, base_dir, url, site, group, options=None):
  148. self.base_dir = base_dir
  149. self.url = url
  150. self.site = site
  151. self.group = group
  152. self.options = options
  153. #
  154. # Base class for test configurator
  155. #
  156. class TestBuilder:
  157. def __init__(self, config, build_config_name="",
  158. user_mak="", config_site="", exclude=[], not_exclude=[]):
  159. self.config = config # BaseConfig instance
  160. self.build_config_name = build_config_name # Optional build suffix
  161. self.user_mak = user_mak # To be put in user.mak
  162. self.config_site = config_site # To be put in config_s..
  163. self.saved_user_mak = "" # To restore user.mak
  164. self.saved_config_site = "" # To restore config_s..
  165. self.exclude = exclude # List of exclude pattern
  166. self.not_exclude = not_exclude # List of include pattern
  167. self.ccdash_args = [] # ccdash cmd line
  168. def stamp(self):
  169. return time.strftime("%Y%m%d-%H%M", time.localtime())
  170. def pre_action(self):
  171. # Override user.mak
  172. name = self.config.base_dir + "/user.mak"
  173. if os.access(name, os.F_OK):
  174. f = open(name, "r")
  175. self.saved_user_mak = f.read()
  176. f.close()
  177. if True:
  178. f = open(name, "w")
  179. f.write(self.user_mak)
  180. f.close()
  181. # Override config_site.h
  182. name = self.config.base_dir + "/pjlib/include/pj/config_site.h"
  183. if os.access(name, os.F_OK):
  184. f = open(name, "r")
  185. self.saved_config_site= f.read()
  186. f.close()
  187. if True:
  188. f = open(name, "wt")
  189. f.write(self.config_site)
  190. f.close()
  191. def post_action(self):
  192. # Restore user.mak
  193. name = self.config.base_dir + "/user.mak"
  194. f = open(name, "wt")
  195. f.write(self.saved_user_mak)
  196. f.close()
  197. # Restore config_site.h
  198. name = self.config.base_dir + "/pjlib/include/pj/config_site.h"
  199. f = open(name, "wt")
  200. f.write(self.saved_config_site)
  201. f.close()
  202. def build_tests(self):
  203. # This should be overridden by subclasses
  204. pass
  205. def execute(self):
  206. if len(self.ccdash_args)==0:
  207. self.build_tests()
  208. self.pre_action()
  209. mandatory_op = ["update", "configure", "build"]
  210. counter = 0
  211. for a in self.ccdash_args:
  212. # Check if this test is in exclusion list
  213. fullcmd = " ".join(a)
  214. excluded = False
  215. included = False
  216. for pat in self.exclude:
  217. if pat and re.search(pat, fullcmd) != None:
  218. excluded = True
  219. break
  220. if excluded:
  221. for pat in self.not_exclude:
  222. if pat and re.search(pat, fullcmd) != None:
  223. included = True
  224. break
  225. if excluded and not included:
  226. if len(fullcmd)>60:
  227. fullcmd = fullcmd[0:60] + ".."
  228. print "Skipping '%s'" % (fullcmd)
  229. continue
  230. b = ["ccdash.py"]
  231. b.extend(a)
  232. a = b
  233. #print a
  234. try:
  235. rc = ccdash.main(a)
  236. except Exception, e:
  237. errmsg = str(e)
  238. print "**** Error: ccdash got exception %s ****" % errmsg
  239. rc = -1
  240. except:
  241. print "**** Error: ccdash got unknown exception ****"
  242. rc = -1
  243. if rc!=0 and a[1] in mandatory_op:
  244. print "Stopping because of error.."
  245. break
  246. counter = counter + 1
  247. self.post_action()
  248. #
  249. # GNU test configurator
  250. #
  251. class GNUTestBuilder(TestBuilder):
  252. """\
  253. This class creates list of tests suitable for GNU targets.
  254. """
  255. def __init__(self, config, build_config_name="", user_mak="", \
  256. config_site="", cross_compile="", exclude=[], not_exclude=[]):
  257. """\
  258. Parameters:
  259. config - BaseConfig instance
  260. build_config_name - Optional name to be added as suffix to the build
  261. name. Sample: "min-size", "O4", "TLS", etc.
  262. user_mak - Contents to be put on user.mak
  263. config_site - Contents to be put on config_site.h
  264. cross_compile - Optional cross-compile prefix. Must include the
  265. trailing dash, e.g. "arm-unknown-linux-"
  266. exclude - List of regular expression patterns for tests
  267. that will be excluded from the run
  268. not_exclude - List of regular expression patterns for tests
  269. that will be run regardless of whether they
  270. match the excluded pattern.
  271. """
  272. TestBuilder.__init__(self, config, build_config_name=build_config_name,
  273. user_mak=user_mak, config_site=config_site,
  274. exclude=exclude, not_exclude=not_exclude)
  275. self.cross_compile = cross_compile
  276. if self.cross_compile and self.cross_compile[-1] != '-':
  277. self.cross_compile.append("-")
  278. def build_tests(self):
  279. if self.cross_compile:
  280. suffix = "-" + self.cross_compile[0:-1]
  281. build_name = self.cross_compile + \
  282. gcc_version(self.cross_compile + "gcc")
  283. else:
  284. proc = subprocess.Popen("sh "+self.config.base_dir+"/config.guess",
  285. shell=True, stdout=subprocess.PIPE)
  286. plat = proc.stdout.readline().rstrip(" \r\n")
  287. build_name = plat + "-"+gcc_version(self.cross_compile + "gcc")
  288. suffix = "-" + plat
  289. if self.build_config_name:
  290. build_name = build_name + "-" + self.build_config_name
  291. cmds = []
  292. cmds.extend(update_ops)
  293. cmds.append(Operation(Operation.CONFIGURE, "sh ./configure"))
  294. if sys.platform=="win32":
  295. # Don't build python module on Mingw
  296. cmds.append(Operation(Operation.BUILD,
  297. "sh -c 'make distclean && make dep && make'"))
  298. else:
  299. cmds.append(Operation(Operation.BUILD,
  300. "sh -c 'make distclean && make dep && make" + \
  301. " && cd pjsip-apps/src/python && " + \
  302. "python setup.py clean build'"))
  303. cmds.extend(std_test_ops)
  304. cmds.extend(build_pjsua_test_ops())
  305. self.ccdash_args = []
  306. for c in cmds:
  307. c.cmdline = c.cmdline.replace("$SUFFIX", suffix)
  308. args = c.encode(self.config.base_dir)
  309. args.extend(["-U", self.config.url,
  310. "-S", self.config.site,
  311. "-T", self.stamp(),
  312. "-B", build_name,
  313. "-G", self.config.group])
  314. args.extend(self.config.options)
  315. self.ccdash_args.append(args)
  316. #
  317. # MSVC test configurator
  318. #
  319. class MSVCTestBuilder(TestBuilder):
  320. """\
  321. This class creates list of tests suitable for Visual Studio builds.
  322. You need to set the MSVC environment variables (typically by calling
  323. vcvars32.bat) prior to running this class.
  324. """
  325. def __init__(self, config, target="Release|Win32", build_config_name="",
  326. config_site="", exclude=[], not_exclude=[]):
  327. """\
  328. Parameters:
  329. config - BaseConfig instance
  330. target - Visual Studio build configuration to build.
  331. Sample: "Debug|Win32", "Release|Win32".
  332. build_config_name - Optional name to be added as suffix to the build
  333. name. Sample: "Debug", "Release", "IPv6", etc.
  334. config_site - Contents to be put on config_site.h
  335. exclude - List of regular expression patterns for tests
  336. that will be excluded from the run
  337. not_exclude - List of regular expression patterns for tests
  338. that will be run regardless of whether they
  339. match the excluded pattern.
  340. """
  341. TestBuilder.__init__(self, config, build_config_name=build_config_name,
  342. config_site=config_site, exclude=exclude,
  343. not_exclude=not_exclude)
  344. self.target = target.lower()
  345. def build_tests(self):
  346. (vsbuild,sys) = self.target.split("|",2)
  347. build_name = sys + "-" + vs_get_version() + "-" + vsbuild
  348. if self.build_config_name:
  349. build_name = build_name + "-" + self.build_config_name
  350. vccmd = "vcbuild.exe /nologo /nohtmllog /nocolor /rebuild " + \
  351. "pjproject-vs8.sln " + " \"" + self.target + "\""
  352. suffix = "-i386-win32-vc8-" + vsbuild
  353. pjsua = "pjsua_vc8"
  354. if vsbuild=="debug":
  355. pjsua = pjsua + "d"
  356. cmds = []
  357. cmds.extend(update_ops)
  358. cmds.append(Operation(Operation.CONFIGURE, "CMD /C echo Nothing to do"))
  359. cmds.append(Operation(Operation.BUILD, vccmd))
  360. cmds.extend(std_test_ops)
  361. cmds.extend(build_pjsua_test_ops(pjsua))
  362. self.ccdash_args = []
  363. for c in cmds:
  364. c.cmdline = c.cmdline.replace("$SUFFIX", suffix)
  365. args = c.encode(self.config.base_dir)
  366. args.extend(["-U", self.config.url,
  367. "-S", self.config.site,
  368. "-T", self.stamp(),
  369. "-B", build_name,
  370. "-G", self.config.group])
  371. args.extend(self.config.options)
  372. self.ccdash_args.append(args)
  373. #
  374. # Symbian test configurator
  375. #
  376. class SymbianTestBuilder(TestBuilder):
  377. """\
  378. This class creates list of tests suitable for Symbian builds. You need to
  379. set the command line build settings prior to running this class (typically
  380. that involves setting the EPOCROOT variable and current device).
  381. """
  382. def __init__(self, config, target="gcce urel", build_config_name="",
  383. config_site="", exclude=[], not_exclude=[]):
  384. """\
  385. Parameters:
  386. config - BaseConfig instance
  387. target - Symbian target to build. Default is "gcce urel".
  388. build_config_name - Optional name to be added as suffix to the build
  389. name. Sample: "APS", "VAS", etc.
  390. config_site - Contents to be put on config_site.h
  391. exclude - List of regular expression patterns for tests
  392. that will be excluded from the run
  393. not_exclude - List of regular expression patterns for tests
  394. that will be run regardless of whether they
  395. match the excluded pattern.
  396. """
  397. TestBuilder.__init__(self, config, build_config_name=build_config_name,
  398. config_site=config_site, exclude=exclude,
  399. not_exclude=not_exclude)
  400. self.target = target.lower()
  401. def build_tests(self):
  402. # Check that EPOCROOT is set
  403. if not "EPOCROOT" in os.environ:
  404. print "Error: EPOCROOT environment variable is not set"
  405. sys.exit(1)
  406. epocroot = os.environ["EPOCROOT"]
  407. # EPOCROOT must have trailing backslash
  408. if epocroot[-1] != "\\":
  409. epocroot = epocroot + "\\"
  410. os.environ["EPOCROOT"] = epocroot
  411. sdk1 = epocroot.split("\\")[-2]
  412. # Check that correct device is set
  413. proc = subprocess.Popen("devices", stdout=subprocess.PIPE,
  414. stderr=subprocess.STDOUT, shell=True)
  415. sdk2 = ""
  416. while True:
  417. line = proc.stdout.readline()
  418. if line.find("- default") > 0:
  419. sdk2 = line.split(":",1)[0]
  420. break
  421. proc.wait()
  422. if sdk1 != sdk2:
  423. print "Error: default SDK in device doesn't match EPOCROOT"
  424. print "Default device SDK =", sdk2
  425. print "EPOCROOT SDK =", sdk1
  426. sys.exit(1)
  427. build_name = sdk2.replace("_", "-") + "-" + \
  428. self.target.replace(" ", "-")
  429. if self.build_config_name:
  430. build_name = build_name + "-" + self.build_config_name
  431. cmdline = "cmd /C \"cd build.symbian && bldmake bldfiles && abld build %s\"" % (self.target)
  432. cmds = []
  433. cmds.extend(update_ops)
  434. cmds.append(Operation(Operation.CONFIGURE, "CMD /C echo Nothing to do"))
  435. cmds.extend([Operation(Operation.BUILD, cmdline)])
  436. self.ccdash_args = []
  437. suffix = ""
  438. for c in cmds:
  439. c.cmdline = c.cmdline.replace("$SUFFIX", suffix)
  440. args = c.encode(self.config.base_dir)
  441. args.extend(["-U", self.config.url,
  442. "-S", self.config.site,
  443. "-T", self.stamp(),
  444. "-B", build_name,
  445. "-G", self.config.group])
  446. args.extend(self.config.options)
  447. self.ccdash_args.append(args)