Skip to content

Commit

Permalink
feat: run-before + fix relover
Browse files Browse the repository at this point in the history
  • Loading branch information
F33RNI committed Jul 19, 2024
1 parent 0708f15 commit 9bbc381
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 71 deletions.
45 changes: 28 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)",
...
Expand Down Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/mml/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
If not, see <http://www.gnu.org/licenses/>.
"""

__version__ = "1.1.rc2"
__version__ = "1.2.dev"

# For comparing with "minimumLauncherVersion"
LAUNCHER_VERSION = 21
81 changes: 38 additions & 43 deletions src/mml/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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"):
Expand Down
38 changes: 28 additions & 10 deletions src/mml/resolve_artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand All @@ -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

0 comments on commit 9bbc381

Please sign in to comment.