--- /dev/null
+# -*- coding: utf-8 -*-
+import sys, os
+import sqlite3
+import ConfigParser
+import subprocess
+import re
+import json
+from pygerrit.ssh import GerritSSHClient
+import smtplib
+import base64
+import traceback
+
+# Import the email modules we'll need
+from email.mime.text import MIMEText
+from email.mime.multipart import MIMEMultipart
+
+reload(sys)
+sys.setdefaultencoding('utf-8')
+
+def numberOfProcesses(pgrepArg):
+ numberOfPollingProcesses = "0"
+ try:
+ numberOfPollingProcesses = subprocess.check_output(["pgrep", "-c", "-f", pgrepArg])
+ except subprocess.CalledProcessError as err:
+ numberOfPollingProcesses = err.output
+ return int(numberOfPollingProcesses)
+
+
+class NativeSamplesConfig:
+ configFilePath = None
+ gerritServerName = None
+ gerritUser = None
+ gerritPort = None
+ gerritSshPrefixUrl = None
+ tizenSdkPath = None
+ sampleCoreComponentsPath = None
+ tizenBinary = None
+ tccBinary = None
+ checkpatchTizenBinary = None
+ convertToSampleProjectBinary = None
+ nativeSampleProjectList = None
+
+ emailNotificationsSmtpServerName = None
+ emailNotificationsSmtpPort = None
+ emailNotificationsSmtpUser = None
+ emailNotificationsSmtpB64EncodedPassword = None
+ emailNotificationsMailFrom = None
+ emailNotificationsMailTo = None
+ emailNotificationsMailCc = None
+ emailNotificationsMailBcc = None
+
+ def __init__(self, configFilePath = None):
+ if configFilePath is not None:
+ self.readConfigFile(configFilePath)
+ def readConfigFile(self, configFilePath):
+ if os.path.exists(configFilePath):
+ self.configFilePath = configFilePath
+ config = ConfigParser.SafeConfigParser({'GerritServerName': None,
+ 'GerritUser': None,
+ 'GerritPort': None,
+ 'TizenSdkPath': None,
+ 'SampleCoreComponentsPath': None,
+ 'SmtpServerName': None,
+ 'SmtpPort': None,
+ 'SmtpUser': None,
+ 'SmtpB64EncodedPassword': None,
+ 'MailFrom': None,
+ 'MailTo': None,
+ 'MailCc': None,
+ 'MailBcc': None})
+ config.read(configFilePath)
+ self.gerritServerName = config.get('Gerrit', 'GerritServerName')
+ self.gerritUser = config.get('Gerrit', 'GerritUser')
+ self.gerritPort = config.get('Gerrit', 'GerritPort')
+ self.tizenSdkPath = config.get('BuildTools', 'TizenSdkPath')
+ self.sampleCoreComponentsPath = config.get('BuildTools', 'SampleCoreComponentsPath')
+ #ssh://[username@]servername[:port]/
+ self.gerritSshPrefixUrl = "ssh://"
+ if self.gerritUser is not None:
+ self.gerritSshPrefixUrl += self.gerritUser + "@"
+ self.gerritSshPrefixUrl += self.gerritServerName
+ if self.gerritPort is not None:
+ self.gerritSshPrefixUrl += ":" + self.gerritPort
+ self.gerritSshPrefixUrl += "/"
+ self.nativeSampleProjectList = config.get('Gerrit', 'RepositoryList').split(",")
+ if os.path.exists(self.tizenSdkPath):
+ self.tizenBinary = os.path.join(self.tizenSdkPath, "tools/ide/bin/tizen")
+ if os.path.exists(self.sampleCoreComponentsPath):
+ self.tccBinary = os.path.join(self.sampleCoreComponentsPath, "tool/tcc.py")
+ self.convertToSampleProjectBinary = os.path.join(self.sampleCoreComponentsPath, "tool/development/convert-tpk-to-sample-project.sh")
+ self.checkpatchTizenBinary = os.path.join(self.sampleCoreComponentsPath, "tool/checkpatch_tizen.pl")
+ self.emailNotificationsSmtpServerName = config.get('EmailNotifications', 'SmtpServerName')
+ self.emailNotificationsSmtpPort = int(config.get('EmailNotifications', 'SmtpPort'))
+ self.emailNotificationsSmtpUser = config.get('EmailNotifications', 'SmtpUser')
+ self.emailNotificationsSmtpB64EncodedPassword = config.get('EmailNotifications', 'SmtpB64EncodedPassword')
+ self.emailNotificationsMailFrom = config.get('EmailNotifications', 'MailFrom')
+ self.emailNotificationsMailTo = config.get('EmailNotifications', 'MailTo').split(",")
+ self.emailNotificationsMailCc = config.get('EmailNotifications', 'MailCc').split(",")
+ self.emailNotificationsMailBcc = config.get('EmailNotifications', 'MailBcc').split(",")
+ else:
+ raise ValueError('Config file name:' + configFilePath + " does not exist")
+
+class NativeSample:
+ projectName = None
+ projectPath = None
+ repositoryUrl = None
+ def __init__(self, projectName, projectPath, repositoryUrl):
+ self.projectName = projectName
+ self.projectPath = projectPath
+ self.repositoryUrl = repositoryUrl
+ def __repr__(self):
+ return ('NativeSample(projectName: %s, ' \
+ 'projectPath: %s, '\
+ 'repositoryUrl = %s)') % (
+ self.projectName,
+ self.projectPath,
+ self.repositoryUrl)
+
+class NativeSampleChange:
+ revisionId = None
+ changeId = None
+ nativeSample = None
+ def __init__(self, nativeSample, revId, changeId = None):
+ self.nativeSample = nativeSample
+ self.revisionId = revId
+ self.changeId = changeId
+ def __repr__(self):
+ return ('NativeSampleChange(nativeSample: %s, ' \
+ 'revisionId: %s, '\
+ 'changeId = %s)') % (
+ self.nativeSample,
+ self.revisionId,
+ self.changeId)
+
+class NativeSamplesDatabase:
+ createDatabaseScript = """
+ CREATE TABLE IF NOT EXISTS changes_to_evaluate (
+ id INTEGER NOT NULL PRIMARY KEY,
+ project_name TEXT NOT NULL,
+ revision_id TEXT NOT NULL
+ );
+ """
+ insertChangeStatement = """
+ INSERT INTO changes_to_evaluate(project_name, revision_id) VALUES(:project_name, :revision_id)
+ """
+ conn = None
+ databaseFile = None
+ def __init__(self, dbfile):
+ self.databaseFile = dbfile
+ self.conn = sqlite3.connect(dbfile, isolation_level = None)
+ self.conn.execute("PRAGMA busy_timeout = 30000")
+ self.cur = self.conn.cursor()
+ self.cur.executescript(self.createDatabaseScript)
+ self.conn.commit()
+ def saveNativeSampleChange(self, nativeSampleChange):
+ self.cur.execute(self.insertChangeStatement, {'project_name':nativeSampleChange.nativeSample.projectName, 'revision_id': nativeSampleChange.revisionId})
+ def saveNativeSampleChange(self, nativeSampleChange):
+ self.cur.execute(self.insertChangeStatement, {'project_name':nativeSampleChange.nativeSample.projectName, 'revision_id': nativeSampleChange.revisionId})
+ def deleteNativeSampleChange(self, nativeSampleChange):
+ 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})
+ def getPendingNativeSampleChanges(self, nativeSamples):
+ ret = []
+ for row in self.cur.execute("SELECT id, project_name, revision_id FROM changes_to_evaluate ORDER BY id ASC"):
+ ret.append(NativeSampleChange(NativeSample(row[1], None, None), row[2]))
+ return ret
+
+class NativeSampleGerritManager:
+ client = None
+ version = None
+ def __init__(self, config):
+ self.client = GerritSSHClient(config.gerritServerName, username=config.gerritUser, port = int(config.gerritPort), keepalive=True)
+ def getVersion(self):
+ return self.client.run_gerrit_command("version").stdout.read()
+ def getChangeInfo(self, nativeSampleChange):
+ if nativeSampleChange.revisionId is None:
+ raise ValueError('Native sample revision Id cannot be None for: ' + str(nativeSampleChange))
+ jsonChangeIdStr = self.client.run_gerrit_command("query --format=JSON --current-patch-set=" + nativeSampleChange.revisionId +" project:" + nativeSampleChange.nativeSample.projectName + " limit:1").stdout.read()
+ changeInfo = json.loads(jsonChangeIdStr.split("\n")[0])
+ if 'id' in changeInfo.keys() and 'currentPatchSet' in changeInfo.keys():
+ return changeInfo
+ else:
+ return None
+ def addCommentToChange(self, nativeSampleChange, commentText):
+ commentText = commentText.replace("'", " ").replace('"', " ")
+ result = self.client.run_gerrit_command("review -m '\""+commentText+"\"' " + nativeSampleChange.revisionId)
+ print result.stdout.read(), result.stderr.read()
+
+class EmailSender:
+ smtpServer = None
+ smtpPort = None
+ smtpUser = None
+ smtpPassword = None
+ mailFrom = None
+ mailTo = None
+ mailCc = None
+ mailBcc = None
+ def __init__(self, config):
+ self.smtpServer = config.emailNotificationsSmtpServerName
+ self.smtpPort = config.emailNotificationsSmtpPort
+ self.smtpUser = config.emailNotificationsSmtpUser
+ self.smtpPassword = config.emailNotificationsSmtpB64EncodedPassword
+ self.mailFrom = config.emailNotificationsMailFrom
+ self.mailTo = config.emailNotificationsMailTo
+ self.mailCc = config.emailNotificationsMailCc
+ self.mailBcc = config.emailNotificationsMailBcc
+ def send(self, mailSubject, mailText = None, mailTo = None, mailFrom = None, mailCc = None, mailBcc = None):
+ msg = MIMEText(mailText, 'plain', 'utf-8')
+ msg['Subject'] = mailSubject
+ msg['From'] = self.mailFrom
+ msg['To'] = ",".join(mailTo or self.mailTo)
+ msg['CC'] = ",".join(mailCc or self.mailCc)
+ msg['BCC'] = ",".join(mailBcc or self.mailBcc)
+
+ s = smtplib.SMTP(host = self.smtpServer, port = self.smtpPort)
+
+ s.login(self.smtpUser, base64.b64decode(self.smtpPassword))
+ s.sendmail(msg['From'], msg['To'].split(","), msg.as_string())
+ s.quit()
+
+class NativeSamples:
+ samplesList = []
+ config = None
+ nativeSamplesRootPath = None
+ databaseModel = None
+ gerrit = None
+ emailSender = None
+ def __init__(self, rootBuildPath):
+ self.nativeSamplesRootPath = rootBuildPath
+ self.databaseModel = NativeSamplesDatabase(os.path.join(rootBuildPath, ".native-samples.db"))
+ self.config = NativeSamplesConfig(os.path.join(rootBuildPath, ".native-samples.cfg"))
+ self.samplesList = self.config.nativeSampleProjectList
+ self.gerrit = NativeSampleGerritManager(self.config)
+ self.emailSender = EmailSender(self.config)
+ def _cloneSampleFromGerrit(self, nativeSample):
+ if os.path.exists(nativeSample.projectPath):
+ raise ValueError('Path ' + projectPath + ' of project ' + projectName + ' clone already exist')
+ print "Cloning repository of ", nativeSample
+ subprocess.check_call(["git", "clone", nativeSample.repositoryUrl, nativeSample.projectPath])
+ def _getNativeSample(self, projectName):
+ return NativeSample(projectName, os.path.join(self.nativeSamplesRootPath, projectName), self.config.gerritSshPrefixUrl + projectName)
+
+ def pollForChanges(self, projectList = None):
+ if projectList is None:
+ projectList = self.samplesList
+ for nativeSampleProjectName in projectList:
+ nativeSample = self._getNativeSample(nativeSampleProjectName)
+ if not os.path.exists(nativeSample.projectPath):
+ self._cloneSampleFromGerrit(nativeSample)
+ gitCommandList = ["git", "--git-dir=" + os.path.join(nativeSample.projectPath, ".git")]
+ fetchOutput = subprocess.check_output(gitCommandList + ["fetch", "origin", "refs/changes/*:refs/remotes/origin/gerrit/*"], stderr=subprocess.STDOUT)
+ if len(fetchOutput) > 0:
+ print fetchOutput
+ changeIds = re.findall("(?<=-> ).*", fetchOutput)
+ if changeIds is None:
+ continue
+ for changeId in changeIds:
+ revisionId = subprocess.check_output(gitCommandList + [ "log", "--pretty=format:%H", "-1", changeId])
+ self.databaseModel.saveNativeSampleChange(NativeSampleChange(nativeSample, revisionId))
+
+ def buildSampleFromTpkBranch(self, nativeSampleChange, changeInfo = None):
+ print "building tpk for ", nativeSampleChange.nativeSample.projectName, " and changeId:", nativeSampleChange.changeId
+ curDir = os.getcwd()
+ try:
+ if changeInfo is None:
+ changeInfo = self.gerrit.getChangeInfo(nativeSampleChange)
+ os.chdir(nativeSampleChange.nativeSample.projectPath)
+ if changeInfo['status'] != "MERGED" and changeInfo['status'] != "ABANDONED":
+ if changeInfo['branch'] == 'tpk':
+ print 'building tpk branch'
+ if os.path.exists(os.path.join(nativeSampleChange.nativeSample.projectPath, "Build")):
+ subprocess.check_call(["rm", "-rf", "Debug"], stderr=subprocess.STDOUT)
+ try:
+ subprocess.check_call([self.config.tizenBinary, "clean"], stderr=subprocess.STDOUT)
+ output = subprocess.check_output([self.config.tizenBinary, "build-native", "-a", "arm"], stderr=subprocess.STDOUT)
+ print "SUCCESS: built change " + nativeSampleChange.changeId + " from project " + nativeSampleChange.nativeSample.projectName
+ print output
+ except subprocess.CalledProcessError as err:
+ print "FAIL: built change " + nativeSampleChange.changeId + " from project " + nativeSampleChange.nativeSample.projectName
+ print "ERROR:", err.output
+ return False, err.output
+ finally:
+ os.chdir(curDir)
+ else:
+ print 'No Tizen CLI configuration in ' + nativeSampleChange.nativeSample.projectPath
+ else:
+ print "Can't built for branch:" + changeInfo['branch']
+ else:
+ print "change already " + changeInfo['status']
+ finally:
+ os.chdir(curDir)
+ return True, ""
+
+ def checkSampleUsingTcc(self, nativeSampleChange, changeInfo = None):
+ print "checking using tcc tool for ", nativeSampleChange.nativeSample.projectName, " and changeId:", nativeSampleChange.changeId
+ curDir = os.getcwd()
+ try:
+ if changeInfo is None:
+ changeInfo = self.gerrit.getChangeInfo(nativeSampleChange)
+
+ if changeInfo['status'] != "MERGED" and changeInfo['status'] != "ABANDONED":
+ if changeInfo['branch'] == 'tpk':
+ os.chdir(nativeSampleChange.nativeSample.projectPath)
+ subprocess.check_call(["git", "checkout", nativeSampleChange.revisionId])
+ tccTemplateFile = os.path.join(nativeSampleChange.nativeSample.projectPath, "sample-project-src/.tcc_template_type")
+ if not os.path.exists(tccTemplateFile):
+ return False, "No file " + str(tccTemplateFile)
+
+ templateType = open("sample-project-src/.tcc_template_type").readline()
+ templatePath = os.path.join(self.config.sampleCoreComponentsPath, "rule/" + templateType)
+
+ subprocess.call(["git", "checkout", "."], stderr=subprocess.STDOUT)
+ subprocess.check_call(["git", "clean", "-fdx", "."], stderr=subprocess.STDOUT)
+ subprocess.check_call(["git", "checkout", "tizen_2.4"], stderr=subprocess.STDOUT)
+ subprocess.check_call([self.config.convertToSampleProjectBinary, "-v", "-r", nativeSampleChange.revisionId], stderr=subprocess.STDOUT)
+
+ tccOutput = subprocess.check_output([self.config.tccBinary, "-c", templatePath, "."], stderr=subprocess.STDOUT)
+ print tccOutput
+ result = re.search("[0-9]+(?= code sections are modified)", tccOutput)
+ if int(result.group(0)) > 0:
+ return False, result.group(0) + " code sections modified: " + tccOutput
+ result = re.search("[0-9]+(?= template files are removed)", tccOutput)
+ if int(result.group(0)) > 0:
+ return False, result.group(0) + " template files are removed: " + tccOutput
+ else:
+ print "Can't check for branch:" + changeInfo['branch'] + " only tpk"
+ else:
+ print "change already " + changeInfo['status']
+ finally:
+ subprocess.call(["git", "checkout", "."], stderr=subprocess.STDOUT)
+ subprocess.check_call(["git", "clean", "-fdx", "."], stderr=subprocess.STDOUT)
+ subprocess.check_call(["git", "checkout", nativeSampleChange.revisionId])
+ os.chdir(curDir)
+ return True, ""
+ def checkSampleUsingCheckpatchTizen(self, nativeSampleChange, changeInfo):
+ print "checking using checkpatch_tizen.pl", nativeSampleChange.nativeSample.projectName, " and changeId:", nativeSampleChange.changeId
+ curDir = os.getcwd()
+ try:
+ if changeInfo is None:
+ changeInfo = self.gerrit.getChangeInfo(nativeSampleChange)
+
+ if changeInfo['status'] != "MERGED" and changeInfo['status'] != "ABANDONED":
+ if changeInfo['branch'] == 'tpk':
+ os.chdir(nativeSampleChange.nativeSample.projectPath)
+ subprocess.check_call(["git", "checkout", "-f", nativeSampleChange.revisionId])
+
+ try:
+ filesToCheck = []
+ for root, dirs, files in os.walk(os.getcwd()):
+ for fileName in files:
+ if fileName.endswith(".c") or fileName.endswith(".h"):
+ filesToCheck.append(os.path.join(root, fileName))
+ checkPatchTizenOutput = subprocess.check_output(["perl", self.config.checkpatchTizenBinary] + filesToCheck, stderr=subprocess.STDOUT)
+ print "================================"
+ print "SUCCESS: built change " + nativeSampleChange.changeId + " from project " + nativeSampleChange.nativeSample.projectName
+ print checkPatchTizenOutput
+ print "================================"
+ except subprocess.CalledProcessError as err:
+ print "================================"
+ print "FAIL: checkpatch_tizen.pl of change " + nativeSampleChange.changeId + " from project " + nativeSampleChange.nativeSample.projectName
+ print "ERROR:", err.output
+ print "================================"
+ return False, err.output
+ finally:
+ os.chdir(curDir)
+
+ else:
+ print "Can't check for branch:" + changeInfo['branch'] + " only tpk"
+ else:
+ print "change already " + changeInfo['status']
+ finally:
+ subprocess.call(["git", "checkout", "."], stderr=subprocess.STDOUT)
+ subprocess.check_call(["git", "clean", "-fdx", "."], stderr=subprocess.STDOUT)
+ subprocess.check_call(["git", "checkout", nativeSampleChange.revisionId])
+ os.chdir(curDir)
+ return True, ""
+ def evaluateChange(self, nativeSampleChange):
+ print 'evaluating change:', nativeSampleChange
+ curDir = os.getcwd()
+ os.chdir(nativeSampleChange.nativeSample.projectPath)
+
+ subprocess.call(["git", "checkout", "."], stderr=subprocess.STDOUT)
+ subprocess.check_call(["git", "clean", "-fdx", "."], stderr=subprocess.STDOUT)
+ subprocess.check_call(["git", "checkout", nativeSampleChange.revisionId])
+
+ changeInfo = self.gerrit.getChangeInfo(nativeSampleChange)
+
+ if changeInfo is not None:
+ if changeInfo['status'] != "MERGED" and changeInfo['status'] != "ABANDONED":
+ nativeSampleChange.changeId = changeInfo['id']
+
+ result, message = self.buildSampleFromTpkBranch(nativeSampleChange, changeInfo)
+ if result:
+ self.gerrit.addCommentToChange(nativeSampleChange, "BuildBot: Compilation successful")
+ else:
+ self.gerrit.addCommentToChange(nativeSampleChange, "BuildBot: Compilation Failed:"+message)
+
+ result, message = self.checkSampleUsingTcc(nativeSampleChange, changeInfo)
+ if result:
+ self.gerrit.addCommentToChange(nativeSampleChange, "BuildBot: TCC Check successful")
+ else:
+ self.gerrit.addCommentToChange(nativeSampleChange, "BuildBot: TCC Check Failed:"+message)
+
+ result, message = self.checkSampleUsingCheckpatchTizen(nativeSampleChange, changeInfo)
+ if result:
+ self.gerrit.addCommentToChange(nativeSampleChange, "BuildBot: checkpatch_tizen successful")
+ else:
+ self.gerrit.addCommentToChange(nativeSampleChange, "BuildBot: checkpatch_tizen Failed:"+message)
+ else:
+ print "Change already " + changeInfo['status']
+ else:
+ subject = "Can't find change id for change with revision : " + nativeSampleChange.revisionId + " of project:" + nativeSampleChange.nativeSample.projectName
+ print subject
+ self.emailSender.send(mailSubject = subject)
+
+ def evaluatePendingChanges(self):
+ changesList = self.databaseModel.getPendingNativeSampleChanges(self)
+
+ for i in range(len(changesList)):
+ try:
+ changesList[i].nativeSample = self._getNativeSample(changesList[i].nativeSample.projectName)
+ self.evaluateChange(changesList[i])
+ self.databaseModel.deleteNativeSampleChange(changesList[i])
+ except:
+ subject = 'Tizen SAMPLE BUILD SYSTEM error: Something unexpected happened during build process'
+ stacktrace = "Exception Info:\n\n" + traceback.format_exc()
+ traceback.print_exc()
+ self.emailSender.send(mailSubject = subject, mailText = stacktrace)
+ print "Evaluating next change"