diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 17:36:40 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 17:36:40 +0300 |
| commit | 1d4bf6e8b1208383021e22f61d859e9b721c482d (patch) | |
| tree | 21c8e0c4350d7c44d817243ebf409be625ea4f21 /launcher/minecraft/launch/VerifyJavaInstall.cpp | |
| parent | 38c57e163abad6216f7e0f44d401c362c5fd5da6 (diff) | |
| download | Project-Tick-1d4bf6e8b1208383021e22f61d859e9b721c482d.tar.gz Project-Tick-1d4bf6e8b1208383021e22f61d859e9b721c482d.zip | |
NOISSUE reformat MeshMC to new clang format rules and fixed macOS portability issue and java find issue
Signed-off-by: Mehmet Samet Duman <yongdohyun@projecttick.org>
Diffstat (limited to 'launcher/minecraft/launch/VerifyJavaInstall.cpp')
| -rw-r--r-- | launcher/minecraft/launch/VerifyJavaInstall.cpp | 536 |
1 files changed, 286 insertions, 250 deletions
diff --git a/launcher/minecraft/launch/VerifyJavaInstall.cpp b/launcher/minecraft/launch/VerifyJavaInstall.cpp index 3368bfa8c1..0f58b5efa4 100644 --- a/launcher/minecraft/launch/VerifyJavaInstall.cpp +++ b/launcher/minecraft/launch/VerifyJavaInstall.cpp @@ -23,9 +23,11 @@ #include <QDir> #include <QDirIterator> +#include <QFileInfo> #include <QJsonDocument> #include <QJsonObject> #include <QJsonArray> +#include <QProcess> #include <QRegularExpression> #include <launch/LaunchTask.h> @@ -33,306 +35,340 @@ #include <minecraft/PackProfile.h> #include <minecraft/VersionFilterData.h> +#include "Application.h" #include "FileSystem.h" #include "Json.h" #include "java/JavaUtils.h" #ifndef MeshMC_DISABLE_JAVA_DOWNLOADER -#include "Application.h" #include "BuildConfig.h" #include "net/Download.h" #endif #ifdef major - #undef major +#undef major #endif #ifdef minor - #undef minor +#undef minor #endif +namespace +{ + std::optional<JavaVersion> probeJavaVersion(const QString& javaPath) + { + const auto checkerJar = + FS::PathCombine(APPLICATION->getJarsPath(), "JavaCheck.jar"); + if (!QFileInfo::exists(checkerJar)) { + return std::nullopt; + } + + QProcess process; + process.setProgram(javaPath); + process.setArguments({"-jar", checkerJar}); + process.setProcessEnvironment(CleanEnviroment()); + process.setProcessChannelMode(QProcess::SeparateChannels); + process.start(); + + if (!process.waitForFinished(15000) || + process.exitStatus() != QProcess::NormalExit || + process.exitCode() != 0) { + return std::nullopt; + } + + const auto stdoutData = + QString::fromLocal8Bit(process.readAllStandardOutput()); + const auto lines = stdoutData.split('\n', Qt::SkipEmptyParts); + for (const auto& rawLine : lines) { + const auto line = rawLine.trimmed(); + if (!line.startsWith("java.version=")) { + continue; + } + return JavaVersion(line.mid(QString("java.version=").size())); + } + + return std::nullopt; + } +} // namespace + int VerifyJavaInstall::determineRequiredJavaMajor() const { - auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); - auto minecraftComponent = m_inst->getPackProfile()->getComponent("net.minecraft"); - - if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java25BeginsDate) - return 25; - if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java21BeginsDate) - return 21; - if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java17BeginsDate) - return 17; - if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java16BeginsDate) - return 16; - if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java8BeginsDate) - return 8; - return 0; + auto m_inst = + std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); + auto minecraftComponent = + m_inst->getPackProfile()->getComponent("net.minecraft"); + + if (minecraftComponent->getReleaseDateTime() >= + g_VersionFilterData.java25BeginsDate) + return 25; + if (minecraftComponent->getReleaseDateTime() >= + g_VersionFilterData.java21BeginsDate) + return 21; + if (minecraftComponent->getReleaseDateTime() >= + g_VersionFilterData.java17BeginsDate) + return 17; + if (minecraftComponent->getReleaseDateTime() >= + g_VersionFilterData.java16BeginsDate) + return 16; + if (minecraftComponent->getReleaseDateTime() >= + g_VersionFilterData.java8BeginsDate) + return 8; + return 0; } QString VerifyJavaInstall::javaInstallDir() const { - return FS::PathCombine(QDir::currentPath(), "java"); + return JavaUtils::managedJavaRoot(); } QString VerifyJavaInstall::findInstalledJava(int requiredMajor) const { -#if defined(Q_OS_WIN) - QString binaryName = "javaw.exe"; -#else - QString binaryName = "java"; -#endif - - // Pattern matching Java major version in directory names - // Matches: "java-8-openjdk", "jdk-17", "java-21-openjdk-amd64", "zulu25.30.31-ca-jdk25.0.0-linux_x64" - QRegularExpression re(QString("(?:jdk|java|jre)[-.]?%1(?:[^0-9]|$)").arg(requiredMajor)); - - // 1. Scan MeshMC's managed java/{vendor}/{version}/bin/java directory - QString javaBaseDir = javaInstallDir(); - QDir baseDir(javaBaseDir); - if (baseDir.exists()) { - QDirIterator vendorIt(javaBaseDir, QDir::Dirs | QDir::NoDotAndDotDot); - while (vendorIt.hasNext()) { - vendorIt.next(); - QString vendorPath = vendorIt.filePath(); - - QDirIterator versionIt(vendorPath, QDir::Dirs | QDir::NoDotAndDotDot); - while (versionIt.hasNext()) { - versionIt.next(); - QString versionPath = versionIt.filePath(); - - QDirIterator binIt(versionPath, QStringList() << binaryName, - QDir::Files, QDirIterator::Subdirectories); - while (binIt.hasNext()) { - binIt.next(); - QString javaPath = binIt.filePath(); - if (!javaPath.contains("/bin/")) - continue; - QString dirName = versionIt.fileName().toLower(); - if (re.match(dirName).hasMatch()) - return javaPath; - } - } - } - } - - // 2. Scan system-installed Java paths - // Use JavaUtils to discover all Java installations on the system, then - // check if any match the required major version by parsing the path. - JavaUtils javaUtils; - QList<QString> systemJavas = javaUtils.FindJavaPaths(); - for (const QString &javaPath : systemJavas) { - // Resolve to absolute path and verify it exists - QString resolved = FS::ResolveExecutable(javaPath); - if (resolved.isEmpty()) - continue; - - // Extract the parent directory components to check for version info - // Typical paths: - // /usr/lib/jvm/java-8-openjdk/bin/java - // /usr/lib/jvm/java-8-openjdk/jre/bin/java - // /usr/lib64/jvm/java-17-openjdk/bin/java - // /opt/jdk/jdk-21/bin/java - QFileInfo fi(resolved); - QString fullPath = fi.absoluteFilePath().toLower(); - - // Check if any component in the path matches the required Java version - QStringList parts = fullPath.split('/'); - for (const QString &part : parts) { - if (re.match(part).hasMatch()) { - return resolved; - } - } - } - - return {}; + JavaUtils javaUtils; + QList<QString> systemJavas = javaUtils.FindJavaPaths(); + QSet<QString> seenPaths; + for (const QString& javaPath : systemJavas) { + QString resolved = FS::ResolveExecutable(javaPath); + if (resolved.isEmpty() || seenPaths.contains(resolved)) + continue; + + seenPaths.insert(resolved); + const auto version = probeJavaVersion(resolved); + if (version.has_value() && version->major() >= requiredMajor) { + return resolved; + } + } + + return {}; } -void VerifyJavaInstall::executeTask() { - auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); - - auto javaVersion = m_inst->getJavaVersion(); - int requiredMajor = determineRequiredJavaMajor(); - - // No Java requirement or already met - if (requiredMajor == 0 || javaVersion.major() >= requiredMajor) { - emitSucceeded(); - return; - } - - // Java version insufficient — try to find an already-downloaded one - emit logLine(tr("Current Java version %1 does not meet the requirement of Java %2.") - .arg(javaVersion.toString()).arg(requiredMajor), MessageLevel::Warning); - - QString existingJava = findInstalledJava(requiredMajor); - if (!existingJava.isEmpty()) { - emit logLine(tr("Found installed Java %1 at: %2").arg(requiredMajor).arg(existingJava), MessageLevel::MeshMC); +void VerifyJavaInstall::executeTask() +{ + auto m_inst = + std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); + + auto javaVersion = m_inst->getJavaVersion(); + int requiredMajor = determineRequiredJavaMajor(); + + // No Java requirement or already met + if (requiredMajor == 0 || javaVersion.major() >= requiredMajor) { + emitSucceeded(); + return; + } + + // Java version insufficient — try to find an already-downloaded one + emit logLine( + tr("Current Java version %1 does not meet the requirement of Java %2.") + .arg(javaVersion.toString()) + .arg(requiredMajor), + MessageLevel::Warning); + + QString existingJava = findInstalledJava(requiredMajor); + if (!existingJava.isEmpty()) { + emit logLine(tr("Found installed Java %1 at: %2") + .arg(requiredMajor) + .arg(existingJava), + MessageLevel::MeshMC); #ifndef MeshMC_DISABLE_JAVA_DOWNLOADER - setJavaPathAndSucceed(existingJava); + setJavaPathAndSucceed(existingJava); #else - m_inst->settings()->set("OverrideJavaLocation", true); - m_inst->settings()->set("JavaPath", existingJava); - emit logLine(tr("Java path set to: %1").arg(existingJava), MessageLevel::MeshMC); - emitSucceeded(); + m_inst->settings()->set("OverrideJavaLocation", true); + m_inst->settings()->set("JavaPath", existingJava); + emit logLine(tr("Java path set to: %1").arg(existingJava), + MessageLevel::MeshMC); + emitSucceeded(); #endif - return; - } + return; + } #ifndef MeshMC_DISABLE_JAVA_DOWNLOADER - // Not found — auto-download - emit logLine(tr("No installed Java %1 found. Downloading...").arg(requiredMajor), MessageLevel::MeshMC); - autoDownloadJava(requiredMajor); + // Not found — auto-download + emit logLine( + tr("No installed Java %1 found. Downloading...").arg(requiredMajor), + MessageLevel::MeshMC); + autoDownloadJava(requiredMajor); #else - emitFailed(tr("Java %1 is required but not installed. Please install it manually.").arg(requiredMajor)); + emitFailed( + tr("Java %1 is required but not installed. Please install it manually.") + .arg(requiredMajor)); #endif } #ifndef MeshMC_DISABLE_JAVA_DOWNLOADER void VerifyJavaInstall::autoDownloadJava(int requiredMajor) { - // Fetch version list from net.minecraft.java (Mojang) - fetchVersionList(requiredMajor); + // Fetch version list from net.minecraft.java (Mojang) + fetchVersionList(requiredMajor); } void VerifyJavaInstall::fetchVersionList(int requiredMajor) { - m_fetchData.clear(); - QString uid = "net.minecraft.java"; - QString url = QString("%1%2/index.json").arg(BuildConfig.META_URL, uid); - - m_fetchJob = new NetJob(tr("Fetch Java versions"), APPLICATION->network()); - auto dl = Net::Download::makeByteArray(QUrl(url), &m_fetchData); - m_fetchJob->addNetAction(dl); - - connect(m_fetchJob.get(), &NetJob::succeeded, this, [this, uid, requiredMajor]() { - m_fetchJob.reset(); - - QJsonDocument doc; - try { - doc = Json::requireDocument(m_fetchData); - } catch (const Exception &e) { - emitFailed(tr("Failed to parse Java version list from meta server: %1").arg(e.cause())); - return; - } - if (!doc.isObject()) { - emitFailed(tr("Failed to parse Java version list from meta server.")); - return; - } - - auto versions = JavaDownload::parseVersionIndex(doc.object(), uid); - - // Find the matching version (e.g., "java25" for requiredMajor=25) - QString targetVersionId = QString("java%1").arg(requiredMajor); - bool found = false; - for (const auto &ver : versions) { - if (ver.versionId == targetVersionId) { - found = true; - fetchRuntimes(ver.versionId, requiredMajor); - return; - } - } - - if (!found) { - emitFailed(tr("Java %1 is not available for download from Mojang. Please install it manually.") - .arg(requiredMajor)); - } - }); - - connect(m_fetchJob.get(), &NetJob::failed, this, [this, requiredMajor](QString reason) { - emitFailed(tr("Failed to fetch Java version list: %1. Please install Java %2 manually.") - .arg(reason).arg(requiredMajor)); - m_fetchJob.reset(); - }); - - m_fetchJob->start(); + m_fetchData.clear(); + QString uid = "net.minecraft.java"; + QString url = QString("%1%2/index.json").arg(BuildConfig.META_URL, uid); + + m_fetchJob = new NetJob(tr("Fetch Java versions"), APPLICATION->network()); + auto dl = Net::Download::makeByteArray(QUrl(url), &m_fetchData); + m_fetchJob->addNetAction(dl); + + connect( + m_fetchJob.get(), &NetJob::succeeded, this, + [this, uid, requiredMajor]() { + m_fetchJob.reset(); + + QJsonDocument doc; + try { + doc = Json::requireDocument(m_fetchData); + } catch (const Exception& e) { + emitFailed( + tr("Failed to parse Java version list from meta server: %1") + .arg(e.cause())); + return; + } + if (!doc.isObject()) { + emitFailed( + tr("Failed to parse Java version list from meta server.")); + return; + } + + auto versions = JavaDownload::parseVersionIndex(doc.object(), uid); + + // Find the matching version (e.g., "java25" for requiredMajor=25) + QString targetVersionId = QString("java%1").arg(requiredMajor); + bool found = false; + for (const auto& ver : versions) { + if (ver.versionId == targetVersionId) { + found = true; + fetchRuntimes(ver.versionId, requiredMajor); + return; + } + } + + if (!found) { + emitFailed(tr("Java %1 is not available for download from " + "Mojang. Please install it manually.") + .arg(requiredMajor)); + } + }); + + connect(m_fetchJob.get(), &NetJob::failed, this, + [this, requiredMajor](QString reason) { + emitFailed(tr("Failed to fetch Java version list: %1. Please " + "install Java %2 manually.") + .arg(reason) + .arg(requiredMajor)); + m_fetchJob.reset(); + }); + + m_fetchJob->start(); } -void VerifyJavaInstall::fetchRuntimes(const QString &versionId, int requiredMajor) +void VerifyJavaInstall::fetchRuntimes(const QString& versionId, + int requiredMajor) { - m_fetchData.clear(); - QString uid = "net.minecraft.java"; - QString url = QString("%1%2/%3.json").arg(BuildConfig.META_URL, uid, versionId); - - m_fetchJob = new NetJob(tr("Fetch Java runtime details"), APPLICATION->network()); - auto dl = Net::Download::makeByteArray(QUrl(url), &m_fetchData); - m_fetchJob->addNetAction(dl); - - connect(m_fetchJob.get(), &NetJob::succeeded, this, [this, requiredMajor]() { - auto fetchJob = std::move(m_fetchJob); - - QJsonDocument doc; - try { - doc = Json::requireDocument(m_fetchData); - } catch (const Exception &e) { - emitFailed(tr("Failed to parse Java runtime details: %1").arg(e.cause())); - return; - } - if (!doc.isObject()) { - emitFailed(tr("Failed to parse Java runtime details.")); - return; - } - - auto allRuntimes = JavaDownload::parseRuntimes(doc.object()); - QString myOS = JavaDownload::currentRuntimeOS(); - - // Filter for current platform - for (const auto &rt : allRuntimes) { - if (rt.runtimeOS == myOS) { - emit logLine(tr("Downloading %1 (%2)...") - .arg(rt.name, rt.version.toString()), MessageLevel::MeshMC); - startDownload(rt, requiredMajor); - return; - } - } - - emitFailed(tr("No Java %1 download available for your platform (%2). Please install it manually.") - .arg(requiredMajor).arg(myOS)); - }); - - connect(m_fetchJob.get(), &NetJob::failed, this, [this](QString reason) { - emitFailed(tr("Failed to fetch Java runtime details: %1").arg(reason)); - m_fetchJob.reset(); - }); - - m_fetchJob->start(); + m_fetchData.clear(); + QString uid = "net.minecraft.java"; + QString url = + QString("%1%2/%3.json").arg(BuildConfig.META_URL, uid, versionId); + + m_fetchJob = + new NetJob(tr("Fetch Java runtime details"), APPLICATION->network()); + auto dl = Net::Download::makeByteArray(QUrl(url), &m_fetchData); + m_fetchJob->addNetAction(dl); + + connect(m_fetchJob.get(), &NetJob::succeeded, this, + [this, requiredMajor]() { + auto fetchJob = std::move(m_fetchJob); + + QJsonDocument doc; + try { + doc = Json::requireDocument(m_fetchData); + } catch (const Exception& e) { + emitFailed(tr("Failed to parse Java runtime details: %1") + .arg(e.cause())); + return; + } + if (!doc.isObject()) { + emitFailed(tr("Failed to parse Java runtime details.")); + return; + } + + auto allRuntimes = JavaDownload::parseRuntimes(doc.object()); + QString myOS = JavaDownload::currentRuntimeOS(); + + // Filter for current platform + for (const auto& rt : allRuntimes) { + if (rt.runtimeOS == myOS) { + emit logLine(tr("Downloading %1 (%2)...") + .arg(rt.name, rt.version.toString()), + MessageLevel::MeshMC); + startDownload(rt, requiredMajor); + return; + } + } + + emitFailed(tr("No Java %1 download available for your platform " + "(%2). Please install it manually.") + .arg(requiredMajor) + .arg(myOS)); + }); + + connect(m_fetchJob.get(), &NetJob::failed, this, [this](QString reason) { + emitFailed(tr("Failed to fetch Java runtime details: %1").arg(reason)); + m_fetchJob.reset(); + }); + + m_fetchJob->start(); } -void VerifyJavaInstall::startDownload(const JavaDownload::RuntimeEntry &runtime, int requiredMajor) +void VerifyJavaInstall::startDownload(const JavaDownload::RuntimeEntry& runtime, + int requiredMajor) { - QString dirName = QString("%1-%2").arg(runtime.name, runtime.version.toString()); - QString targetDir = FS::PathCombine(javaInstallDir(), runtime.vendor, dirName); - - m_downloadTask = std::make_unique<JavaDownloadTask>(runtime, targetDir, this); - - connect(m_downloadTask.get(), &Task::succeeded, this, [this]() { - QString javaPath = m_downloadTask->installedJavaPath(); - - if (javaPath.isEmpty()) { - emitFailed(tr("Java was downloaded but the binary could not be found.")); - return; - } - - emit logLine(tr("Java downloaded and installed at: %1").arg(javaPath), MessageLevel::MeshMC); - setJavaPathAndSucceed(javaPath); - }); - - connect(m_downloadTask.get(), &Task::failed, this, [this, requiredMajor](const QString &reason) { - emitFailed(tr("Failed to download Java %1: %2").arg(requiredMajor).arg(reason)); - m_downloadTask.reset(); - }); - - connect(m_downloadTask.get(), &Task::status, this, [this](const QString &status) { - emit logLine(status, MessageLevel::MeshMC); - }); - - m_downloadTask->start(); + QString dirName = + QString("%1-%2").arg(runtime.name, runtime.version.toString()); + QString targetDir = + FS::PathCombine(javaInstallDir(), runtime.vendor, dirName); + + m_downloadTask = + std::make_unique<JavaDownloadTask>(runtime, targetDir, this); + + connect(m_downloadTask.get(), &Task::succeeded, this, [this]() { + QString javaPath = m_downloadTask->installedJavaPath(); + + if (javaPath.isEmpty()) { + emitFailed( + tr("Java was downloaded but the binary could not be found.")); + return; + } + + emit logLine(tr("Java downloaded and installed at: %1").arg(javaPath), + MessageLevel::MeshMC); + setJavaPathAndSucceed(javaPath); + }); + + connect(m_downloadTask.get(), &Task::failed, this, + [this, requiredMajor](const QString& reason) { + emitFailed(tr("Failed to download Java %1: %2") + .arg(requiredMajor) + .arg(reason)); + m_downloadTask.reset(); + }); + + connect(m_downloadTask.get(), &Task::status, this, + [this](const QString& status) { + emit logLine(status, MessageLevel::MeshMC); + }); + + m_downloadTask->start(); } -void VerifyJavaInstall::setJavaPathAndSucceed(const QString &javaPath) +void VerifyJavaInstall::setJavaPathAndSucceed(const QString& javaPath) { - auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); - // Set Java path override on the instance only, not globally - m_inst->settings()->set("OverrideJavaLocation", true); - m_inst->settings()->set("JavaPath", javaPath); - emit logLine(tr("Java path set to: %1").arg(javaPath), MessageLevel::MeshMC); - emitSucceeded(); + auto m_inst = + std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); + // Set Java path override on the instance only, not globally + m_inst->settings()->set("OverrideJavaLocation", true); + m_inst->settings()->set("JavaPath", javaPath); + emit logLine(tr("Java path set to: %1").arg(javaPath), + MessageLevel::MeshMC); + emitSucceeded(); } #endif |
