Dockerise

This commit is contained in:
clienthax 2021-08-06 18:07:52 +01:00
parent 8ec2dd75c2
commit 55e2e94b63
3921 changed files with 178 additions and 111 deletions

2
resources/scripts/ctcci/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
ctcconfig.py
__pycache__

View file

@ -0,0 +1,25 @@
import os
from ctcconfig import *
verdata = {}
# Get all APKs from the dir
for file in os.listdir(RESULT_FOLDER):
if not file.startswith("cutthecord-"):
continue
filedata = file.replace("cutthecord-", "").replace(".apk", "").split("-")
if filedata[1] not in verdata:
verdata[filedata[1]] = []
verdata[filedata[1]].append(int(filedata[0]))
# Delete the older APKs
for branch in verdata:
vers = verdata[branch]
vers.sort()
if len(vers) <= CLEAN_ARTIFACT_KEEP_COUNT:
continue
for ver in vers[0:-1 * CLEAN_ARTIFACT_KEEP_COUNT]:
file = os.path.join(RESULT_FOLDER, f"cutthecord-{ver}-{branch}.apk")
os.unlink(file)

View file

@ -0,0 +1,253 @@
#!/usr/bin/env python3
import os
import sys
import shutil
import json
import subprocess
from ctcconfig import *
print("Welcome to CutTheCord CI :)")
BRANCH = sys.argv[1]
PATCHES = sys.argv[2:]
FORCE = False
DEFAULT_PATCHES = ["necessary", "branding", "customversion"]
OPTIONAL_PATCHES = ["necessary", "blobs"]
def patch(patch_file, workdir, patch_name=""):
if not os.path.exists(patch_file):
# Allow missing optional patches
if patch_name in OPTIONAL_PATCHES:
return
raise FileNotFoundError(f"No patches with name \"{patch_file}\" :(")
print(f"Applying {patch_file}")
subprocess.run(f"patch -p1 --no-backup-if-mismatch -i {patch_file}",
shell=True, check=True, cwd=workdir)
# Wipe and recreate the working folder
if os.path.exists(WORK_APK_PATH):
shutil.rmtree(WORK_APK_PATH)
os.environ["DISTOK_EXTRACTED_DISCORD_PATH"] = WORK_APK_PATH
os.makedirs(WORK_FOLDER, exist_ok=True)
with open(os.path.join(REPO_FOLDER, "resources/patchport-state.json")) as f:
STATE = json.load(f)
VERSION = STATE["versioncode"]
BASE_APK_PATH = os.path.join(WORK_FOLDER, f"discord-base-{VERSION}")
# Prepare names of input and output APKs
INPUT_FILE = os.path.join(DISTOK_FOLDER, "android",
f"{PACKAGE_ID}-{VERSION}.apk")
# OUTPUT_FILE = os.path.join(RESULT_FOLDER,
# f"cutthecord-{VERSION}-{'_'.join(PATCHES)}.apk")
OUTPUT_FILE = os.path.join(RESULT_FOLDER,
f"cutthecord-{VERSION}-{BRANCH}.apk")
# Add necessary patches to the list of patches that will be applied
# Important to have this after the output file name generation
# otherwise it'll include it, which is not wanted
PATCHES = DEFAULT_PATCHES + PATCHES
print(f"Branch: {BRANCH}, output name: {OUTPUT_FILE}")
# Check if the version is already patched, if it is exit
if not FORCE and os.path.exists(OUTPUT_FILE):
print("This version is already patched, bye!")
sys.exit(1)
if DO_GITPULL:
# Update cutthecord
subprocess.run("git pull", shell=True, cwd=REPO_FOLDER)
# Extract the APK if it's not already extracted to base cache
if not os.path.exists(BASE_APK_PATH):
subprocess.run(f"{APKTOOL_BIN} d --no-dummy {INPUT_FILE} -o {BASE_APK_PATH} -f",
shell=True,
cwd=WORK_FOLDER)
# Copy the base cache to work on it
shutil.copytree(BASE_APK_PATH, WORK_APK_PATH)
# Go through patches and apply every single one of them
for patch_name in PATCHES:
pre_script = os.path.join(REPO_FOLDER, "resources/patches",
patch_name, f"{VERSION}-pre.sh")
if os.path.exists(pre_script):
subprocess.run(f"bash {pre_script}",
shell=True,
cwd=WORK_APK_PATH)
# Apply custom emoji patches
if patch_name in ["mutant", "blobs"]:
print(f"Applying {patch_name} emoji patch")
patch_script = os.path.join(REPO_FOLDER, "resources/patches",
patch_name, "emojireplace.py")
subprocess.run(f"{PYTHON_BIN} {patch_script}",
shell=True,
cwd=WORK_APK_PATH)
# Apply custom emoji patches
elif patch_name == "customtheme":
print(f"Applying splash patch")
splash = os.path.join(REPO_FOLDER, "resources/patches",
patch_name, "asset_loading.png")
patch_script = os.path.join(REPO_FOLDER, "resources/patches",
patch_name, "fixsplash.sh")
subprocess.run(f"bash {patch_script} {splash}",
shell=True,
cwd=WORK_APK_PATH)
patch_file = os.path.join(REPO_FOLDER, "resources/patches",
patch_name, f"{VERSION}.patch")
# Apply custom version patches
if patch_name == "customversion":
print(f"Applying custom version")
patch_script = os.path.join(REPO_FOLDER, "resources/patches",
patch_name, "addpatch.py")
subprocess.run(f"{PYTHON_BIN} {patch_script} {patch_file} "
f"{' '.join(PATCHES)}",
shell=True,
cwd=WORK_APK_PATH)
patch(patch_file.replace(".patch", "-custom.patch"), WORK_APK_PATH)
# Apply branding patches
elif patch_name == "branding":
print(f"Applying branding icon patch")
if BRANCH in ICONS:
shutil.copyfile(ICONS[BRANCH],
os.path.join(WORK_APK_PATH, "res",
"mipmap-xxxhdpi", "logo_debug.png"))
elif "default" in ICONS:
shutil.copyfile(ICONS["default"],
os.path.join(WORK_APK_PATH, "res",
"mipmap-xxxhdpi", "logo_debug.png"))
if BRANCH in DYN_ICONS:
shutil.copyfile(DYN_ICONS[BRANCH]["bg"],
os.path.join(WORK_APK_PATH, "res",
"mipmap-xxxhdpi",
"ic_launcher_background.png"))
shutil.copyfile(DYN_ICONS[BRANCH]["fg"],
os.path.join(WORK_APK_PATH, "res",
"mipmap-xxxhdpi",
"ic_launcher_foreground.png"))
elif "default" in ICONS:
shutil.copyfile(DYN_ICONS["default"]["bg"],
os.path.join(WORK_APK_PATH, "res",
"mipmap-xxxhdpi",
"ic_launcher_background.png"))
shutil.copyfile(DYN_ICONS["default"]["fg"],
os.path.join(WORK_APK_PATH, "res",
"mipmap-xxxhdpi",
"ic_launcher_foreground.png"))
patch_script = os.path.join(REPO_FOLDER, "resources/patches",
patch_name, "customicon.sh")
subprocess.run(f"bash {patch_script}", shell=True, cwd=WORK_APK_PATH)
patch_script = os.path.join(REPO_FOLDER, "resources/patches",
patch_name, "customdynamicicon.sh")
subprocess.run(f"bash {patch_script}", shell=True, cwd=WORK_APK_PATH)
patch_script = os.path.join(REPO_FOLDER, "resources/patches",
patch_name, "addpatch.py")
# Hell code
app_name = APP_NAMES.get(BRANCH, APP_NAMES.get("default", "CutTheCord"))
subprocess.run(f"{PYTHON_BIN} {patch_script} "
f"{patch_file} {app_name} {BRANCH}",
shell=True,
cwd=WORK_APK_PATH)
patch(patch_file.replace(".patch", "-custom.patch"), WORK_APK_PATH)
elif patch_name in ["bettertm", "bettertmlight"]:
print(f"Applying bettertm emoji patch")
patch_dir = os.path.join(REPO_FOLDER, "resources/patches",
patch_name)
patch_script = os.path.join(REPO_FOLDER, "resources/patches",
patch_name, "bettertm.sh")
subprocess.run(f"bash {patch_script} {patch_dir}",
shell=True,
cwd=WORK_APK_PATH)
# Apply custom ringtone
elif patch_name == "customring":
print(f"Applying custom ringtone")
if BRANCH in RINGTONES:
CUSTOM_RINGTONE = RINGTONES[BRANCH]
else:
CUSTOM_RINGTONE = RINGTONES["default"]
shutil.copyfile(CUSTOM_RINGTONE,
os.path.join(WORK_APK_PATH, "res",
"raw", "call_ringing.mp3"))
# Apply custom fonts
elif patch_name == "customfont":
print(f"Applying custom font")
if BRANCH in FONTS:
fonts = FONTS[BRANCH]
else:
fonts = FONTS["default"]
for font in fonts:
shutil.copyfile(fonts[font],
os.path.join(WORK_APK_PATH, "res",
"font", font))
# Apply any other patches
else:
patch(patch_file, WORK_APK_PATH, patch_name)
post_script = os.path.join(REPO_FOLDER, "resources/patches",
patch_name, f"{VERSION}-post.sh")
if os.path.exists(post_script):
subprocess.run(f"bash {post_script}",
shell=True,
cwd=WORK_APK_PATH)
# Pack the APK
subprocess.run(f"{APKTOOL_BIN} b discord",
shell=True,
cwd=WORK_FOLDER)
APK_PATH = os.path.join(WORK_FOLDER, "discord",
"dist", f"{PACKAGE_ID}-{VERSION}.apk")
# Sign the APK
if DO_APKSIGNER:
subprocess.run(f"apksigner sign --ks {KEYSTORE_FILE} "
f"--ks-key-alias {KEYSTORE_ALIAS} "
f"--ks-pass pass:{KEYSTORE_PASS} "
f"{APK_PATH}",
shell=True,
cwd=WORK_FOLDER)
else:
subprocess.run(f"jarsigner -storepass {KEYSTORE_PASS} -keystore "
f"{KEYSTORE_FILE} {APK_PATH} {KEYSTORE_ALIAS}",
shell=True,
cwd=WORK_FOLDER)
# Copy the result file
shutil.copyfile(APK_PATH, OUTPUT_FILE)
if DO_FDROID:
# Do fdroid magic
subprocess.run(f"fdroid update -c",
shell=True,
cwd=FDROID_FOLDER)
# Wipe caches
shutil.rmtree(WORK_APK_PATH)
print("CutTheCord CI has fulfilled its purpose.")

