core/kernel/source/jetbrains/mps/util/PathManager.java (102 lines of code) (raw):

/* * Copyright 2000-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. */ package jetbrains.mps.util; import org.jetbrains.mps.annotations.Internal; import org.jetbrains.mps.annotations.Singleton; import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; /** * Responsible for different predefined paths in the distribution layout * * IMPORTANT: this class is not for MPS startup, rather to figure out relevant values when there's MPS instance running. */ @Singleton public final class PathManager { private static final String FILE_PROTO = "file"; private static final String JAR_PROTO = "jar"; private static final String LAUNCHER_CLASS = "jetbrains/mps/Launcher.class"; private static String ourHomePath; private PathManager() { } /** * The thing is that we have two main #getHomePath implementations: here and in IDEA's PathManager#getHomePath. * These almost always should return the same value, however the method here answers to the question where the MPS classes are located, * while the IDEA's method answers where the IDEA classes are located. * Also this paths are configurable from the outside by the properties. * In MPS IDE we obviously have these two pointing to the same location, however * in MPS IDEA plugin the one below point to the root of the mps-core plugin, while the IDEA's method returns * the location of the IDEA distribution. * @see #getPlatformLibPath() * * @return the MPS home path */ public static String getHomePath() { // [AT] it's odd to use different PathManager with different idea about 'home path' from various parts of MPS if (ourHomePath != null) { return ourHomePath; } // ContainingJar(PAthManager.class): /.../mps/lib/mps-core.jar // CL.SystemResource(Launcher): file:/.../mps/startup/classes/jetbrains/mps/Launcher.class // CL.SystemResource(PathManager): jar:file:/.../mps/lib/mps-core.jar!/jetbrains/mps/util/PathManager.class try { // we know PathManager.class is part of [kernel], which always goes into lib/mps-core.jar (in sources - as an IDEA project artifact) final String thisClassQualifiedFile = PathManager.class.getName().replace('.', '/') + ".class"; URI thisClassURI; URL sr = ClassLoader.getSystemResource(thisClassQualifiedFile); if (sr == null) { // For "Run IDEA Tests" scenarios, where we do have .jar artifacts in classpath, but don't specify IDEA's CL as system CL sr = PathManager.class.getClassLoader().getResource(thisClassQualifiedFile); } thisClassURI = sr.toURI(); assert JAR_PROTO.equals(thisClassURI.getScheme()) : thisClassURI; // FWIW, sr.getPath() == null String path = thisClassURI.getRawSchemeSpecificPart(); int delim = path.indexOf("!/"); if (delim > 0) { path = path.substring(0, delim); } URI file = new URI(path); assert FILE_PROTO.equals(file.getScheme()); File root = new File(file.getSchemeSpecificPart()); // {mps_home}/lib root = root.getParentFile(); if (root != null) { // {mps_home} root = root.getParentFile(); } ourHomePath = root == null ? "/" : root.getAbsolutePath(); } catch (URISyntaxException ex) { throw new RuntimeException(ex); } if ("/".equals(ourHomePath)) { // XXX not sure `new File("c:/").getAbsolutePath()` translates to "/" on Windows, likely incomplete check here throw new IllegalStateException("cannot detect MPS location"); } return ourHomePath; } /** * Defines whether we are starting from sources not from distribution */ @Internal public static boolean isFromSources() { final URL launcherURL = ClassLoader.getSystemResource(LAUNCHER_CLASS); return launcherURL != null && launcherURL.getProtocol().equals(FILE_PROTO); } /** * Returns the classpath entry corresponding to {@code jetbrains.mps.Launcher} class used to bootstrap MPS. * Only makes sense if {@link PathManager#isFromSources()} returns true. */ @Internal public static String getLauncherClassPathEntry() { URL launcherURL = ClassLoader.getSystemResource(LAUNCHER_CLASS); if (launcherURL != null && launcherURL.getProtocol().equals(FILE_PROTO)) { return launcherURL.getFile().substring(0, launcherURL.getFile().length() - LAUNCHER_CLASS.length() - 1); // drop trailing File.separator } return null; } public static String getLibExtPath() { return getLibPath() + File.separator + "ext"; } /** * @return <MPS home>/lib location, where IDEA platform jars reside. Is the same as {@link #getLibPath()} */ public static String getPlatformLibPath() { return getLibPath(); } public static Collection<String> getBootstrapPaths() { Collection<String> paths = new ArrayList<>(4); if (new File(getCorePath()).exists()) { paths.add(getCorePath()); } if (new File(getEditorPath()).exists()) { paths.add(getEditorPath()); } return paths; } /** * @return <MPS home>/lib location, where mps own jars reside. Now is the same as {@link #getPlatformLibPath()} */ public static String getLibPath() { // Given getIdeaPath() + getHomePath(), I assume we face few scenarios with location for MPS libraries: // I) "Big" MPS aka MPS as IDE // there's one <MPS Installation>/lib folder to host both IDEA and MPS libraries // II) MPS as IDEA plugin -- NO LONGER ACTUAL // there's <IDEA installation>/lib for IDEA jars // <mps-core plugin>/lib with MPS jars // III) MPS started from sources // there's <checkout dir>/lib with IDEA jars // there's lib/ with MPS jars (IDEA project artifacts) // getLibPath() == getPlatformLibPath(). return getHomePath() + File.separator + "lib"; } public static String getLanguagesPath() { return getHomePath() + File.separator + "languages"; } public static String getWorkbenchPath() { return getHomePath() + File.separator + "workbench"; } public static String getCorePath() { return getHomePath() + File.separator + "core"; } public static String getEditorPath() { return getHomePath() + File.separator + "editor"; } public static String getPreInstalledPluginsPath() { return getHomePath() + File.separator + "plugins"; } public static String getUserDir() { return System.getProperty("user.dir"); } }