1 # -*- coding: utf-8 -*-
8 from pygerrit.ssh import GerritSSHClient
14 import xml.etree.ElementTree as ET
18 # Import the email modules we'll need
19 from email.mime.text import MIMEText
20 from email.mime.multipart import MIMEMultipart
23 sys.setdefaultencoding('utf-8')
25 def numberOfProcesses(pgrepArg):
26 numberOfPollingProcesses = "0"
28 numberOfPollingProcesses = subprocess.check_output(["pgrep", "-c", "-f", pgrepArg])
29 except subprocess.CalledProcessError as err:
30 numberOfPollingProcesses = err.output
31 return int(numberOfPollingProcesses)
34 class NativeSamplesConfig:
36 gerritServerName = None
39 gerritSshPrefixUrl = None
41 sampleCoreComponentsPath = None
44 checkpatchTizenBinary = None
45 convertToSampleProjectBinary = None
46 nativeSampleProjectList = None
48 emailNotificationsSmtpServerName = None
49 emailNotificationsSmtpPort = None
50 emailNotificationsSmtpUser = None
51 emailNotificationsSmtpB64EncodedPassword = None
52 emailNotificationsMailFrom = None
53 emailNotificationsMailTo = None
54 emailNotificationsMailCc = None
55 emailNotificationsMailBcc = None
57 def __init__(self, configFilePath = None):
58 if configFilePath is not None:
59 self.readConfigFile(configFilePath)
60 def readConfigFile(self, configFilePath):
61 if os.path.exists(configFilePath):
62 self.configFilePath = configFilePath
63 config = ConfigParser.SafeConfigParser({'GerritServerName': None,
67 'SampleCoreComponentsPath': None,
68 'SmtpServerName': None,
71 'SmtpB64EncodedPassword': None,
76 config.read(configFilePath)
77 self.gerritServerName = config.get('Gerrit', 'GerritServerName')
78 self.gerritUser = config.get('Gerrit', 'GerritUser')
79 self.gerritPort = config.get('Gerrit', 'GerritPort')
80 self.tizenSdkPath = config.get('BuildTools', 'TizenSdkPath')
81 self.sampleCoreComponentsPath = config.get('BuildTools', 'SampleCoreComponentsPath')
82 #ssh://[username@]servername[:port]/
83 self.gerritSshPrefixUrl = "ssh://"
84 if self.gerritUser is not None:
85 self.gerritSshPrefixUrl += self.gerritUser + "@"
86 self.gerritSshPrefixUrl += self.gerritServerName
87 if self.gerritPort is not None:
88 self.gerritSshPrefixUrl += ":" + self.gerritPort
89 self.gerritSshPrefixUrl += "/"
90 self.nativeSampleProjectList = config.get('Gerrit', 'RepositoryList').split(",")
91 for i, prj in enumerate(self.nativeSampleProjectList):
92 self.nativeSampleProjectList[i] = prj.strip("\n\t")
93 if os.path.exists(self.tizenSdkPath):
94 self.tizenBinary = os.path.join(self.tizenSdkPath, "tools/ide/bin/tizen")
95 if os.path.exists(self.sampleCoreComponentsPath):
96 self.tccBinary = os.path.join(self.sampleCoreComponentsPath, "tool/tcc.py")
97 self.convertToSampleProjectBinary = os.path.join(self.sampleCoreComponentsPath, "tool/development/convert-tpk-to-sample-project.sh")
98 self.checkpatchTizenBinary = os.path.join(self.sampleCoreComponentsPath, "tool/checkpatch_tizen.pl")
99 self.emailNotificationsSmtpServerName = config.get('EmailNotifications', 'SmtpServerName')
100 self.emailNotificationsSmtpPort = int(config.get('EmailNotifications', 'SmtpPort'))
101 self.emailNotificationsSmtpUser = config.get('EmailNotifications', 'SmtpUser')
102 self.emailNotificationsSmtpB64EncodedPassword = config.get('EmailNotifications', 'SmtpB64EncodedPassword')
103 self.emailNotificationsMailFrom = config.get('EmailNotifications', 'MailFrom')
104 self.emailNotificationsMailTo = config.get('EmailNotifications', 'MailTo').split(",")
105 self.emailNotificationsMailCc = config.get('EmailNotifications', 'MailCc').split(",")
106 self.emailNotificationsMailBcc = config.get('EmailNotifications', 'MailBcc').split(",")
108 raise ValueError('Config file name:' + configFilePath + " does not exist")
114 def __init__(self, projectName, projectPath, repositoryUrl):
115 self.projectName = projectName
116 self.projectPath = projectPath
117 self.repositoryUrl = repositoryUrl
119 return ('NativeSample(projectName: %s, ' \
121 'repositoryUrl = %s)') % (
126 class NativeSampleChange:
130 def __init__(self, nativeSample, revId, changeId = None):
131 self.nativeSample = nativeSample
132 self.revisionId = revId
133 self.changeId = changeId
135 return ('NativeSampleChange(nativeSample: %s, ' \
137 'changeId = %s)') % (
142 class NativeSamplesDatabase:
143 createDatabaseScript = """
144 CREATE TABLE IF NOT EXISTS changes_to_evaluate (
145 id INTEGER NOT NULL PRIMARY KEY,
146 project_name TEXT NOT NULL,
147 revision_id TEXT NOT NULL
150 insertChangeStatement = """
151 INSERT INTO changes_to_evaluate(project_name, revision_id) VALUES(:project_name, :revision_id)
155 def __init__(self, dbfile):
156 self.databaseFile = dbfile
157 self.conn = sqlite3.connect(dbfile, isolation_level = None)
158 self.conn.execute("PRAGMA busy_timeout = 30000")
159 self.cur = self.conn.cursor()
160 self.cur.executescript(self.createDatabaseScript)
162 def saveNativeSampleChange(self, nativeSampleChange):
163 self.cur.execute(self.insertChangeStatement, {'project_name':nativeSampleChange.nativeSample.projectName, 'revision_id': nativeSampleChange.revisionId})
164 def saveNativeSampleChange(self, nativeSampleChange):
165 self.cur.execute(self.insertChangeStatement, {'project_name':nativeSampleChange.nativeSample.projectName, 'revision_id': nativeSampleChange.revisionId})
166 def deleteNativeSampleChange(self, nativeSampleChange):
167 self.cur.execute("DELETE FROM changes_to_evaluate WHERE project_name = :project_name AND revision_id = :revision_id", {'project_name':nativeSampleChange.nativeSample.projectName, 'revision_id': nativeSampleChange.revisionId})
168 def getPendingNativeSampleChanges(self, nativeSamples):
170 for row in self.cur.execute("SELECT id, project_name, revision_id FROM changes_to_evaluate ORDER BY id ASC"):
171 ret.append(NativeSampleChange(NativeSample(row[1], None, None), row[2]))
174 class NativeSampleGerritManager:
177 def __init__(self, config):
178 self.client = GerritSSHClient(config.gerritServerName, username=config.gerritUser, port = int(config.gerritPort), keepalive=True)
179 def getVersion(self):
180 return self.client.run_gerrit_command("version").stdout.read()
181 def getChangeInfo(self, nativeSampleChange):
182 if nativeSampleChange.revisionId is None:
183 raise ValueError('Native sample revision Id cannot be None for: ' + str(nativeSampleChange))
184 jsonChangeIdStr = self.client.run_gerrit_command("query --format=JSON --current-patch-set=" + nativeSampleChange.revisionId +" project:" + nativeSampleChange.nativeSample.projectName + " limit:1").stdout.read()
185 changeInfo = json.loads(jsonChangeIdStr.split("\n")[0])
186 if 'id' in changeInfo.keys() and 'currentPatchSet' in changeInfo.keys():
190 def addCommentToChange(self, nativeSampleChange, commentText):
191 command = subprocess.list2cmdline(["review", "-m", commentText[:20000].replace("\n", "\n "), nativeSampleChange.revisionId])
192 result = self.client.run_gerrit_command(command)
193 print result.stdout.read(), result.stderr.read()
204 def __init__(self, config):
205 self.smtpServer = config.emailNotificationsSmtpServerName
206 self.smtpPort = config.emailNotificationsSmtpPort
207 self.smtpUser = config.emailNotificationsSmtpUser
208 self.smtpPassword = config.emailNotificationsSmtpB64EncodedPassword
209 self.mailFrom = config.emailNotificationsMailFrom
210 self.mailTo = config.emailNotificationsMailTo
211 self.mailCc = config.emailNotificationsMailCc
212 self.mailBcc = config.emailNotificationsMailBcc
213 def send(self, mailSubject, mailText = None, mailTo = None, mailFrom = None, mailCc = None, mailBcc = None, mimeType = None):
214 msg = MIMEText(mailText, mimeType or 'plain', 'utf-8')
215 msg['Subject'] = mailSubject
216 msg['From'] = self.mailFrom
217 msg['To'] = ",".join(mailTo or self.mailTo)
218 msg['CC'] = ",".join(mailCc or self.mailCc)
219 msg['BCC'] = ",".join(mailBcc or self.mailBcc)
221 s = smtplib.SMTP(host = self.smtpServer, port = self.smtpPort)
223 s.login(self.smtpUser, base64.b64decode(self.smtpPassword))
224 s.sendmail(msg['From'], msg['To'].split(","), msg.as_string())
228 #enum like source type
229 SourceTypeSampleProject = 0
230 SourceTypeTpkProject = 1
231 SampleTemplateTypeMobile = 0
232 SampleTemplateTypeWearable = 1
233 SampleTemplateTypeWatchface = 2
234 SampleTemplateTypeService = 3
237 nativeSamplesRootPath = None
241 def __init__(self, rootBuildPath):
242 self.nativeSamplesRootPath = rootBuildPath
243 self.databaseModel = NativeSamplesDatabase(os.path.join(rootBuildPath, ".native-samples.db"))
244 self.config = NativeSamplesConfig(os.path.join(rootBuildPath, ".native-samples.cfg"))
245 self.samplesList = self.config.nativeSampleProjectList
246 self.gerrit = NativeSampleGerritManager(self.config)
247 self.emailSender = EmailSender(self.config)
248 def _cloneSampleFromGerrit(self, nativeSample):
249 if os.path.exists(nativeSample.projectPath):
250 raise ValueError('Path ' + projectPath + ' of project ' + projectName + ' clone already exist')
251 print "Cloning repository of ", nativeSample
252 subprocess.check_call(["git", "clone", nativeSample.repositoryUrl, nativeSample.projectPath])
253 def _getNativeSample(self, projectName):
254 return NativeSample(projectName, os.path.join(self.nativeSamplesRootPath, projectName), self.config.gerritSshPrefixUrl + projectName)
256 def pollForChanges(self, projectList = None):
257 if projectList is None:
258 projectList = self.samplesList
259 for nativeSampleProjectName in projectList:
260 nativeSample = self._getNativeSample(nativeSampleProjectName)
261 if not os.path.exists(nativeSample.projectPath):
262 self._cloneSampleFromGerrit(nativeSample)
263 gitCommandList = ["git", "--git-dir=" + os.path.join(nativeSample.projectPath, ".git")]
266 fetchOutput = subprocess.check_output(gitCommandList + ["fetch", "origin", "refs/changes/*:refs/remotes/origin/gerrit/*"], stderr=subprocess.STDOUT)
267 except subprocess.CalledProcessError as error:
268 if error.returncode == 128:
269 print 'network or server error - trying to fetch next project'
272 if len(fetchOutput) > 0:
274 changeIds = re.findall("(?<=-> ).*", fetchOutput)
275 if changeIds is None:
277 for changeId in changeIds:
278 revisionId = subprocess.check_output(gitCommandList + [ "log", "--pretty=format:%H", "-1", changeId])
279 self.databaseModel.saveNativeSampleChange(NativeSampleChange(nativeSample, revisionId))
280 def _cleanGitRepo(self, repoDir):
284 subprocess.call(["git", "checkout", "-q", "."], stderr=subprocess.STDOUT)
285 subprocess.check_call(["git", "clean", "-fdxq", "."], stderr=subprocess.STDOUT)
288 def _cleanCurrentGitRepo(self):
289 self._cleanGitRepo(os.getcwd())
290 def _cleanRepoAndCheckoutToRevision(self, sampleChange = None, repoPath = None, revision = None):
293 os.chdir(repoPath or sampleChange.nativeSample.projectPath or curDir)
294 self._cleanCurrentGitRepo()
295 subprocess.check_call(["git", "checkout", "-qf", revision or sampleChange.revisionId])
298 def _sourceType(self, sampleDirectoryPath = None):
301 if sampleDirectoryPath is not None:
302 os.chdir(sampleDirectoryPath)
304 #if sample contains sample.xml then we assume that it is sample project type
305 if os.path.exists("sample.xml"):
306 return self.SourceTypeSampleProject
307 if os.path.exists("tizen-manifest.xml"):
308 return self.SourceTypeTpkProject
309 raise ValueError("Can't determine source project type in path:" + os.getcwd())
312 def _currentSourceType(self):
313 return self._sourceType(os.getcwd())
314 def _directoryContainsFileWithString(self, directory, stringToFind):
316 subprocess.check_call(["grep", "-qr", stringToFind, directory])
318 except subprocess.CalledProcessError:
321 def _sampleTemplateType(self, sampleDirectoryPath = None):
322 if sampleDirectoryPath is None:
323 sampleDirectoryPath = os.getcwd()
325 #if it is service type then profile doesn't matter
326 if self._directoryContainsFileWithString(sampleDirectoryPath, "service_app_lifecycle_callback_s"):
327 ret = self.SampleTemplateTypeService
328 elif self._directoryContainsFileWithString(sampleDirectoryPath, "watch_app_lifecycle_callback_s"):
329 ret = self.SampleTemplateTypeWatchface
331 sourceType = self._sourceType()
332 tizenXmlFilePath = None
333 descriptionXmlFilePath = None
334 if sourceType == self.SourceTypeSampleProject:
335 descriptionXmlFilePath = os.path.join(os.getcwd(), "description.xml")
336 elif sourceType == self.SourceTypeTpkProject:
337 if not os.path.exists(os.path.join(os.getcwd(), "sample-project-src/description.xml")):
338 tizenXmlFilePath = os.path.join(os.getcwd(), "tizen-manifest.xml")
340 descriptionXmlFilePath = os.path.join(os.getcwd(), "sample-project-src/description.xml")
341 profileType = "undefined"
342 if descriptionXmlFilePath is not None:
343 if not os.path.exists(descriptionXmlFilePath):
344 raise ValueError("Can't find description.xml file in %s. Can't determine sample template type." % descriptionXmlFilePath)
345 xmlTree = ET.parse(descriptionXmlFilePath)
346 xmlRoot = xmlTree.getroot()
347 xmlnsPrefix = xmlRoot.tag.replace("Overview", "")
348 profileType = xmlRoot.iter(xmlnsPrefix + 'ProfileName').next().text.lower()
349 if tizenXmlFilePath is not None:
350 if not os.path.exists(tizenXmlFilePath):
351 raise ValueError("Can't find tizen-manifest.xml file in %s. Can't determine sample template type." % tizenXmlFilePath)
352 xmlTree = ET.parse(tizenXmlFilePath)
353 xmlRoot = xmlTree.getroot()
354 xmlnsPrefix = xmlRoot.tag.replace("manifest", "")
355 profileType = xmlRoot.find(xmlnsPrefix + 'profile').attrib["name"]
357 if profileType == "wearable":
358 ret = self.SampleTemplateTypeWearable
359 elif profileType == "mobile":
360 ret = self.SampleTemplateTypeMobile
362 raise ValueError("Can't determine sample template type")
366 subprocess.check_call(["rm", "-rf", "Debug"], stderr=subprocess.STDOUT)
368 output = subprocess.check_output([self.config.tizenBinary, "build-native", "-a", "arm"], stderr=subprocess.STDOUT)
369 #sometimes build-native returns 0 exit status if linker error occured
370 return not "clang++: error" in output, output
371 except subprocess.CalledProcessError as error:
372 return False, traceback.format_exc() + "\n" + error.output
374 def buildSampleFromTpkBranch(self, nativeSampleChange, changeInfo = None):
375 print "========> TPK_BUILD for ", nativeSampleChange.nativeSample.projectName, " and changeId:", nativeSampleChange.changeId
378 if changeInfo is None:
379 changeInfo = self.gerrit.getChangeInfo(nativeSampleChange)
380 os.chdir(nativeSampleChange.nativeSample.projectPath)
381 if os.path.exists(os.path.join(nativeSampleChange.nativeSample.projectPath, "Build")):
382 result, output = self.buildTpk()
385 print "SUCCESS: built change " + nativeSampleChange.changeId + " from project " + nativeSampleChange.nativeSample.projectName
387 print "ERROR:", output
388 print "FAIL: built change " + nativeSampleChange.changeId + " from project " + nativeSampleChange.nativeSample.projectName
391 print 'No Tizen CLI configuration in ' + nativeSampleChange.nativeSample.projectPath
395 def buildTpkFromProject(self, nativeSample, uniquePostfix):
397 tmpProjectPath = None
398 destSamplesPathDir = None
400 os.chdir(nativeSample.projectPath)
401 projectProfileWithVersion = None
402 projectDefFile = os.path.join(nativeSample.projectPath,'project/project_def.prop')
403 if not os.path.exists(projectDefFile):
404 return False, "No Tizen CLI project file:" + projectDefFile
405 f = open(projectDefFile)
407 res = re.search("\s*profile\s*=\s*", line)
409 projectProfileWithVersion = re.sub("\s*profile\s*=\s*", "", line).strip("\n\t ")
411 tizenVersion = "tizen" + re.search("-[0-9].[0-9](.[0-9])?", projectProfileWithVersion).group(0)
412 profileName = re.search(".*(?=-)", projectProfileWithVersion).group(0)
413 tmpProjectPath = tempfile.mkdtemp()
414 tmpProjectName = "SampleProjectToBuild"
415 projectBasename = os.path.basename(nativeSample.projectPath)
416 samplesUniqueProjectName = projectBasename + uniquePostfix
417 projectPrefixInSamplesDir = "buildbot"
418 destSamplesPathDir = os.path.join(self.config.tizenSdkPath, "platforms", tizenVersion, profileName, "samples/Template/Native", projectPrefixInSamplesDir)
419 if not os.path.exists(destSamplesPathDir):
420 os.makedirs(destSamplesPathDir)
421 destSamplesPathDir = os.path.join(destSamplesPathDir, samplesUniqueProjectName)
422 print "Copying sample dir to " + destSamplesPathDir
423 subprocess.check_call(["cp", "-r", nativeSample.projectPath, destSamplesPathDir])
424 print "removing " + destSamplesPathDir + "/.git"
425 shutil.rmtree(os.path.join(destSamplesPathDir, ".git"), ignore_errors=True)
426 createProjectArgs = [self.config.tizenBinary, "create", "native-project", "-n", tmpProjectName, "-p", projectProfileWithVersion, "-t", samplesUniqueProjectName, "--", tmpProjectPath]
427 print "Creating project from sample template: " + " ".join(createProjectArgs)
428 subprocess.check_call(createProjectArgs)
429 os.chdir(os.path.join(tmpProjectPath, tmpProjectName))
430 print "building project using command line"
431 return self.buildTpk()
432 except subprocess.CalledProcessError as error:
433 return False, traceback.format_exc() + "\n" + error.output
435 if tmpProjectPath is not None:
436 shutil.rmtree(tmpProjectPath, ignore_errors=True)
437 if destSamplesPathDir is not None:
438 shutil.rmtree(destSamplesPathDir, ignore_errors=True)
440 def buildSampleFromProjectBranch(self, nativeSampleChange, changeInfo = None):
441 print "========> SAMPLE_PROJECT_BUILD for ", nativeSampleChange.nativeSample.projectName, " and changeId:", nativeSampleChange.changeId
444 tmpProjectPath = None
445 destSamplesPathDir = None
446 if changeInfo is None:
447 changeInfo = self.gerrit.getChangeInfo(nativeSampleChange)
448 if os.path.exists(os.path.join(nativeSampleChange.nativeSample.projectPath, "project/Build")):
449 print "building project using command line"
450 result, output = self.buildTpkFromProject(nativeSampleChange.nativeSample, nativeSampleChange.changeId)
453 print "SUCCESS: built change " + nativeSampleChange.changeId + " from project " + nativeSampleChange.nativeSample.projectName
455 print "ERROR:", output
456 print "FAIL: built change " + nativeSampleChange.changeId + " from project " + nativeSampleChange.nativeSample.projectName
459 print 'No Tizen CLI configuration in ' + nativeSampleChange.nativeSample.projectPath
461 if tmpProjectPath is not None:
462 shutil.rmtree(tmpProjectPath, ignore_errors=True)
463 if destSamplesPathDir is not None:
464 shutil.rmtree(destSamplesPathDir, ignore_errors=True)
467 def invokeConvert(self, revision, outputDirectory = None):
468 if outputDirectory is None:
469 outputDirectory = os.getcwd()
471 subprocess.check_call([self.config.convertToSampleProjectBinary, "-v", "-r", revision, "-o", outputDirectory], stderr=subprocess.STDOUT)
472 except subprocess.CalledProcessError as error:
473 return False, traceback.format_exc() + "\n" + error.output
476 templateType = self._sampleTemplateType()
477 templatePostfix = "undefined"
478 if templateType == self.SampleTemplateTypeMobile:
479 templatePostfix = "mobile"
480 elif templateType == self.SampleTemplateTypeWatchface:
481 templatePostfix = "watchface"
482 elif templateType == self.SampleTemplateTypeWearable:
483 templatePostfix = "wearable"
484 elif templateType == self.SampleTemplateTypeService:
485 templatePostfix = "service"
486 templatePath = os.path.join(self.config.sampleCoreComponentsPath, "rule/" + templatePostfix)
488 tccOutput = subprocess.check_output([self.config.tccBinary, "-c", templatePath, "."], stderr=subprocess.STDOUT)
489 result = re.search("[0-9]+(?= code sections are modified)", tccOutput)
490 if int(result.group(0)) > 0:
491 return False, tccOutput
492 result = re.search("[0-9]+(?= template files are removed)", tccOutput)
493 if int(result.group(0)) > 0:
494 return False, tccOutput
495 except subprocess.CalledProcessError as error:
496 return False, traceback.format_exc() + "\n" + error.output
498 return True, tccOutput
501 def checkSampleUsingTcc(self, nativeSampleChange, changeInfo = None):
502 print "========> TCC for ", nativeSampleChange.nativeSample.projectName, " and changeId:", nativeSampleChange.changeId
505 if changeInfo is None:
506 changeInfo = self.gerrit.getChangeInfo(nativeSampleChange)
508 os.chdir(nativeSampleChange.nativeSample.projectPath)
509 self._cleanRepoAndCheckoutToRevision(sampleChange = nativeSampleChange)
510 sourceType = self._currentSourceType()
511 if sourceType == self.SourceTypeTpkProject:
512 tempTccOutputDir = tempfile.mkdtemp()
513 self.invokeConvert(revision = nativeSampleChange.revisionId, outputDirectory = tempTccOutputDir)
514 os.chdir(tempTccOutputDir)
515 elif sourceType != self.SourceTypeSampleProject:
516 raise ValueError("Wrong source type to use tcc tool")
517 result, tccOutput = self.invokeTcc()
520 print 'FAIL: tcc for ', nativeSampleChange.nativeSample.projectName, ' and changeId:', nativeSampleChange.changeId
521 return False, tccOutput
523 self._cleanRepoAndCheckoutToRevision(sampleChange = nativeSampleChange)
525 print 'SUCCCESS: tcc for ', nativeSampleChange.nativeSample.projectName, ' and changeId:', nativeSampleChange.changeId
527 def invokeCheckpatchTizen(self):
530 for root, dirs, files in os.walk(os.getcwd()):
531 for fileName in files:
532 if fileName.endswith(".c") or fileName.endswith(".h"):
533 filesToCheck.append(os.path.join(root, fileName))
534 checkPatchTizenOutput = subprocess.check_output(["perl", self.config.checkpatchTizenBinary] + filesToCheck, stderr=subprocess.STDOUT)
535 return True, checkPatchTizenOutput
536 except subprocess.CalledProcessError as err:
537 return False, traceback.format_exc() + "\noutput:" + err.output
538 def invokeCloc(self):
540 sourceType = self._currentSourceType()
542 if sourceType == self.SourceTypeTpkProject:
543 additionalArgs.append("--exclude-dir=Build")
544 elif sourceType == self.SourceTypeSampleProject:
545 additionalArgs.append("--exclude-dir=project/Build")
547 output = subprocess.check_output(["cloc", "--exclude-ext=py", "--csv", "--quiet", "."] + additionalArgs)
548 languagesOutputList = []
549 for i, line in enumerate(output.split("\n")):
550 if i in [0, 1] or not line:
552 #we ommit first column which is rownum column
553 languagesOutputList.append(line.split(","))
554 return True, output, languagesOutputList
555 except subprocess.CalledProcessError as err:
556 return False, traceback.format_exc() + "\noutput:" + err.output
557 def invokeAspell(self, ignoredWordsList = None):
559 def __init__(self, wordStr = None, hintsStr = None, positionInLine = None, lineNumber = None, lineStr = None, wordInfo = None):
561 self.wordStr = wordStr
562 self.hintsStr = hintsStr
563 self.positionInLine = positionInLine
564 self.lineNumber = lineNumber
565 self.lineStr = lineStr
567 self.wordStr = wordInfo.wordStr
568 self.hintsStr = wordInfo.hintsStr
569 self.positionInLine = wordInfo.positionInLine
570 self.lineNumber = wordInfo.lineNumber
571 self.lineStr = wordInfo.lineStr
572 def __eq__(self, other):
573 return (isinstance(other, self.__class__) and self.__dict__ == other.__dict__)
575 def __ne__(self, other):
576 return not self.__eq__(other)
577 def getDataFromAspellOutput(aspellOutput):
578 outputLines = filter(None, aspellOutput.split("\n"))
581 longestOutputLine = 0
582 if outputLines is not None and len(outputLines) > 0:
583 quotedStringOrComment = pyparsing.cppStyleComment | pyparsing.QuotedString(quoteChar = '"', escChar='\\', multiline = True).ignore(pyparsing.cppStyleComment)
584 for outputLine in outputLines:
585 wordParts = re.search("[\w']+ [0-9]+ [0-9]+", outputLine).group(0).split(" ")
586 tmpWord = WordInfo(wordStr = wordParts[0], hintsStr = outputLine.split(":")[1], positionInLine = int(wordParts[2]))
587 wordsInfo.append(tmpWord)
588 longestOutputLine = len(max(wordsInfo, key=lambda wordInfo: len(wordInfo.wordStr)).wordStr)
589 for i in range(len(wordsInfo)):
590 wordInfoTmp = WordInfo(wordInfo = wordsInfo[i])
591 regExStr = "(?<=^.{"+str(wordInfoTmp.positionInLine)+"})"+"((\\b{0}\\b)|(\\b{0}_+)|(\\b{0}[0-9]+))".format(wordInfoTmp.wordStr)
592 wordRegexToken = pyparsing.Regex(regExStr, flags = re.MULTILINE)
593 for r, start, end in quotedStringOrComment.parseWithTabs().scanString(fileContent):
594 for res, s, e in wordRegexToken.scanString(r[0]):
595 newWord = WordInfo(wordInfo = wordInfoTmp)
596 newWord.lineNumber = fileContent[:start +s].count("\n")
597 newWord.lineStr = lines[newWord.lineNumber]
598 if not any(x == newWord for x in foundWords):
599 foundWords.append(newWord)
600 #let's sort by line numbers now
601 foundWords.sort(key=lambda wordInfo: wordInfo.lineNumber)
602 return foundWords, longestOutputLine
605 aspellIgnoreDictPath = ""
606 aspellIgnoreDictPathArg = ""
607 sourceType = self._currentSourceType()
608 dictTemporaryFile = None
609 aspellDictionaryObject = None
610 if sourceType == self.SourceTypeTpkProject:
611 aspellIgnoreDictPath = os.path.join(os.getcwd(), "sample-project-src/aspell.ignore.json")
612 elif sourceType == self.SourceTypeSampleProject:
613 aspellIgnoreDictPath = os.path.join(os.getcwd(), "aspell.ignore.json")
614 if os.path.exists(aspellIgnoreDictPath):
615 jsonFile = open(aspellIgnoreDictPath)
616 aspellDictionaryObject = json.load(jsonFile)
618 dictTemporaryFile = tempfile.NamedTemporaryFile()
619 dictTemporaryFile.write("personal_ws-1.1 en 0\n")
620 for dictItem in aspellDictionaryObject:
621 dictTemporaryFile.write(dictItem["word"] +"\n")
622 dictTemporaryFile.flush()
623 aspellIgnoreDictPathArg = " --personal=" + dictTemporaryFile.name + " "
624 for root, dirs, files in os.walk(os.getcwd()):
625 for fileName in files:
626 if fileName.endswith(".c") or fileName.endswith(".h"):
627 fullFilePath = os.path.join(root, fileName)
628 relativeFilePath = os.path.relpath(fullFilePath)
629 file = open(fullFilePath)
630 lines = file.readlines()
631 fileContent = "".join(lines)
634 for i, line in enumerate(lines):
635 if line.startswith("*"):
636 return False, "Please fix line number:" + str(i+1) + " in file:" + relativeFilePath + ". It shouldn't start with '*' because it breaks aspell"
637 aspell = subprocess.Popen("bash -c 'set -o pipefail; aspell pipe list --encoding=utf-8 --mode=ccpp --run-together --lang=en_US " + aspellIgnoreDictPathArg + "< " +
638 fullFilePath + " | grep \"\w\+ [0-9]\+ [0-9]\+:\" || true'", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
639 stdoutOutput,stderrOutput = aspell.communicate()
641 if aspell.returncode != 0 or stderrOutput:
642 return False, "Error from aspell (error code:" + str(aspell.returncode) + "):\n" + stderrOutput
644 foundWords, longestOutputLine = getDataFromAspellOutput(stdoutOutput)
645 if len(foundWords) > 0:
646 aspellSummary += "\nASPELL CHECK FILE: " + relativeFilePath + "\n"
648 aspellSummary += "Something appeared on stderr: " + stderrOutput + "\n"
649 for wordInfo in foundWords:
650 lineWithWordUppercased = (wordInfo.lineStr[:wordInfo.positionInLine] + wordInfo.lineStr[wordInfo.positionInLine:wordInfo.positionInLine+len(wordInfo.wordStr)].upper() + wordInfo.lineStr[wordInfo.positionInLine + len(wordInfo.wordStr):]).strip()
651 aspellSummary += (str(wordInfo.lineNumber + 1).rjust(5, " ") + ": " + wordInfo.wordStr.ljust(longestOutputLine + 1, " ") + str(wordInfo.positionInLine) + "-> " + lineWithWordUppercased +
652 "\n" + "(".rjust(5 + 2 + longestOutputLine + 1 + 4, " ") + wordInfo.hintsStr + ")" + "\n")
653 if ignoredWordsList is not None and aspellDictionaryObject is not None:
654 aspellWithoutDict = subprocess.Popen("bash -c 'set -o pipefail; aspell pipe list --encoding=utf-8 --mode=ccpp --run-together --lang=en_US < " + fullFilePath + " | grep \"\w\+ [0-9]\+ [0-9]\+:\" || true'", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
655 stdoutOutput,stderrOutput = aspellWithoutDict.communicate()
656 if aspellWithoutDict.returncode != 0 or stderrOutput:
657 return False, "Error from aspell (error code:" + str(aspellWithoutDict.returncode) + "):\n" + stderrOutput
658 foundWords, longestOutputLine = getDataFromAspellOutput(stdoutOutput)
659 for wordInfo in foundWords:
660 for item in aspellDictionaryObject:
661 if item["word"] == wordInfo.wordStr:
662 ignoredWordsList.append([relativeFilePath, wordInfo.wordStr, wordInfo.lineNumber, item["type"]])
665 return len(aspellSummary) == 0, aspellSummary
666 except subprocess.CalledProcessError as err:
667 return False, traceback.format_exc() + "\noutput:" + err.output
669 def checkSampleUsingCheckpatchTizen(self, nativeSampleChange, changeInfo):
670 print "========> CHECKPATCH_TIZEN for ", nativeSampleChange.nativeSample.projectName, " and changeId:", nativeSampleChange.changeId
673 if changeInfo is None:
674 changeInfo = self.gerrit.getChangeInfo(nativeSampleChange)
676 os.chdir(nativeSampleChange.nativeSample.projectPath)
677 self._cleanRepoAndCheckoutToRevision(sampleChange = nativeSampleChange)
678 res, output = self.invokeCheckpatchTizen()
681 print "SUCCESS: sources checked with checkpatch_tizen.pl for " + nativeSampleChange.changeId + " from project " + nativeSampleChange.nativeSample.projectName
683 print "ERROR:", output
684 print "FAIL: sources checked failed with checkpatch_tizen.pl for " + nativeSampleChange.changeId + " from project " + nativeSampleChange.nativeSample.projectName
689 def checkSampleUsingAspell(self, nativeSampleChange, changeInfo):
690 print "========> ASPELL_CHECK for ", nativeSampleChange.nativeSample.projectName, " and changeId:", nativeSampleChange.changeId
693 if changeInfo is None:
694 changeInfo = self.gerrit.getChangeInfo(nativeSampleChange)
695 os.chdir(nativeSampleChange.nativeSample.projectPath)
696 self._cleanRepoAndCheckoutToRevision(sampleChange = nativeSampleChange)
697 res, output = self.invokeAspell()
700 print "SUCCESS: sources checked with aspell for " + nativeSampleChange.changeId + " from project " + nativeSampleChange.nativeSample.projectName
702 print "ERROR:", output
703 print "FAIL: sources checked failed with aspell for " + nativeSampleChange.changeId + " from project " + nativeSampleChange.nativeSample.projectName
708 def evaluateChange(self, nativeSampleChange):
709 print 'evaluating change:', nativeSampleChange
711 os.chdir(nativeSampleChange.nativeSample.projectPath)
713 self._cleanRepoAndCheckoutToRevision(sampleChange = nativeSampleChange)
714 changeInfo = self.gerrit.getChangeInfo(nativeSampleChange)
716 if changeInfo is not None:
717 if changeInfo['status'] != "MERGED" and changeInfo['status'] != "ABANDONED":
718 nativeSampleChange.changeId = changeInfo['id']
720 if self._currentSourceType() == self.SourceTypeTpkProject:
721 result, message = self.buildSampleFromTpkBranch(nativeSampleChange, changeInfo)
723 self.gerrit.addCommentToChange(nativeSampleChange, "BuildBot: Compilation successful")
725 self.gerrit.addCommentToChange(nativeSampleChange, "BuildBot: Compilation failed:\n"+message)
726 elif self._currentSourceType() == self.SourceTypeSampleProject:
727 result, message = self.buildSampleFromProjectBranch(nativeSampleChange, changeInfo)
729 self.gerrit.addCommentToChange(nativeSampleChange, "BuildBot: Project creation and compilation successful")
731 self.gerrit.addCommentToChange(nativeSampleChange, "BuildBot: Project creation and/or compilation failed:\n"+message)
733 result, message = self.checkSampleUsingTcc(nativeSampleChange, changeInfo)
735 self.gerrit.addCommentToChange(nativeSampleChange, "BuildBot: TCC Check successful")
737 self.gerrit.addCommentToChange(nativeSampleChange, "BuildBot: TCC Check failed:\n"+message)
739 result, message = self.checkSampleUsingCheckpatchTizen(nativeSampleChange, changeInfo)
741 self.gerrit.addCommentToChange(nativeSampleChange, "BuildBot: checkpatch_tizen successful")
743 self.gerrit.addCommentToChange(nativeSampleChange, "BuildBot: checkpatch_tizen failed:\n"+message)
745 result, message = self.checkSampleUsingAspell(nativeSampleChange, changeInfo)
747 self.gerrit.addCommentToChange(nativeSampleChange, "BuildBot: spelling check successful")
749 self.gerrit.addCommentToChange(nativeSampleChange, "BuildBot: fix spelling check errors or add words to ignore list:\n"+message)
752 print "Change already " + changeInfo['status']
754 subject = "Can't find change id for change with revision : " + nativeSampleChange.revisionId + " of project:" + nativeSampleChange.nativeSample.projectName
756 self.emailSender.send(mailSubject = subject)
759 def evaluateProject(self, nativeSample):
760 print "evaluating project", nativeSample.projectName
762 def convertResult(resultList):
763 ret = {'result': False, 'comment': None}
764 ret['result'] = resultList[0]
767 ret['comment'] = "OK"
769 ret['comment'] = resultList[1]
772 os.chdir(nativeSample.projectPath)
773 subprocess.call(["git", "fetch", "origin"], stderr=subprocess.STDOUT)
776 'SAMPLE_PROJECT_BUILD': {'result': False, 'comment': ""},
777 'TPK_BUILD': {'result': False, 'comment': ""},
778 'TCC_CHECK': {'result': False, 'comment': ""},
779 'CHECKPATCH_TIZEN': {'result': False, 'comment': ""},
780 'ASPELL_CHECK': {'result': False, 'comment': ""},
781 'CLOC_CHECK': {'result': False, 'comment': ""}
784 self._cleanRepoAndCheckoutToRevision(repoPath = nativeSample.projectPath, revision="origin/tizen_2.4")
785 #now we are on tizen_2.4 branch
786 print '=======> SAMPLE_PROJECT_BUILD for ', nativeSample.projectName
787 ret["SAMPLE_PROJECT_BUILD"] = convertResult(self.buildTpkFromProject(nativeSample, "daily_build_origin_tizen_2.4"))
789 print '=======> invoking TCC_CHECK ', nativeSample.projectName
790 ret["TCC_CHECK"] = convertResult(self.invokeTcc())
792 print '=======> CHECKPATCH_TIZEN ', nativeSample.projectName
793 ret["CHECKPATCH_TIZEN"] = convertResult(self.invokeCheckpatchTizen())
795 print '=======> ASPELL_CHECK ', nativeSample.projectName
796 ignoredWordsList = []
797 ret["ASPELL_CHECK"] = convertResult(self.invokeAspell(ignoredWordsList))
798 ignoredWordsCsvStr = ""
799 for ignoredWord in ignoredWordsList:
800 dirname, fileName = os.path.split(ignoredWord[0])
801 ignoredWordsCsvStr += ",".join([dirname, fileName, ignoredWord[1], str(ignoredWord[2]), ignoredWord[3], ignoredWord[1]]) + "\n"
802 ret["ASPELL_CHECK"]["comment"] = ret["ASPELL_CHECK"]["comment"] + "\nINGORED_LIST_CSV:\n" + ignoredWordsCsvStr
804 self._cleanRepoAndCheckoutToRevision(repoPath = nativeSample.projectPath, revision="origin/tpk")
805 #now we are on tpk branch - let's get first change
806 print '=======> COMMENT_RATIO for ', nativeSample.projectName
807 resultList = list(self.invokeCloc())
808 languagesOutputList = resultList[2]
810 commentLinesCount = 0
812 for languagesOutput in languagesOutputList:
813 commentLinesCount += float(languagesOutput[3])
814 codeLinesCount += float(languagesOutput[4])
815 tmp = languagesOutput[0]
816 languagesOutput[0] = languagesOutput[1]
817 languagesOutput[1] = tmp
818 csvOutput.append(",".join(languagesOutput))
819 commentRatio = round(commentLinesCount*100/(commentLinesCount + codeLinesCount), 2)
820 if commentRatio > 30:
821 ret["CLOC_CHECK"]["result"] = True
822 ret["CLOC_CHECK"]["comment"] = "\n" + "COMMENT_RATIO: " + str(commentRatio) + "%\nCLOC_OUTPUT_CSV:\n" + "\n".join(csvOutput)
824 ret["CLOC_CHECK"]["result"] = False
825 ret["CLOC_CHECK"]["comment"] = "\n" + "COMMENT_RATIO: " + str(commentRatio) + "%\n" + resultList[1]
826 print ret["CLOC_CHECK"]["comment"]
827 print '=======> TPK_BUILD for ', nativeSample.projectName
828 ret["TPK_BUILD"] = convertResult(self.buildTpk())
832 def evaluatePendingChanges(self):
833 changesList = self.databaseModel.getPendingNativeSampleChanges(self)
835 for i in range(len(changesList)):
837 changesList[i].nativeSample = self._getNativeSample(changesList[i].nativeSample.projectName)
838 self.evaluateChange(changesList[i])
839 self.databaseModel.deleteNativeSampleChange(changesList[i])
840 except KeyboardInterrupt:
843 subject = 'Tizen SAMPLE BUILD SYSTEM error: Something unexpected happened during build process'
844 stacktrace = "Exception Info:\n\n" + traceback.format_exc()
845 traceback.print_exc()
846 self.emailSender.send(mailSubject = subject, mailText = stacktrace)
847 print "Evaluating next change"
848 def dailyRegressionCheck(self):
849 htmlText = "<HTML><TABLE border=\"1\" style=\"width:80%\"><TR><TH>Project Name</TH><TH>Check step</TH><TH>Result</TH><TH>Comment</TH></TR>"
851 return s.replace("&", "&").replace("<", "<").replace(">", ">")
852 for sampleProjectName in self.samplesList:
854 nativeSample = self._getNativeSample(sampleProjectName)
855 if not os.path.exists(nativeSample.projectPath):
856 self._cloneSampleFromGerrit(nativeSample)
857 res = self.evaluateProject(nativeSample)
858 spanText = "<TD rowspan=\"" + str(len(res.keys())) + "\">%s</TD>" % sampleProjectName
860 for i, checkStep in enumerate(res.keys()):
864 htmlText += ("<TD>%s</TD><TD>%i</TD><TD><PRE>%s</PRE></TD></TR>") % (checkStep, res[checkStep]['result'], escapeHtml(res[checkStep]['comment']))
865 except KeyboardInterrupt:
868 subject = 'Tizen DAILY REGRESSION BUILD SYSTEM error: Something unexpected happened during daily build process'
869 stacktrace = "Exception Info:\n\n" + traceback.format_exc()
870 traceback.print_exc()
871 self.emailSender.send(mailSubject = subject, mailText = stacktrace)
872 print "Evaluating next project"
873 htmlText += "</TABLE></HTML>"
874 self.emailSender.send(mailSubject = 'DAILY REGRESSION TESTS SUMMARY (' + str(datetime.date.today())+")", mailText = htmlText, mimeType='html')
875 def checkAndSendEmailIfUpdateIsAvailable(self):
876 os.chdir(self.config.sampleCoreComponentsPath)
877 subprocess.call(["git", "fetch", "origin"])
878 output = subprocess.check_output(["git", "branch", "--contains", "origin/master"], stderr = subprocess.STDOUT)
879 reRes = re.search("^\*", output)
881 self.emailSender.send(mailSubject = "BUILDBOT UPDATE AVAILABLE", mailText = "Seems like update for sample-core-components is available. origin/master is not pulled to local repository")