View file

@ -0,0 +1,79 @@
import os
APKTOOL_BIN = "java -jar /opt/ctc/tools/apktool.jar" # use latest
# APKs must be placed under $DISTOK_FOLDER/android/$PACKAGE_ID-$VERSION_NUMBER.apk
# Example: /home/ave/distok/android/com.discord-909.apk
DISTOK_FOLDER = "/opt/ctc/gitrepo/resources/distok"
# Set if F-Droid repo should be automatically updated or not
# If set to False, you can leave FDROID_FOLDER empty
DO_FDROID = True
FDROID_FOLDER = "/opt/ctc/gitrepo/resources/fdroid"
# This is where APKs will be placed
RESULT_FOLDER = FDROID_FOLDER + "/repo"
# Repo for CutTheCord (https://gitdab.com/distok/cutthecord)
DO_GITPULL = True
REPO_FOLDER = "/opt/ctc/gitrepo"
# If false, jarsigner will be used for signing the apk.
DO_APKSIGNER = True
# Keystore file, alias and pass. Required.
KEYSTORE_FILE = "/opt/ctc/gitrepo/resources/keystore.jks"
KEYSTORE_ALIAS = "test"
KEYSTORE_PASS = "password"
# Set this to the python version you want to use. Needs to be 3.6+.
PYTHON_BIN = "python3"
# Folder where the apk will be extracted to, patched in, and packed back on
# You're not recommended to touch WORK_APK_PATH.
WORK_FOLDER = "/tmp/cutthecord"
WORK_APK_PATH = os.path.join(WORK_FOLDER, "discord")
# Package ID for discord. You should probably not touch this.
PACKAGE_ID = "com.discord"
# Set to force if you want builds to be done
# even if there's already a build for this version
FORCE = False
# Custom ringtones, default = applied to all the ones not explicitly stated
RINGTONES = {"ave": "/opt/ctc/gitrepo/resources/ringtones/sans.mp3",
"default": "/opt/ctc/gitrepo/resources/ringtones/removeskype.mp3"}
# Custom icons, default = applied to all the ones not explicitly stated
ICONS = {"ave": "/opt/ctc/gitrepo/resources/icons/ctclogoave.png",
"default": "/opt/ctc/gitrepo/resources/icons/ctclogo.png"}
# Custom dynamic icons, default = applied to all the ones not explicitly stated
DYN_ICONS = {"ave": {"bg": "/opt/ctc/gitrepo/resources/icons/bg.png",
"fg": "/opt/ctc/gitrepo/resources/icons/fg.png"},
"default": {"bg": "/opt/ctc/gitrepo/resources/icons/dcbg.png",
"fg": "/opt/ctc/gitrepo/resources/icons/dcfg.png"}}
# Custom app names, default = applied to all the ones not explicitly stated
# Needs to be one word, __ gets replaced with space
APP_NAMES = {"ave": "CutTheCord__Ave",
"litecord": "Litecord",
"test": "CTC__Testing",
"default": "CutTheCord"}
# Custom fonts, default = applied to all the ones not explicitly stated
FONTS = {"ellie": {"whitney_bold.ttf": "/opt/ctc/gitrepo/resources/fonts/GoogleSans-Bold.ttf",
"whitney_semibold.ttf": "/opt/ctc/gitrepo/resources/fonts/GoogleSans-Medium.ttf",
"whitney_medium.ttf": "/opt/ctc/gitrepo/resources/fonts/GoogleSans-Regular.ttf"},
"dyslexic": {"whitney_bold.ttf": "/opt/ctc/gitrepo/resources/fonts/OpenDyslexic3-Bold.ttf",
"whitney_semibold.ttf": "/opt/ctc/gitrepo/resources/fonts/OpenDyslexic3-Bold.ttf",
"whitney_medium.ttf": "/opt/ctc/gitrepo/resources/fonts/OpenDyslexic3-Regular.ttf"},
"murm": {"whitney_bold.ttf": "/opt/ctc/gitrepo/resources/fonts/comicbd.ttf",
"whitney_semibold.ttf": "/opt/ctc/gitrepo/resources/fonts/comicbd.ttf",
"whitney_medium.ttf": "/opt/ctc/gitrepo/resources/fonts/comic.ttf"},
"ave": {"sourcecodepro_semibold.ttf": "/opt/ctc/gitrepo/resources/fonts/comic.ttf"},
"default": {}}
# Amount of last builds cleanartifacts.py should keep
CLEAN_ARTIFACT_KEEP_COUNT = 2

