#Github pull reqest builder for Jenkins
import json
import os
import re
import urllib2
import urllib
import base64
import requests
import sys
import traceback
import platform
import subprocess
import codecs
from shutil import copy
import MySQLdb
#set Jenkins build description using submitDescription to mock browser behavior
#TODO: need to set parent build description
def set_description(desc, url):
req_data = urllib.urlencode({'description': desc})
req = urllib2.Request(url + 'submitDescription', req_data)
#print(os.environ['BUILD_URL'])
req.add_header('Content-Type', 'application/x-www-form-urlencoded')
base64string = base64.encodestring(os.environ['JENKINS_ADMIN']+ ":" + os.environ['JENKINS_ADMIN_PW']).replace('\n', '')
req.add_header("Authorization", "Basic " + base64string)
try:
urllib2.urlopen(req)
except:
traceback.print_exc()
def check_current_3rd_libs(branch):
#get current_libs config
backup_files = range(2)
current_files = range(2)
config_file_paths = ['external/config.json','templates/lua-template-runtime/runtime/config.json']
if (branch == 'v2'):
config_file_paths = ['external/config.json']
backup_files = range(1)
current_files = range(1)
for i, config_file_path in enumerate(config_file_paths):
if not os.path.isfile(config_file_path):
raise Exception("Could not find 'external/config.json'")
with open(config_file_path) as data_file:
data = json.load(data_file)
current_3rd_libs_version = data["version"]
filename = current_3rd_libs_version + '.zip'
node_name = os.environ['NODE_NAME']
backup_file = '../../../cocos-2dx-external/node/' + node_name + '/' + filename
backup_files[i] = backup_file
current_file = filename
current_files[i] = current_file
if os.path.isfile(backup_file):
copy(backup_file, current_file)
#run download-deps.py
os.system('python download-deps.py -r no')
#backup file
for i, backup_file in enumerate(backup_files):
current_file = current_files[i]
copy(current_file, backup_file)
def save_build_stats(pr_num, key, value):
db_host = os.environ['db_host']
db_user = os.environ['db_user']
db_pw = os.environ['db_pw']
db = MySQLdb.connect(db_host, db_user, db_pw, "jenkins" )
cursor = db.cursor()
sql = '''INSERT INTO PullRequestBuild (pr_number, %s)
VALUES(%d, %d) ON DUPLICATE KEY UPDATE pr_number=VALUES(pr_number),
%s=VALUES(%s)''' % (key, pr_num, value, key, key)
print sql
cursor.execute(sql)
db.commit()
db.close()
http_proxy = ''
if(os.environ.has_key('HTTP_PROXY')):
http_proxy = os.environ['HTTP_PROXY']
proxyDict = {'http':http_proxy,'https':http_proxy}
def main():
#get payload from os env
payload_str = os.environ['payload']
payload_str = payload_str.decode('utf-8','ignore')
#parse to json obj
payload = json.loads(payload_str)
#get pull number
pr_num = payload['number']
print 'pr_num:' + str(pr_num)
#build for pull request action 'open' and 'synchronize', skip 'close'
action = payload['action']
print 'action: ' + action
#pr = payload['pull_request']
url = payload['html_url']
print "url:" + url
pr_desc = '
'
#get statuses url
statuses_url = payload['statuses_url']
#get pr target branch
branch = payload['branch']
#set commit status to pending
#target_url = os.environ['BUILD_URL']
jenkins_url = os.environ['JENKINS_URL']
job_name = os.environ['JOB_NAME'].split('/')[0]
build_number = os.environ['BUILD_NUMBER']
target_url = jenkins_url + 'job/' + job_name + '/' + build_number + '/'
set_description(pr_desc, target_url)
data = {"state":"pending", "target_url":target_url, "context":"Jenkins CI", "description":"Build started..."}
access_token = os.environ['GITHUB_ACCESS_TOKEN']
Headers = {"Authorization":"token " + access_token}
try:
requests.post(statuses_url, data=json.dumps(data), headers=Headers, proxies = proxyDict)
except:
traceback.print_exc()
#reset path to workspace root
os.system("cd " + os.environ['WORKSPACE']);
#pull latest code
os.system("git pull origin v3")
os.system("git checkout v3")
os.system("git branch -D pull" + str(pr_num))
#clean workspace
print "Before checkout: git clean -xdf -f"
os.system("git clean -xdf -f")
#fetch pull request to local repo
git_fetch_pr = "git fetch origin pull/" + str(pr_num) + "/head"
ret = os.system(git_fetch_pr)
if(ret != 0):
return(2)
#checkout a new branch from v3
git_checkout = "git checkout -b " + "pull" + str(pr_num)
os.system(git_checkout)
#merge pull reqeust head
p = os.popen('git merge --no-edit FETCH_HEAD')
r = p.read()
#check if merge fail
if r.find('CONFLICT') > 0:
print r
return(3)
# After checkout a new branch, clean workspace again
print "After checkout: git clean -xdf -f"
os.system("git clean -xdf -f")
#update submodule
git_update_submodule = "git submodule update --init --force"
ret = os.system(git_update_submodule)
if(ret != 0):
return(2)
#copy check_current_3rd_libs
check_current_3rd_libs(branch)
# Generate binding glue codes
if(branch == 'v3'):
ret = os.system("python tools/jenkins-scripts/gen_jsb.py")
elif(branch == 'v2'):
os.chdir('tools/tojs')
if(platform.system() == 'Windows'):
os.environ['NDK_ROOT'] = os.environ['NDK_ROOT_R8E']
ret = os.system("genbindings-win32.bat")
os.environ['NDK_ROOT'] = os.environ['NDK_ROOT_R9B']
else:
ret = os.system("./genbindings.sh")
os.chdir('../..')
if(ret != 0):
return(1)
#make temp dir
print "current dir is: " + os.environ['WORKSPACE']
os.system("cd " + os.environ['WORKSPACE']);
os.mkdir("android_build_objs")
#add symbol link
PROJECTS=["cpp-empty-test", "cpp-tests"]
print platform.system()
if(platform.system() == 'Darwin'):
for item in PROJECTS:
cmd = "ln -s " + os.environ['WORKSPACE']+"/android_build_objs/ " + os.environ['WORKSPACE']+"/tests/"+item+"/proj.android/obj"
os.system(cmd)
elif(platform.system() == 'Windows'):
for item in PROJECTS:
p = item.replace("/", os.sep)
cmd = "mklink /J "+os.environ['WORKSPACE']+os.sep+"tests"+os.sep +p+os.sep+"proj.android"+os.sep+"obj " + os.environ['WORKSPACE']+os.sep+"android_build_objs"
print cmd
os.system(cmd)
#build
#TODO: add android-linux build
#TODO: add mac build
node_name = os.environ['NODE_NAME']
if(branch == 'v3'):
if(node_name == 'android_mac') or (node_name == 'android_win7'):
#modify tests/cpp-empty-test/Classes/AppDelegate.cpp to support Console
modify_file = 'tests/cpp-empty-test/Classes/AppDelegate.cpp'
data = codecs.open(modify_file, encoding='UTF-8').read()
data = re.sub("director->setDisplayStats\(true\);", "director->setDisplayStats(true); director->getConsole()->listenOnTCP(5678);", data)
codecs.open(modify_file, 'wb', encoding='UTF-8').write(data)
#modify tests/cpp-empty-test/proj.android/AndroidManifest.xml to support Console
modify_file = 'tests/cpp-empty-test/proj.android/AndroidManifest.xml'
data = codecs.open(modify_file, encoding='UTF-8').read()
data = re.sub('', ' ', data)
codecs.open(modify_file, 'wb', encoding='UTF-8').write(data)
print "Start build android..."
ret = os.system("python build/android-build.py -p 10 all")
# create and save apk
if(ret == 0):
sample_dir = 'tests/cpp-tests/proj.android/'
local_apk = sample_dir + 'bin/CppTests-debug.apk'
backup_apk = os.environ['BACKUP_PATH'] + 'CppTests_' + str(pr_num) + '.apk'
os.system('cp ' + local_apk + ' ' + backup_apk)
ret = os.system("python build/android-build.py -p 10 -b release cpp-empty-test")
if(ret == 0):
_path = 'tests/cpp-empty-test/proj.android/libs/armeabi/libcpp_empty_test.so'
filesize = os.path.getsize(_path)
pr_desc = pr_desc + 'size of libcpp_empty_test.so is:' + str(filesize/1024) + 'kb
'
set_description(pr_desc, target_url)
save_build_stats(pr_num, 'cpp_empty_test_so', filesize/1024)
elif(node_name == 'win32_win7'):
ret = subprocess.call('"%VS110COMNTOOLS%..\IDE\devenv.com" "build\cocos2d-win32.vc2012.sln" /Build "Debug|Win32"', shell=True)
elif(node_name == 'ios_mac'):
ret = os.system("tools/jenkins-scripts/ios-build.sh")
elif(node_name == 'linux_centos'):
os.chdir("build/")
ret = os.system("cmake ../")
ret = os.system("make -j10")
os.chdir("../")
elif(branch == 'v2'):
SAMPLES_DIRS = ['Cpp/HelloCpp', 'Cpp/SimpleGame', 'Cpp/TestCpp', 'Javascript/TestJavascript', 'Lua/HelloLua', 'Lua/TestLua']
SAMPLES_NAMES = ['HelloCpp', 'SimpleGame', 'TestCpp', 'TestJavascript', 'HelloLua', 'TestLua']
if(node_name == 'android_mac'):
for item in SAMPLES_DIRS:
proj_dir = "samples/" + item + "/proj.android"
os.system('ln -s ../../../../android_build_objs obj')
os.system(proj_dir + "/build_native.sh")
if (ret != 0):
break
elif(node_name == 'win32_win7'):
ret = subprocess.call('"%VS110COMNTOOLS%..\IDE\devenv.com" "cocos2d-win32.vc2012.sln" /Build "Debug|Win32"', shell=True)
elif(node_name == 'ios_mac'):
for i, item in enumerate(SAMPLES_DIRS):
cmd = "xcodebuild -project samples/" + item + "/proj.ios/" + SAMPLES_NAMES[i] + ".xcodeproj -scheme " + SAMPLES_NAMES[i] + ' -destination "platform=iOS Simulator,name=iPhone Retina (4-inch)"'
cmd_clean = cmd + ' clean'
cmd_build = cmd + ' build'
ret = os.system(cmd_clean)
if(ret != 0):
break
ret = os.system(cmd_build)
if(ret != 0):
break
elif(node_name == 'linux_centos'):
data = codecs.open('cocos2dx/proj.linux/cocos2dx.mk', encoding='UTF-8').read()
data = re.sub('-lglfw','-L$(GLFW_279_LIB) -lglfw', data)
codecs.open('cocos2dx/proj.linux/cocos2dx.mk', 'wb', encoding='UTF-8').write(data)
ret = os.system('make -j10')
else:
ret = 0
#get build result
print "build finished and return " + str(ret)
exit_code = 1
if ret == 0:
exit_code = 0
else:
exit_code = 1
#clean workspace
os.system("cd " + os.environ['WORKSPACE'])
os.system("git reset --hard")
os.system("git clean -xdf -f")
os.system("git checkout v3")
os.system("git branch -D pull" + str(pr_num))
return(exit_code)
# -------------- main --------------
if __name__ == '__main__':
sys_ret = 0
try:
sys_ret = main()
except:
traceback.print_exc()
sys_ret = 1
finally:
sys.exit(sys_ret)