From 9bbc3816bcf8d58162025439aa6e962b4f4b601b Mon Sep 17 00:00:00 2001 From: F33RNI Date: Fri, 19 Jul 2024 16:08:21 +0300 Subject: [PATCH] feat: run-before + fix relover --- README.md | 45 +++++++++++++-------- src/mml/_version.py | 2 +- src/mml/main.py | 81 +++++++++++++++++-------------------- src/mml/resolve_artifact.py | 38 ++++++++++++----- 4 files changed, 95 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index f8b7ab1..6268bc5 100644 --- a/README.md +++ b/README.md @@ -44,13 +44,13 @@ ### 📄 CLI usage ```text -usage: micro-minecraft-launcher-1.1.dev7-linux-x86_64 [-h] [-c CONFIG] [-d GAME_DIR] [-l] [-u USER] [--auth-uuid AUTH_UUID] - [--auth-access-token AUTH_ACCESS_TOKEN] [--user-type USER_TYPE] [-i] - [--java-path JAVA_PATH] [-e KEY=VALUE [KEY=VALUE ...]] [-j JVM_ARGS] - [-g GAME_ARGS] [--resolver-processes RESOLVER_PROCESSES] [--write-profiles] - [--install-forge INSTALL_FORGE] - [--delete-files DELETE_FILES [DELETE_FILES ...]] [--verbose] [--version] - [id] +usage: micro-minecraft-launcher-1.2.dev-linux-x86_64 [-h] [-c CONFIG] [-d GAME_DIR] [-l] [-u USER] [--auth-uuid AUTH_UUID] + [--auth-access-token AUTH_ACCESS_TOKEN] [--user-type USER_TYPE] [-i] + [--java-path JAVA_PATH] [-e KEY=VALUE [KEY=VALUE ...]] [-j JVM_ARGS] + [-g GAME_ARGS] [--resolver-processes RESOLVER_PROCESSES] [--write-profiles] + [--run-before RUN_BEFORE [RUN_BEFORE ...]] + [--delete-files DELETE_FILES [DELETE_FILES ...]] [--verbose] [--version] + [id] Simple cross-platform cli launcher for Minecraft @@ -91,15 +91,14 @@ options: file --resolver-processes RESOLVER_PROCESSES number of processes to resolve (download, copy and unpack) files(Default: 4) - --write-profiles write all found local versions into game_dir/launcher_profiles.json (useful for installing Forge) - --install-forge INSTALL_FORGE - run specified path to forge installer (.jar file) with --installClient game_dir NOTE: Consider adding - --write-profiles argument NOTE: Consider adding --delete-files forge*installer.jar argument NOTE: Will - download JRE / JDK 17 + --write-profiles write all found local versions into game_dir/launcher_profiles.json (useful for installing Forge/Fabric) + --run-before RUN_BEFORE [RUN_BEFORE ...] + run specified command before launching game (ex.: --run-before java -jar forge_installer.jar --installClient + .) NOTE: Consider adding --write-profiles argument NOTE: Consider adding --delete-files forge*installer.jar + argument NOTE: Will download JRE / JDK 17 if first argument is "java" and replace it with local java path --delete-files DELETE_FILES [DELETE_FILES ...] delete files before launching minecraft. Uses glob to find files (Ex.: --delete-files "forge*installer.jar" - "hs_err_pid*.log") NOTE: Consider adding --write-profiles argument NOTE: Consider adding --delete-forge- - installer argument NOTE: Will download JRE / JDK 17 + "hs_err_pid*.log") --verbose debug logs --version show launcher's version number and exit @@ -111,7 +110,7 @@ examples: micro-minecraft-launcher -d /path/to/custom/minecraft -j="-Xmx6G" -g="--server 192.168.0.1" 1.21 micro-minecraft-launcher -j="-Xmx4G" -g="--width 800 --height 640" 1.18.2 micro-minecraft-launcher --write-profiles - micro-minecraft-launcher --write-profiles --install-forge forge-1.18.2-40.2.4-installer.jar --delete-files forge*.jar + micro-minecraft-launcher --write-profiles --run-before java -jar forge-1.18.2-40.2.4-installer.jar --delete-files forge*.jar ``` > ⚠️ NOTE: CLI arguments will overwrite config @@ -170,7 +169,13 @@ examples: ], "resolver_processes": 4 (number of processes to download / copy / unpack files), "write_profiles": true (write all found local versions into game_dir/launcher_profiles.json), - "install_forge": "path/to/forge-...-installer.jar", + "run_before": [ + "java", + "-jar", + "path/to/forge-...-installer.jar", + "--installClient", + "." + ] "delete_files": [ "any file patterns to delete (for glob)", ... @@ -207,7 +212,13 @@ examples: "530" ], "write_profiles": true, - "install_forge": "forge-1.18.2-40.2.4-installer.jar", + "run_before": [ + "java", + "-jar", + "forge-1.18.2-40.2.4-installer.jar", + "--installClient", + "." + ], "delete_files": [ "forge-1.18.2-40.2.4-installer*", "hs_err_pid*.log" diff --git a/src/mml/_version.py b/src/mml/_version.py index 4f7e4e8..e9b4096 100644 --- a/src/mml/_version.py +++ b/src/mml/_version.py @@ -15,7 +15,7 @@ If not, see . """ -__version__ = "1.1.rc2" +__version__ = "1.2.dev" # For comparing with "minimumLauncherVersion" LAUNCHER_VERSION = 21 diff --git a/src/mml/main.py b/src/mml/main.py index 4a8f1dd..fabbe44 100644 --- a/src/mml/main.py +++ b/src/mml/main.py @@ -51,7 +51,7 @@ micro-minecraft-launcher -d /path/to/custom/minecraft -j="-Xmx6G" -g="--server 192.168.0.1" 1.21 micro-minecraft-launcher -j="-Xmx4G" -g="--width 800 --height 640" 1.18.2 micro-minecraft-launcher --write-profiles - micro-minecraft-launcher --write-profiles --install-forge forge-1.18.2-40.2.4-installer.jar --delete-files forge*.jar + micro-minecraft-launcher --write-profiles --run-before java -jar forge-1.18.2-40.2.4-installer.jar --delete-files forge*.jar """ LAUNCHER_PROFILES_FILE = "launcher_profiles.json" @@ -187,27 +187,24 @@ def parse_args() -> argparse.Namespace: "--write-profiles", action="store_true", default=False, - help="write all found local versions into game_dir/launcher_profiles.json (useful for installing Forge)", + help="write all found local versions into game_dir/launcher_profiles.json (useful for installing Forge/Fabric)", ) parser.add_argument( - "--install-forge", - type=str, + "--run-before", + nargs="+", required=False, - default=None, - help="run specified path to forge installer (.jar file) with --installClient game_dir" + help="run specified command before launching game" + " (ex.: --run-before java -jar forge_installer.jar --installClient .)" " NOTE: Consider adding --write-profiles argument" " NOTE: Consider adding --delete-files forge*installer.jar argument" - " NOTE: Will download JRE / JDK 17", + ' NOTE: Will download JRE / JDK 17 if first argument is "java" and replace it with local java path', ) parser.add_argument( "--delete-files", nargs="+", required=False, help="delete files before launching minecraft. Uses glob to find files" - ' (Ex.: --delete-files "forge*installer.jar" "hs_err_pid*.log")' - " NOTE: Consider adding --write-profiles argument" - " NOTE: Consider adding --delete-forge-installer argument" - " NOTE: Will download JRE / JDK 17", + ' (Ex.: --delete-files "forge*installer.jar" "hs_err_pid*.log")', ) parser.add_argument( "id", @@ -356,63 +353,61 @@ def write_profiles(game_dir: str, versions: List[Dict]) -> None: json.dump(launcher_profiles, launcher_profiles_io, ensure_ascii=False, indent=4) -def install_forge(forge_path: str, game_dir: str) -> bool: - """Installs forge +def run_before(command: List[str], cwd: str) -> bool: + """Runs custom command before launching game + (Will install Java 17 if first argument is "java" and replace it with locally installed java) Args: - forge_path (str): path to forge installer (.jar) - game_dir (str): path to .minecraft (where to install forge) + command (List[str]): command and all arguments + cwd (str): path to .minecraft Raises: Exception: java error or interrupted Returns: - bool: if installer process finished without interrupting + bool: if process finished without interrupting (will not check for process exit code) """ - if not os.path.exists(forge_path): - logging.warning(f"Skipping forge installation. Path {forge_path} doesn't exist") - return False - - forge_java = jdk_check_install(version=17) - if not forge_java: - raise Exception("Unable to install Java for installing Forge") - - installer_cmd = [forge_java, "-jar", forge_path, "--installClient", game_dir] - logging.info(f"Installing forge using command: {' '.join(installer_cmd)}") - installer_process = subprocess.Popen( - installer_cmd, + if command[0] == "java": + java_ = jdk_check_install(version=17) + if not java_: + raise Exception("Unable to install Java for --run-before") + command[0] = java_ + + logging.info(f"Running: {' '.join(command)}") + process = subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False, - cwd=game_dir, + cwd=cwd, ) # Redirect logs and capture CTRL+C try: - while installer_process.poll() is None: + while process.poll() is None: # Read logs from STOUT (blocking) - installer_stdout = installer_process.stdout.readline() - if not installer_stdout: + stdout = process.stdout.readline() + if not stdout: continue # Redirect log - log_line = installer_stdout.decode("utf-8", errors="replace").strip() - logging.info(f"[Forge installer] {log_line}") + log_line = stdout.decode("utf-8", errors="replace").strip() + logging.info(f"[Run before] {log_line}") except (SystemExit, KeyboardInterrupt) as e: - logging.warning("Interrupted! Killing forge installer") - if installer_process.poll() is None: + logging.warning("Interrupted! Killing run-before process") + if process.poll() is None: try: - installer_process.kill() + process.kill() except Exception as e_: - logging.warning(f"Unable to kill forge installer process: {e_}") + logging.warning(f"Unable to kill process: {e_}") logging.debug("Error details", exc_info=e_) # Re-raise interrupt raise e # Installer stopped - logging.info("Forge installer process stopped") + logging.info("run-before process stopped") return True @@ -492,10 +487,10 @@ def main(): if args.write_profiles or config_manager_.get("write_profiles", ignore_args=True): write_profiles(game_dir, versions) - # Install forge client - forge_path = config_manager_.get("install_forge") - if forge_path: - if install_forge(forge_path, game_dir): + # Run custom command + run_before_cmd = config_manager_.get("run_before") + if run_before_cmd: + if run_before(run_before_cmd, game_dir): # Update profiles versions = profile_parser_.parse_versions() if args.write_profiles or config_manager_.get("write_profiles"): diff --git a/src/mml/resolve_artifact.py b/src/mml/resolve_artifact.py index 8bc0007..164ab26 100644 --- a/src/mml/resolve_artifact.py +++ b/src/mml/resolve_artifact.py @@ -106,13 +106,22 @@ def resolve_artifact(artifact_: Artifact, _attempt: int = 0) -> str or None: logging.debug("Artifact downloaded successfully") - unpack_copy(artifact_, artifact_path) + if not unpack_copy(artifact_, artifact_path): + return None return artifact_path -def unpack_copy(artifact_: Artifact, artifact_path: str) -> None: - """Unpacks and copies artifact if needed""" +def unpack_copy(artifact_: Artifact, artifact_path: str) -> bool: + """Unpacks and copies artifact if needed + + Args: + artifact_ (Artifact): artifact instance + artifact_path (str): path to downloaded artifact + + Returns: + bool: False in case of error + """ # Unpack it if needed without some files if artifact_.unpack_into: logging.debug(f"Unpacking {artifact_path} into {artifact_.unpack_into}") @@ -131,14 +140,23 @@ def unpack_copy(artifact_: Artifact, artifact_path: str) -> None: except Exception as e: logging.error(f"Unable to unpack {artifact_path}: {e}") logging.debug("Error details", exc_info=e) - return None + return False # Copy if needed if artifact_.copy_to and not os.path.exists(artifact_.copy_to): - copy_to_dir = os.path.dirname(artifact_.copy_to) - if not os.path.exists(copy_to_dir): - logging.debug(f"Creating {copy_to_dir} directory") - os.makedirs(copy_to_dir, exist_ok=True) + try: + copy_to_dir = os.path.dirname(artifact_.copy_to) + if not os.path.exists(copy_to_dir): + logging.debug(f"Creating {copy_to_dir} directory") + os.makedirs(copy_to_dir, exist_ok=True) + + logging.debug(f"Copying {artifact_path} into {artifact_.copy_to}") + shutil.copyfile(artifact_path, artifact_.copy_to) + + except Exception as e: + logging.error(f"Unable to copy {artifact_path} into {artifact_.copy_to}: {e}") + logging.debug("Error details", exc_info=e) + return False - logging.debug(f"Copying {artifact_path} into {artifact_.copy_to}") - shutil.copyfile(artifact_path, artifact_.copy_to) + # Seems OK + return True