View file

@ -0,0 +1,107 @@
#!/bin/env python3
import re
import sys
import os
import subprocess
# Example invocation:
# python3 patchbisect.py /home/ave/workbench/ctc/com.discord-968
# This is really bad code that I wrote between 3am and 6am
# Please do not criticize or judge my programming skills by this.
apk_folder = sys.argv[1]
cutthecord_folder = os.path.dirname(os.path.realpath(__file__))
def apply_patch(patch, reverse=False):
patch_path = os.path.join(cutthecord_folder, "resources/patches", patch,
f"{to_versioncode}.patch")
with open(patch_path) as f:
patch_contents = f.read()
cmd = f"patch -p1 {'-R' if reverse else ''} --no-backup-if-mismatch --force"
subprocess.run(cmd,
shell=True, input=patch_contents, text=True,
cwd=apk_folder, capture_output=True)
re_versioncode_yml = re.compile(r'versionCode: \'([0-9]+)\'')
re_versionname_yml = re.compile(r'versionName: (.+)$')
# Get version code and name
with open(os.path.join(apk_folder, "apktool.yml")) as f:
file_contents = f.read()
to_versioncode = re_versioncode_yml.findall(file_contents)[0]
to_versionname = re_versionname_yml.findall(file_contents)[0]
unsure = []
# Load list of patches
for patch in os.listdir(os.path.join(cutthecord_folder, "resources/patches")):
# Ignore non-dirs
if not os.path.isdir(os.path.join(cutthecord_folder, "resources/patches", patch)):
continue
patch_path = os.path.join(cutthecord_folder, "resources/patches", patch,
f"{to_versioncode}.patch")
# Check if patch exists for from_version, if it doesn't, warn user
if not os.path.isfile(patch_path) and patch not in ["necessary"]:
# Don't warn on instructional patches
if patch not in ["customfont", "customring",
"bettertm", "bettertmlight",
"blobs"]:
print(f"SKIPPED: No {to_versionname} version found for {patch}.")
continue
# Append patch name to the list
unsure.append(patch)
failcount = 1
applied = []
good = []
bad = []
while unsure:
print("So far...")
print(f"Unsure patches: {', '.join(unsure)}")
print(f"Good patches: {', '.join(good)}")
print(f"Bad patches: {', '.join(bad)}")
count_this_round = int(len(unsure) / failcount)
for i in range(0, count_this_round):
patch_name = unsure[i]
print(f"Applying: {patch_name}")
apply_patch(patch_name)
applied.append(patch_name)
# Very cursed lines of code.
is_working = ""
while is_working not in ["y", "n"]:
is_working = input("Is the current patchset working? (y/n) ")
# <3 3.8
# while (is_working := input("Is the current patchset working? (y/n) ")) not in ["y", "n"]:
# continue
if is_working == "y":
good.extend(applied)
unsure = list(set(unsure) - set(applied))
failcount = 1
elif count_this_round > 1:
failcount += 1
else:
bad.extend(applied)
unsure = list(set(unsure) - set(applied))
failcount = 1
for patch_name in applied:
apply_patch(patch_name, True)
applied.clear()
print("Done, all patches identified as good or bad.")
print(f"Good: {', '.join(good)}")
print(f"Bad: {', '.join(bad)}")

