ForgePatch/release.py

417 lines
16 KiB
Python

import os, os.path, sys, glob
import shutil, fnmatch, time, json
import logging, zipfile, re, subprocess
from pprint import pformat, pprint
from optparse import OptionParser
from urllib2 import HTTPError
from contextlib import closing
from datetime import datetime
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
forge_dir = os.path.dirname(os.path.abspath(__file__))
from forge import reset_logger, load_version, zip_folder, zip_create, inject_version, build_forge_dev
from changelog import make_changelog
zip = None
zip_name = None
zip_base = None
version_str = None
version_mc = None
def main():
global version_str
global version_mc
parser = OptionParser()
parser.add_option('-m', '--mcp-dir', action='store', dest='mcp_dir', help='MCP Path', default=None)
parser.add_option('-b', '--build', action='store', dest='build', help='Build number', default=None)
parser.add_option('-s', '--skipchangelog', action='store_true', dest='skip_changelog', help='Skip Changelog', default=False)
parser.add_option('-j', '--sign-jar', action='store', dest='sign_jar', help='Path to jar signer command', default=None)
options, _ = parser.parse_args()
build_num = 0
if not options.build is None:
try:
build_num = int(options.build)
except:
pass
mcp_dir = os.path.join(forge_dir, 'mcp')
if not options.mcp_dir is None:
mcp_dir = os.path.abspath(options.mcp_dir)
ret = 0
fml_dir = os.path.join(forge_dir, 'fml')
ret = build_forge_dev(mcp_dir, forge_dir, fml_dir, build_num)
if ret != 0:
sys.exit(ret)
temp_dir = os.path.join(forge_dir, 'temp')
src_dir = os.path.join(mcp_dir, 'src')
reobf_dir = os.path.join(mcp_dir, 'reobf')
client_dir = os.path.join(reobf_dir, 'minecraft')
fml_dir = os.path.join(temp_dir, 'fml')
print '=================================== Release Start ================================='
fml = glob.glob(os.path.join(forge_dir, 'fml', 'target', 'fml-src-*.%d-*.zip' % build_num))
if not len(fml) == 1:
if len(fml) == 0:
print 'Missing FML source zip, should be named fml-src-*.zip inside ./fml/target/ created when running setup'
else:
print 'To many FML source zips found, we should only have one. Check the Forge Git for the latest FML version supported'
sys.exit(1)
if os.path.isdir(fml_dir):
shutil.rmtree(fml_dir)
print 'Extracting: %s' % os.path.basename(fml[0])
zf = zipfile.ZipFile(fml[0])
zf.extractall(temp_dir)
zf.close()
if os.path.isfile('MANIFEST.MF'):
os.remove('MANIFEST.MF')
fml_name = os.path.basename(fml[0]).replace('src', 'universal').replace('.zip', '.jar').replace('-master.', '.')
print('Extracting %s MANIFEST.MF' % fml_name)
with closing(zipfile.ZipFile(os.path.join(forge_dir, 'fml', 'target', fml_name), mode='r')) as zip_in:
with closing(open('MANIFEST.MF', 'wb')) as out:
out.write(zip_in.read('META-INF/MANIFEST.MF'))
error_level = 0
try:
sys.path.append(mcp_dir)
from runtime.reobfuscate import reobfuscate
os.chdir(mcp_dir)
reset_logger()
reobfuscate(None, False, True, True, True, False, False)
reset_logger()
os.chdir(forge_dir)
except SystemExit, e:
print 'Reobfusicate Exception: %d ' % e.code
error_level = e.code
extract_fml_obfed(fml_dir, mcp_dir, reobf_dir, client_dir)
gen_bin_patches(mcp_dir, os.path.join(forge_dir, 'fml'), build_num, client_dir)
version = load_version(build_num)
version_forge = '%d.%d.%d.%d' % (version['major'], version['minor'], version['revision'], version['build'])
version_mc = load_mc_version(fml_dir)
branch = get_branch_name()
version_str = '%s-%s' % (version_mc, version_forge)
if not branch == "":
version_str = '%s-%s' % (version_str, branch)
out_folder = os.path.join(forge_dir, 'target')
if not os.path.isdir(out_folder):
os.makedirs(out_folder)
for f in ['minecraftforge-changelog-%s.txt', 'minecraftforge-universal-%s.jar', 'minecraftforge-installer-%s.jar']:
fn = os.path.join(out_folder, f % version_str)
if os.path.isfile(fn):
os.remove(fn)
if not options.skip_changelog:
changelog_file = 'target/minecraftforge-changelog-%s.txt' % (version_str)
try:
make_changelog("http://ci.jenkins.minecraftforge.net/job/minecraftforge/", build_num, changelog_file, version_str)
except HTTPError, e:
print 'Changelog failed to generate: %s' % e
options.skip_changelog = True
version_file = 'forgeversion.properties'
if os.path.exists(version_file):
os.remove(version_file)
with open(version_file, 'wb') as fh:
fh.write('forge.major.number=%d\n' % version['major'])
fh.write('forge.minor.number=%d\n' % version['minor'])
fh.write('forge.revision.number=%d\n' % version['revision'])
fh.write('forge.build.number=%d\n' % version['build'])
json_data = gather_json(forge_dir, version_mc, version_forge, version_str)
if not options.sign_jar is None:
sign_jar(forge_dir, options.sign_jar, client_dir, 'minecraftforge-universal-%s.jar' % version_str)
else:
zip_start('minecraftforge-universal-%s.jar' % version_str)
zip_folder(client_dir, '', zip)
zip_add('MANIFEST.MF','META-INF/MANIFEST.MF')
zip_add('client/forge_logo.png')
zip_add('install/MinecraftForge-Credits.txt')
zip_add('install/MinecraftForge-License.txt')
zip_add('install/Paulscode IBXM Library License.txt')
zip_add('install/Paulscode SoundSystem CodecIBXM License.txt')
zip_add('common/forge_at.cfg')
zip_add('common/assets','assets')
zip_add(version_file)
if not options.skip_changelog:
zip_add(changelog_file, 'MinecraftForge-Changelog.txt')
print(' version.json')
zip.writestr('version.json', json.dumps(json_data['versionInfo'], indent=4, separators=(',', ': ')))
#Add dependancy and licenses from FML
FML_FILES = [
'CREDITS-fml.txt',
'LICENSE-fml.txt',
'README-fml.txt',
'common/fml_at.cfg',
'common/fml_marker.cfg',
'common/fmlversion.properties',
'common/mcpmod.info',
'client/mcplogo.png',
'common/deobfuscation_data-%s.lzma' % version_mc
]
for file in FML_FILES:
zip_add(os.path.join(fml_dir, file))
zip_end()
build_installer(forge_dir, version_str, version_forge, version_mc, out_folder, json.dumps(json_data, indent=4, separators=(',', ': ')))
inject_version(os.path.join(forge_dir, 'common/net/minecraftforge/common/ForgeVersion.java'.replace('/', os.sep)), build_num)
zip_start('minecraftforge-src-%s.zip' % version_str, 'forge')
zip_add('client', 'client')
zip_add('common', 'common')
zip_add('patches', 'patches')
zip_add(fml_dir, 'fml')
zip_add('install', '')
zip_add('forge.py')
zip_add(version_file)
if not options.skip_changelog:
zip_add(changelog_file, 'MinecraftForge-Changelog.txt')
zip_end()
inject_version(os.path.join(forge_dir, 'common/net/minecraftforge/common/ForgeVersion.java'.replace('/', os.sep)), 0)
if os.path.exists(version_file):
os.remove(version_file)
shutil.rmtree(temp_dir)
if os.path.isfile('MANIFEST.MF'):
os.remove('MANIFEST.MF')
print '=================================== Release Finished %d =================================' % error_level
sys.exit(error_level)
def gather_json(forge_dir, version_mc, version_forge, version_str):
def getTZ():
ret = '-'
t = time.timezone
if (t < 0):
ret = '+'
t *= -1
h = int(t/60/60)
t -= (h*60*60)
m = int(t/60)
return '%s%02d%02d' % (ret, h, m)
timestamp = datetime.now().replace(microsecond=0).isoformat() + getTZ()
json_data = {}
with closing(open(os.path.join(forge_dir, 'fml', 'jsons', '%s-rel.json' % version_mc), 'r')) as fh:
data = fh.read()
data = data.replace('@version@', version_forge)
data = data.replace('@timestamp@', timestamp)
data = data.replace('@minecraft_version@', version_mc)
data = data.replace('@universal_jar@', 'minecraftforge-universal-%s.jar' % version_str)
data = data.replace('FMLTweaker', 'F_M_L_Tweaker')
data = data.replace('FML', 'Forge')
data = data.replace('F_M_L_Tweaker', 'FMLTweaker')
data = data.replace('cpw.mods:fml:', 'net.minecraftforge:minecraftforge:')
json_data = json.loads(data)
pprint(json_data)
return json_data
def build_installer(forge_dir, version_str, version_forge, version_minecraft, out_folder, json_data):
file_name = 'minecraftforge-installer-%s.jar' % version_str
universal_name = 'minecraftforge-universal-%s.jar' % version_str
print '================== %s Start ==================' % file_name
with closing(zipfile.ZipFile(os.path.join(forge_dir, 'fml', 'installer_base.jar'), mode='a')) as zip_in:
with closing(zipfile.ZipFile(os.path.join(out_folder, file_name), 'w', zipfile.ZIP_DEFLATED)) as zip_out:
# Copy everything over
for i in zip_in.filelist:
if not i.filename in ['install_profile.json', 'big_logo.png']:
#print(' %s' % i.filename)
zip_out.writestr(i.filename, zip_in.read(i.filename))
print(' %s' % universal_name)
zip_out.write(os.path.join(out_folder, universal_name), universal_name)
print(' big_logo.png')
zip_out.write(os.path.join(forge_dir, 'client', 'forge_logo.png'), 'big_logo.png')
print(' install_profile.json')
zip_out.writestr('install_profile.json', json_data)
print '================== %s Finished ==================' % file_name
def zip_add(file, key=None):
if key == None:
key = os.path.basename(file)
else:
key = key.replace('/', os.sep)
if not zip_base is None:
key = os.path.join(zip_base, key)
file = os.path.join(forge_dir, file.replace('/', os.sep))
if os.path.isdir(file):
zip_folder(file, key, zip)
else:
if os.path.isfile(file):
print ' ' + key
zip.write(file, key)
def zip_start(name, base=None):
global zip, zip_name, zip_base
zip_name = name
print '================== %s Start ==================' % zip_name
zip_file = os.path.join(forge_dir, 'target', name)
zip = zipfile.ZipFile(zip_file, 'w', zipfile.ZIP_DEFLATED)
zip_base = base
def zip_end():
global zip, zip_name, zip_base
zip.close()
print '================== %s Finished ==================' % zip_name
zip_name = None
zip_base = None
def load_mc_version(fml_dir):
props = os.path.join(fml_dir, 'common', 'fmlversion.properties')
if not os.path.isfile(props):
print 'Could not load fmlversion.properties, build failed'
sys.exit(1)
with open(props, 'r') as fh:
for line in fh:
line = line.strip()
if line.startswith('fmlbuild.mcversion'):
return line.split('=')[1].strip()
print 'Could not load fmlversion.properties, build failed'
sys.exit(1)
def extract_fml_obfed(fml_dir, mcp_dir, reobf_dir, client_dir):
fml_file = os.path.join(fml_dir, 'difflist.txt')
if not os.path.isfile(fml_file):
print 'Could not find Forge ModLoader\'s DiffList, looking for it at: %s' % fml_file
sys.exit(1)
with open(fml_file, 'r') as fh:
lines = fh.readlines()
client = zipfile.ZipFile(os.path.join(mcp_dir, 'temp', 'client_reobf.jar'))
print 'Extracting Reobfed Forge ModLoader classes'
for line in lines:
line = line.replace('\n', '').replace('\r', '').replace('/', os.sep)
if not os.path.isfile(os.path.join(reobf_dir, line)):
print ' %s' % line
side = line.split(os.sep)[0]
if side == 'minecraft':
client.extract(line[10:].replace(os.sep, '/'), client_dir)
client.close()
def get_branch_name():
from subprocess import Popen, PIPE, STDOUT
branch = ''
if os.getenv("GIT_BRANCH") is None:
try:
process = Popen(["git", "rev-parse", "--abbrev-ref", "HEAD"], stdout=PIPE, stderr=STDOUT, bufsize=-1)
branch, _ = process.communicate()
branch = branch.rstrip('\r\n')
except OSError:
print "Git not found"
else:
branch = os.getenv("GIT_BRANCH").rpartition('/')[2]
branch = branch.replace('master', '')
branch = branch.replace('HEAD', '')
print 'Detected Branch as \'%s\'' % branch
return branch
def sign_jar(forge_dir, command, files, dest_zip):
from subprocess import Popen, PIPE, STDOUT
global zip
zip_file = os.path.join(forge_dir, 'tmp.jar')
if os.path.isfile(zip_file):
os.remove(zip_file)
print '============== Creating tmp zip to sign ====================='
zf = zipfile.ZipFile(zip_file, 'w', zipfile.ZIP_DEFLATED)
zf.write(os.path.join(forge_dir, 'MANIFEST.MF'), 'META-INF/MANIFEST.MF')
zip_folder_filter(files, '', zf, 'cpw/mods/'.replace('/', os.sep))
zip_folder_filter(files, '', zf, 'net/minecraftforge/'.replace('/', os.sep))
zf.close()
print '================ End tmp zip to sign ========================'
try:
process = Popen([command, zip_file, "forge"], stdout=PIPE, stderr=STDOUT, bufsize=-1)
out, _ = process.communicate()
print out
except OSError as e:
print "Error creating signed tmp jar: %s" % e.strerror
sys.exit(1)
tmp_dir = os.path.join(forge_dir, 'tmp')
if os.path.isdir(tmp_dir):
shutil.rmtree(tmp_dir)
zf = zipfile.ZipFile(zip_file)
zf.extractall(tmp_dir)
zf.close()
os.remove(zip_file)
zip_start(dest_zip)
zip_folder(tmp_dir, '', zip)
zip_folder(files, '', zip)
if os.path.isdir(tmp_dir):
shutil.rmtree(tmp_dir)
def zip_folder_filter(path, key, zip, filter):
files = os.listdir(path)
for file in files:
file_path = os.path.join(path, file)
file_key = os.path.join(key, file)
if os.path.isdir(file_path):
zip_folder_filter(file_path, file_key, zip, filter)
else:
if file_key.startswith(filter):
print file_key
zip.write(file_path, file_key)
def gen_bin_patches(mcp_dir, fml_dir, build_num, client_dir):
print('Creating Binary patches')
os.environ['WORKSPACE'] = os.path.join(mcp_dir, '..')
os.environ['BUILD_NUMBER'] = str(build_num)
BUILD = ['ant', 'makebinpatches']
if sys.platform.startswith('win'):
BUILD = ['cmd', '/C'] + BUILD
if not run_command(BUILD, cwd=fml_dir):
print('Could not crate binary patches')
sys.exit(1)
fml_lzma = os.path.join(fml_dir, 'binpatches.pack.lzma')
obf_lzma = os.path.join(client_dir, 'binpatches.pack.lzma')
shutil.move(fml_lzma, obf_lzma)
def run_command(command, cwd='.', verbose=True):
print('Running command: ')
print(pformat(command))
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, cwd=cwd)
while process.poll() is None:
line = process.stdout.readline()
if line:
line = line.rstrip()
print(line)
if process.returncode:
print "failed: {0}".format(process.returncode)
return False
return True
if __name__ == '__main__':
main()