351 lines
10 KiB
Python
351 lines
10 KiB
Python
#!/usr/bin/python2
|
|
import os
|
|
import sys
|
|
import time
|
|
import errno
|
|
from dulwich.repo import Repo
|
|
from dulwich.diff_tree import tree_changes
|
|
import shutil
|
|
import subprocess
|
|
|
|
SOURCE_FOLDER = "source"
|
|
SOURCE_REPO = "git@github.com:BlindMindStudios/StarRuler2.git"
|
|
DEST_FOLDER = "dest"
|
|
DEST_REPO = "git@github.com:BlindMindStudios/SR2Beta.git"
|
|
LOG_FOLDER = "log"
|
|
|
|
CURRENT_COMMIT = ""
|
|
Source = None
|
|
Dest = None
|
|
|
|
SOURCE_DIRS = ["source/game/", "source/as_addons/",
|
|
"source/os/", "source/sound/", "source/util/",
|
|
"source/network/"]
|
|
SOURCE_LINUX = SOURCE_DIRS + ["source/linux/"]
|
|
SOURCE_WINDOWS = SOURCE_DIRS + [
|
|
"source/msvc10/", "source/angelscript/",
|
|
"source/glfw/", "source/libpng/", "source/freetype2/",
|
|
"source/linux/"]
|
|
|
|
class BuildStep(object):
|
|
@classmethod
|
|
def neededBy(cls, path):
|
|
return False
|
|
|
|
def execute(self):
|
|
pass
|
|
|
|
def finished(self):
|
|
return True
|
|
|
|
def finalize(self):
|
|
pass
|
|
|
|
class BuildGame32(BuildStep):
|
|
@classmethod
|
|
def neededBy(cls, path):
|
|
for d in SOURCE_LINUX:
|
|
if path.startswith(d):
|
|
return True
|
|
return False
|
|
|
|
def execute(self):
|
|
#Build 32-bit version
|
|
self.clean32 = subprocess.Popen(
|
|
["ARCH=32 make -f source/linux/Makefile clean_code"],
|
|
shell=True, cwd=SOURCE_FOLDER)
|
|
self.clean32.wait()
|
|
|
|
self.build32 = subprocess.Popen(
|
|
["ARCH=32 make -f source/linux/Makefile -j3 version compile"],
|
|
shell=True, cwd=SOURCE_FOLDER,
|
|
stdout=open(os.path.join(LOG_FOLDER, "build32_"+CURRENT_COMMIT+".log"), "w"),
|
|
stderr=subprocess.STDOUT)
|
|
|
|
def finished(self):
|
|
if self.build32.poll() == None:
|
|
return False
|
|
return True
|
|
|
|
def finalize(self):
|
|
if self.build32.returncode == 0:
|
|
copy_stage_folder("bin/lin32")
|
|
|
|
class BuildGame64(BuildStep):
|
|
@classmethod
|
|
def neededBy(cls, path):
|
|
for d in SOURCE_LINUX:
|
|
if path.startswith(d):
|
|
return True
|
|
return False
|
|
|
|
def execute(self):
|
|
self.clean64 = subprocess.Popen(
|
|
["ARCH=64 make -f source/linux/Makefile clean_code"],
|
|
shell=True, cwd=SOURCE_FOLDER)
|
|
self.clean64.wait()
|
|
|
|
self.build64 = subprocess.Popen(
|
|
["ARCH=64 make -f source/linux/Makefile -j3 version compile"],
|
|
shell=True, cwd=SOURCE_FOLDER,
|
|
stdout=open(os.path.join(LOG_FOLDER, "build64_"+CURRENT_COMMIT+".log"), "w"),
|
|
stderr=subprocess.STDOUT)
|
|
|
|
def finished(self):
|
|
if self.build64.poll() == None:
|
|
return False
|
|
return True
|
|
|
|
def finalize(self):
|
|
if self.build64.returncode == 0:
|
|
copy_stage_folder("bin/lin64")
|
|
|
|
class BuildAS32(BuildStep):
|
|
@classmethod
|
|
def neededBy(cls, path):
|
|
if path.startswith("source/angelscript/"):
|
|
return True
|
|
return False
|
|
|
|
def execute(self):
|
|
self.build32 = subprocess.Popen(
|
|
["ARCH=32 make -f source/linux/Makefile -j3 angelscript"],
|
|
shell=True, cwd=SOURCE_FOLDER,
|
|
stdout=open(os.path.join(LOG_FOLDER, "as32_"+CURRENT_COMMIT+".log"), "w"),
|
|
stderr=subprocess.STDOUT)
|
|
self.build32.wait();
|
|
|
|
class BuildAS64(BuildStep):
|
|
@classmethod
|
|
def neededBy(cls, path):
|
|
if path.startswith("source/angelscript/"):
|
|
return True
|
|
return False
|
|
|
|
def execute(self):
|
|
self.build64 = subprocess.Popen(
|
|
["ARCH=64 make -f source/linux/Makefile -j3 angelscript"],
|
|
shell=True, cwd=SOURCE_FOLDER,
|
|
stdout=open(os.path.join(LOG_FOLDER, "as64_"+CURRENT_COMMIT+".log"), "w"),
|
|
stderr=subprocess.STDOUT)
|
|
self.build64.wait();
|
|
|
|
class BuildWindows(BuildStep):
|
|
def __init__(self):
|
|
self.running = False
|
|
|
|
@classmethod
|
|
def neededBy(cls, path):
|
|
for d in SOURCE_WINDOWS:
|
|
if path.startswith(d):
|
|
return True
|
|
return False
|
|
|
|
def execute(self):
|
|
pass
|
|
|
|
def finished(self):
|
|
if not self.running:
|
|
#Create marker file for windows build
|
|
with open(os.path.join(SOURCE_FOLDER, "WIN_COMPILE"), "w") as f:
|
|
f.write("BUILD");
|
|
self.running = True
|
|
return False
|
|
|
|
return not os.path.exists(os.path.join(SOURCE_FOLDER, "WIN_COMPILE"))
|
|
|
|
def finalize(self):
|
|
#Copy over symbols
|
|
self.syncsymbols = subprocess.Popen(
|
|
["rsync -tavz symbols/ bms@glacicle.org:starruler2.com/symbols/"],
|
|
shell=True)
|
|
|
|
#Stage all the files needed
|
|
if os.path.exists(os.path.join(DEST_FOLDER, "Star Ruler 2.exe")):
|
|
Dest.stage("Star Ruler 2.exe")
|
|
|
|
for f in os.listdir(os.path.join(DEST_FOLDER, "bin/win32")):
|
|
if f.endswith(".exe") or f.endswith(".dll"):
|
|
Dest.stage("bin/win32/"+f)
|
|
|
|
for f in os.listdir(os.path.join(DEST_FOLDER, "bin/win64")):
|
|
if f.endswith(".exe") or f.endswith(".dll"):
|
|
Dest.stage("bin/win64/"+f)
|
|
|
|
#Rename the log to indicate the commit
|
|
if os.path.exists(os.path.join(LOG_FOLDER, "msvc32_build.log")):
|
|
os.rename(os.path.join(LOG_FOLDER, "msvc32_build.log"),
|
|
os.path.join(LOG_FOLDER, "msvc32_"+CURRENT_COMMIT+".log"))
|
|
|
|
if os.path.exists(os.path.join(LOG_FOLDER, "msvc64_build.log")):
|
|
os.rename(os.path.join(LOG_FOLDER, "msvc64_build.log"),
|
|
os.path.join(LOG_FOLDER, "msvc64_"+CURRENT_COMMIT+".log"))
|
|
|
|
|
|
BUILD_STEPS = [BuildAS32, BuildAS64, BuildGame32, BuildGame64, BuildWindows]
|
|
|
|
def publish_file(path):
|
|
if path.startswith("source/"):
|
|
return False
|
|
return True
|
|
|
|
def copy(path):
|
|
folder = os.path.dirname(path)
|
|
|
|
#Make the target folder
|
|
try:
|
|
os.makedirs(os.path.join(DEST_FOLDER, folder))
|
|
except OSError as exc:
|
|
pass
|
|
|
|
#Copy the file
|
|
shutil.copy2(os.path.join(SOURCE_FOLDER, path),
|
|
os.path.join(DEST_FOLDER, path))
|
|
|
|
def copy_stage_folder(path):
|
|
folder = os.path.join(SOURCE_FOLDER, path)
|
|
for root, folders, files in os.walk(folder):
|
|
for f in files:
|
|
fpath = os.path.join(root[len(SOURCE_FOLDER)+1:], f)
|
|
copy(fpath)
|
|
Dest.stage(fpath)
|
|
|
|
def setup():
|
|
#Clone the repositories first
|
|
#Note: This assumes source and dest are in a consistent state,
|
|
#which is something that needs to be guaranteed manually
|
|
if not os.path.exists(SOURCE_FOLDER):
|
|
p = subprocess.Popen(["git", "clone", SOURCE_REPO, SOURCE_FOLDER])
|
|
p.wait()
|
|
if not os.path.exists(DEST_FOLDER):
|
|
p = subprocess.Popen(["git", "clone", DEST_REPO, DEST_FOLDER])
|
|
p.wait()
|
|
if not os.path.exists(LOG_FOLDER):
|
|
os.mkdir(LOG_FOLDER)
|
|
|
|
#Prepare accessors
|
|
global Source
|
|
Source = Repo(SOURCE_FOLDER)
|
|
global Dest
|
|
Dest = Repo(DEST_FOLDER)
|
|
|
|
def updateRepo(folder):
|
|
p = subprocess.Popen(["git", "fetch", "origin", "master"], cwd=folder)
|
|
p.wait()
|
|
|
|
def pushRepo(folder):
|
|
p = subprocess.Popen(["git", "push", "origin", "master"], cwd=folder)
|
|
p.wait()
|
|
|
|
def listCommits(repo, front, back, l):
|
|
#Find all parent commits
|
|
if not isinstance(back, set):
|
|
stack = [repo.commit(back)]
|
|
back = set()
|
|
|
|
while stack:
|
|
c = stack.pop()
|
|
if c.id not in back:
|
|
back.add(c.id)
|
|
for parent in c._get_parents():
|
|
stack.append(repo.commit(parent))
|
|
|
|
#Make sure it isn't already done
|
|
if front in back:
|
|
return
|
|
|
|
#Check this commit
|
|
commit = repo.commit(front)
|
|
l.append(commit)
|
|
|
|
for parent in commit._get_parents():
|
|
listCommits(repo, parent, back, l)
|
|
|
|
def switchTo(folder, commit):
|
|
p = subprocess.Popen(["git", "reset", "--hard", commit], cwd=folder)
|
|
p.wait()
|
|
|
|
def main():
|
|
setup()
|
|
|
|
while True:
|
|
#Try to update the repo
|
|
updateRepo(SOURCE_FOLDER)
|
|
HEAD = Source.head();
|
|
FETCH_HEAD = Source.ref("FETCH_HEAD")
|
|
|
|
#Check if there are any updates
|
|
if HEAD == FETCH_HEAD:
|
|
time.sleep(60)
|
|
continue
|
|
|
|
#Build the tree of commits to build
|
|
commits = []
|
|
listCommits(Source, FETCH_HEAD, HEAD, commits)
|
|
commits.reverse()
|
|
base_tree = Source.commit(HEAD).tree
|
|
|
|
for c in commits:
|
|
global CURRENT_COMMIT
|
|
CURRENT_COMMIT = c.id
|
|
switchTo(SOURCE_FOLDER, c.id)
|
|
print("Executing commit "+c.id)
|
|
|
|
#Iterate through the difference tree
|
|
diff = tree_changes(Source, base_tree, c.tree)
|
|
steps = [False for x in BUILD_STEPS]
|
|
for d in diff:
|
|
#Handle deletes
|
|
if d.type == 'delete':
|
|
fname = d.old.path
|
|
if publish_file(fname):
|
|
os.remove(os.path.join(DEST_FOLDER, fname))
|
|
Dest.stage(fname)
|
|
continue
|
|
|
|
#Copy over all the files
|
|
fname = d.new.path
|
|
if not fname:
|
|
continue
|
|
if publish_file(fname):
|
|
copy(fname)
|
|
Dest.stage(fname)
|
|
for i, st in enumerate(BUILD_STEPS):
|
|
if st.neededBy(fname):
|
|
steps[i] = True
|
|
|
|
#Execute all build steps
|
|
cursteps = []
|
|
for i, needed in enumerate(steps):
|
|
if not needed:
|
|
continue
|
|
step = BUILD_STEPS[i]()
|
|
step.execute()
|
|
while not step.finished():
|
|
time.sleep(1)
|
|
cursteps.append(step)
|
|
|
|
#Finalize all build steps
|
|
for step in cursteps:
|
|
step.finalize()
|
|
|
|
#Set up the commit in the new repo
|
|
Dest.do_commit(
|
|
message=c.message+"\n\nOriginal Commit: BlindMindStudios/StarRuler2@"+c.id,
|
|
committer=c.committer,
|
|
author=c.author,
|
|
commit_timestamp=c._commit_time,
|
|
commit_timezone=c._commit_timezone,
|
|
author_timestamp=c._author_time,
|
|
author_timezone=c._author_timezone)
|
|
|
|
#Set up for next
|
|
base_tree = c.tree
|
|
|
|
#Push this individual commit
|
|
pushRepo(DEST_FOLDER)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|
|
# vim: ff=unix sw=4 et:
|