View file

@ -0,0 +1,262 @@
#!/bin/env python3
import re
import sys
import os
import json
import subprocess
import datetime
import shutil
# Example invocation:
# python3 patchport.py /home/ave/apks/com.discord-900/
with open("/opt/ctc/gitrepo/resources/patchport-state.json", "r") as f:
jin = json.load(f)
from_versioncode = jin["versioncode"]
from_versionname = jin["versionname"]
apk_folder = sys.argv[1]
cutthecord_folder = os.path.dirname(os.path.realpath(__file__))
debug = False
tmp_folder = "/tmp/patchport"
if debug:
print(f"ctc folder: {cutthecord_folder}")
def modify_patch(patch_name, patch_path):
with open(patch_path) as f:
patch_content = f.read()
if patch_name in ["branding", "customversion"]:
patch_content = patch_content.replace(from_versioncode, to_versioncode)
patch_content = patch_content.replace(from_versionname, to_versionname)
if patch_name == "notrack":
# TODO: There's a risk here that we'll replace the nulled value
from_crashlytics_id = re_crashlytics.findall(patch_content)[0]
patch_content = patch_content.replace(from_crashlytics_id,
to_crashlytics_id)
return patch_content
def apply_patch(patch_contents):
subprocess.run("patch -p1 --no-backup-if-mismatch --force",
shell=True, input=patch_contents, text=True,
cwd=apk_folder, capture_output=True)
def fix_offset(patch_contents):
# OH GOD OH FUCK
shutil.rmtree(tmp_folder, ignore_errors=True)
os.makedirs(tmp_folder)
patch_lines = patch_contents.splitlines()
for line in patch_lines:
if "diff -crB" in line:
patch_target = line.split(" ")[2].replace("from/", "")
if not os.path.exists(os.path.join(apk_folder, patch_target)):
return False
os.makedirs(os.path.dirname(os.path.join(tmp_folder, patch_target)), exist_ok=True)
shutil.copy(os.path.join(apk_folder, patch_target), os.path.join(tmp_folder, patch_target))
# shutil.copytree(apk_folder, tmp_folder)
subprocess.run("patch -p1 --no-backup-if-mismatch --force",
shell=True, input=patch_contents, text=True,
cwd=tmp_folder, capture_output=True)
out = subprocess.run(f"diff -crB {apk_folder} {tmp_folder}",
shell=True, input=patch_contents, text=True,
cwd=tmp_folder, capture_output=True)
shutil.rmtree(tmp_folder, ignore_errors=True)
actual_difflines = []
for line in out.stdout.splitlines():
if line[0:4] != "Only":
actual_difflines.append(line)
patch_out = ("\n".join(actual_difflines)+"\n").replace(apk_folder, "from").replace(tmp_folder, "to")
return patch_out
def make_necessary(version_name, version_code):
# OH GOD OH FUCK
shutil.rmtree(tmp_folder, ignore_errors=True)
os.makedirs(tmp_folder)
shutil.copy(os.path.join(apk_folder, "AndroidManifest.xml"), os.path.join(tmp_folder, "AndroidManifest.xml"))
# Set version code and name
# Due to https://github.com/iBotPeaches/Apktool/issues/2046
# Code based on https://stackoverflow.com/a/4128192/3286892
with open(os.path.join(tmp_folder, "AndroidManifest.xml")) as fin:
filec = fin.read()
incorrect_versioncode = re_versioncode_xml.findall(filec)[0]
filec = filec.replace(incorrect_versioncode,
f'platformBuildVersionCode="{to_versioncode}"')
incorrect_versionname = re_versionname_xml.findall(filec)[0]
filec = filec.replace(incorrect_versionname,
f'platformBuildVersionName="{to_versionname}"')
with open(os.path.join(tmp_folder, "AndroidManifest.xml"), "w") as fout:
fout.write(filec)
out = subprocess.run(f"diff -crB {apk_folder} {tmp_folder}",
shell=True, text=True,
cwd=tmp_folder, capture_output=True)
shutil.rmtree(tmp_folder, ignore_errors=True)
actual_difflines = []
for line in out.stdout.splitlines():
if line[0:4] != "Only":
actual_difflines.append(line)
patch_out = ("\n".join(actual_difflines)+"\n").replace(apk_folder, "from").replace(tmp_folder, "to")
return patch_out
re_versioncode_xml = re.compile(r'(platformBuildVersionCode="[0-9]+")')
re_versionname_xml = re.compile(r'(platformBuildVersionName="[0-9a-z.]+")')
re_versioncode_yml = re.compile(r'versionCode: \'([0-9]+)\'')
re_versionname_yml = re.compile(r'versionName: \'?(.+?)\'?$')
re_releasedate = re.compile(r'released on ([0-9]{4}-[0-9]{2}-[0-9]{2})')
re_crashlytics = re.compile(r'com\.crashlytics\.android\.build_id">([a-z0-9]'
r'{8}-?[a-z0-9]{4}-?[a-z0-9]{4}-?[a-z0-9]{4}-?'
r'[a-z0-9]{12})</string>')
# Get version code and name
with open(os.path.join(apk_folder, "apktool.yml")) as f:
file_contents = f.read()
to_versioncode = re_versioncode_yml.findall(file_contents)[0]
to_versionname = re_versionname_yml.findall(file_contents)[0]
# Get crashlytics build ID
with open(os.path.join(apk_folder, "res", "values", "strings.xml")) as f:
file_contents = f.read()
to_crashlytics_id = re_crashlytics.findall(file_contents)[0]
failures = []
for patch in os.listdir(os.path.join(cutthecord_folder, "resources/patches")):
if debug:
print(f"going over patch: {patch}")
# Ignore non-dirs
if not os.path.isdir(os.path.join(cutthecord_folder, "resources/patches", patch)):
if debug:
print(f"patch is not a folder, skipping: {patch}")
continue
pre_in_path = os.path.join(cutthecord_folder, "resources/patches", patch,
f"{from_versioncode}-pre.sh")
post_in_path = os.path.join(cutthecord_folder, "resources/patches", patch,
f"{from_versioncode}-post.sh")
pre_out_path = os.path.join(cutthecord_folder, "resources/patches", patch,
f"{to_versioncode}-pre.sh")
post_out_path = os.path.join(cutthecord_folder, "resources/patches", patch,
f"{to_versioncode}-post.sh")
patch_path = os.path.join(cutthecord_folder, "resources/patches", patch,
f"{from_versioncode}.patch")
out_path = os.path.join(cutthecord_folder, "resources/patches", patch,
f"{to_versioncode}.patch")
readme_path = os.path.join(cutthecord_folder, "resources/patches", patch, "README.md")
# Handle copying of versioned scripts, untested and dirty!
script_path = os.path.join(cutthecord_folder, "resources/patches", patch,
f"{from_versioncode}.sh")
if os.path.exists(script_path):
script_out_path = os.path.join(cutthecord_folder, "resources/patches", patch,
f"{to_versioncode}.sh")
with open(script_path) as f:
with open(script_out_path, "w") as f2:
f2.write(f.read())
# Check if patch exists for from_version, if it doesn't, warn user
if not os.path.isfile(patch_path) and patch not in ["necessary"]:
# Don't warn on instructional patches
if patch not in ["customfont", "customring",
"bettertm", "bettertmlight",
"blobs"]:
print(f"SKIPPED: No {from_versionname} version found for {patch}.")
continue
# Check if pre-script exists, if it does copy it
if os.path.isfile(pre_in_path):
shutil.copyfile(pre_in_path, pre_out_path)
print(f"PRE COPIED: {patch}'s pre script was copied.")
# Check if post-script exists, if it does copy it
if os.path.isfile(post_in_path):
shutil.copyfile(post_in_path, post_out_path)
print(f"POST COPIED: {patch}'s post script was copied.")
# Create necessary instead of porting it.
if patch == "necessary":
patch_contents = make_necessary(to_versioncode, to_versionname)
else:
# Get a modified version of the patch
patch_contents = modify_patch(patch, patch_path)
# Pass the new patch to patch command and get it to attempt to patch
out = subprocess.run("patch -p1 --dry-run --force", shell=True,
cwd=apk_folder, input=patch_contents, text=True,
capture_output=True)
# Check for issues
if "FAILED" in out.stdout or "can't find file to patch" in out.stdout:
print(f"FAILED: {patch} failed, please fix by hand.")
failures.append(patch)
out_path += "-failed"
elif "offset" in out.stdout:
temp_patch_contents = fix_offset(patch_contents)
if temp_patch_contents:
patch_contents = temp_patch_contents
print(f"WARNING: {patch} has offsets which were auto corrected.")
else:
print(f"FAILED: {patch} is missing files, please fix by hand.")
failures.append(patch)
out_path += "-failed"
if debug:
print(out.stdout)
# Apply patch to main APK folder too
if patch in ["necessary"]:
apply_patch(patch_contents)
if from_versionname != to_versionname:
# Add supported version to readme of that patch, hacky
# https://stackoverflow.com/a/35130508/3286892
with open(readme_path, 'r') as f:
readme_text = f.read().replace(f'- {from_versionname}\n',
f'- {from_versionname}\n'
f'- {to_versionname}\n')
with open(readme_path, "w") as f:
f.write(readme_text)
# Save ported patch
with open(out_path, "w") as f:
f.write(patch_contents)
if not out_path.endswith("-failed"):
print(f"PORTED: {patch} was successfully ported.")
ctcreadme_path = os.path.join(cutthecord_folder, "README.md")
# TODO: can we pull the correct date from distok?
out_datestamp = datetime.datetime.utcnow().strftime("%Y-%m-%d")
# Update readme with latest version, hacky
# https://stackoverflow.com/a/35130508/3286892
with open(ctcreadme_path, 'r') as f:
ctcr_text = f.read().replace(f'{from_versionname} ({from_versioncode})',
f'{to_versionname} ({to_versioncode})')
in_datestamp = re_releasedate.findall(ctcr_text)[0]
ctcr_text = ctcr_text.replace(in_datestamp, out_datestamp)
with open(ctcreadme_path, "w") as f:
f.write(ctcr_text)
with open("patchport-state.json", "w") as f:
jout = {"versionname": to_versionname, "versioncode": to_versioncode}
json.dump(jout, f)
if failures:
print(f"Port complete. Following patches failed: {', '.join(failures)}")
else:
print("Port complete. All patches completed successfully.")