diff --git a/pom.xml b/pom.xml index 35322905497..661feb3bc04 100644 --- a/pom.xml +++ b/pom.xml @@ -168,7 +168,7 @@ package - + diff --git a/src/org/rascalmpl/interpreter/Evaluator.java b/src/org/rascalmpl/interpreter/Evaluator.java index 5183ce362df..d5f4a86a50f 100755 --- a/src/org/rascalmpl/interpreter/Evaluator.java +++ b/src/org/rascalmpl/interpreter/Evaluator.java @@ -1174,6 +1174,7 @@ public Result eval(IRascalMonitor monitor, Declaration declaration) { * @param string */ public void doImport(IRascalMonitor monitor, String... string) { + assert monitor != null; IRascalMonitor old = setMonitor(monitor); interrupt = false; try { diff --git a/src/org/rascalmpl/interpreter/utils/RascalManifest.java b/src/org/rascalmpl/interpreter/utils/RascalManifest.java index a23acb73da8..447bc72aba5 100644 --- a/src/org/rascalmpl/interpreter/utils/RascalManifest.java +++ b/src/org/rascalmpl/interpreter/utils/RascalManifest.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; @@ -18,7 +17,6 @@ import java.util.jar.Manifest; import java.util.zip.ZipEntry; -import org.rascalmpl.library.util.PathConfig; import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; import org.rascalmpl.uri.jar.JarURIResolver; @@ -41,12 +39,11 @@ public class RascalManifest { protected static final String SOURCE = "Source"; protected static final String META_INF = "META-INF"; public static final String META_INF_RASCAL_MF = META_INF + "/RASCAL.MF"; + public static final String META_INF_MANIFEST_MF = META_INF + "/MANIFEST.MF"; protected static final String MAIN_MODULE = "Main-Module"; protected static final String MAIN_FUNCTION = "Main-Function"; protected static final String PROJECT_NAME = "Project-Name"; - protected static final String REQUIRE_BUNDLES = "Require-Bundles"; - protected static final String REQUIRE_LIBRARIES = "Require-Libraries"; - + public static String getRascalVersionNumber() { try { Enumeration resources = RascalManifest.class.getClassLoader().getResources("META-INF/MANIFEST.MF"); @@ -61,11 +58,28 @@ public static String getRascalVersionNumber() { } } - return "Rascal version not specified in META-INF/MANIFEST.MF???"; + return "Not specified"; } catch (IOException e) { return "unknown (due to " + e.getMessage(); } } + + /** + * This looks into the META-INF/MANIFEST.MF file for a Name and Specification-Version + */ + public String getManifestVersionNumber(ISourceLocation project) throws IOException { + Manifest mf = new Manifest(javaManifest(project)); + + String bundleName = mf.getMainAttributes().getValue("Name"); + if (bundleName != null && bundleName.equals("rascal")) { + String result = mf.getMainAttributes().getValue("Specification-Version"); + if (result != null) { + return result; + } + } + + return "unknown"; + } public Manifest getDefaultManifest(String projectName) { Manifest manifest = new Manifest(); @@ -75,7 +89,6 @@ public Manifest getDefaultManifest(String projectName) { mainAttributes.put(new Attributes.Name(MAIN_MODULE), DEFAULT_MAIN_MODULE); mainAttributes.put(new Attributes.Name(MAIN_FUNCTION), DEFAULT_MAIN_FUNCTION); mainAttributes.put(new Attributes.Name(PROJECT_NAME), projectName); - mainAttributes.put(new Attributes.Name(REQUIRE_LIBRARIES), ""); return manifest; } @@ -152,13 +165,6 @@ public String getProjectName(File jarFile) { return getManifestProjectName(manifest(jarFile)); } - /** - * @return a list of bundle names this jar depends on, or 'null' if none is configured. - */ - public List getRequiredLibraries(JarInputStream jarStream) { - return getManifestRequiredLibraries(manifest(jarStream)); - } - /** * @return the name of the main module of a deployment unit, or 'null' if none is configured. */ @@ -195,20 +201,6 @@ public boolean hasMainModule(Class clazz) { return getManifestMainModule(manifest(clazz)) != null; } - /** - * @return a list of bundle names this jar depends on, or 'null' if none is configured. - */ - public List getRequiredLibraries(File jarFile) { - return getManifestRequiredLibraries(manifest(jarFile)); - } - - /** - * @return a list of bundle names this jar depends on, or 'null' if none is configured. - */ - public List getRequiredLibraries(Class clazz) { - return getManifestRequiredLibraries(manifest(clazz)); - } - /** * @return a list of paths relative to the root of the jar, if no such option is configured * it will return ["src"]. @@ -238,17 +230,6 @@ public String getManifestMainModule(InputStream project) { public String getManifestMainFunction(InputStream project) { return getManifestAttribute(project, MAIN_FUNCTION, null); } - - /** - * @return a list of bundle names this jar depends on, or 'null' if none is configured. - */ - public List getManifestRequiredLibraries(InputStream project) { - return getManifestAttributeList(project, REQUIRE_LIBRARIES, null); - } - - public List getManifestRequiredLibraries(ISourceLocation root) { - return getManifestAttributeList(manifest(root), REQUIRE_LIBRARIES, null); - } public InputStream manifest(Class clazz) { return clazz.getResourceAsStream("/" + META_INF_RASCAL_MF); @@ -262,6 +243,14 @@ public InputStream manifest(ISourceLocation root) { } } + public InputStream javaManifest(ISourceLocation root) { + try { + return URIResolverRegistry.getInstance().getInputStream(URIUtil.getChildLocation(JarURIResolver.jarify(root), META_INF_MANIFEST_MF)); + } catch (IOException e) { + return null; + } + } + public String getProjectName(ISourceLocation root) { return getManifestProjectName(manifest(root)); } @@ -270,25 +259,6 @@ public List getSourceRoots(ISourceLocation root) { return getManifestSourceRoots(manifest(root)); } - public List getRequiredLibraries(ISourceLocation root) { - return getManifestRequiredLibraries(manifest(root)); - } - - public PathConfig makePathConfig(ISourceLocation root) throws IOException { - List libs = new ArrayList<>(); - List srcs = new ArrayList<>(); - - ISourceLocation binFolder = URIUtil.getChildLocation(root, "bin"); - libs.add(binFolder); - - RascalManifest mf = new RascalManifest(); - for (String src : mf.getSourceRoots(root)) { - srcs.add(URIUtil.getChildLocation(root, src)); - } - - return new PathConfig(srcs, libs, binFolder); - } - public InputStream manifest(JarInputStream stream) { JarEntry next = null; ByteArrayOutputStream out = new ByteArrayOutputStream(); diff --git a/src/org/rascalmpl/library/Location.rsc b/src/org/rascalmpl/library/Location.rsc index 709adca7d49..9804374d335 100644 --- a/src/org/rascalmpl/library/Location.rsc +++ b/src/org/rascalmpl/library/Location.rsc @@ -45,6 +45,14 @@ loc relativize(list[loc] haystack, loc needle) { } } +@synopsis{Shortens an absolute path to a jar inside the local maven repository.} +@javaClass{org.rascalmpl.library.Prelude} +java loc mavenize(loc jar); + +@synopsis{If the location points to a jar file, then this modifies the scheme and the path to point _inside_ of the jar.} +@javaClass{org.rascalmpl.library.Prelude} +java loc jarify(loc jar); + @synopsis{Convert Windows path syntax to a `loc` value} @description{ This conversion supports generic Windows path syntax, including: diff --git a/src/org/rascalmpl/library/Messages.java b/src/org/rascalmpl/library/Messages.java new file mode 100644 index 00000000000..488f26c6e20 --- /dev/null +++ b/src/org/rascalmpl/library/Messages.java @@ -0,0 +1,143 @@ +package org.rascalmpl.library; + +import java.io.PrintWriter; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.rascalmpl.values.IRascalValueFactory; + +import io.usethesource.vallang.IConstructor; +import io.usethesource.vallang.IList; +import io.usethesource.vallang.ISourceLocation; +import io.usethesource.vallang.IString; +import io.usethesource.vallang.IValue; +import io.usethesource.vallang.IValueFactory; +import io.usethesource.vallang.type.TypeFactory; +import io.usethesource.vallang.type.TypeStore; + + +/** + * Java API for the messages in the standard library module `Message` + * + * This is the standard format for all error messages in Rascal projects and beyond. + * Since some low-level core code also produces messages that should end up in UI, + * we write here a bridge between the Java and Rascal representation. + * + * TODO Later when the standard library is bootstrapped, this code might be replaced + * by the generated code from the compiler for the `Message` module. + */ +public class Messages { + private static final TypeFactory tf = TypeFactory.getInstance(); + private static final IValueFactory vf = IRascalValueFactory.getInstance(); + private static final TypeStore ts = new TypeStore(); + + // These declarations mirror the data definition in the `Message` root module of the standard library. + private static final io.usethesource.vallang.type.Type Message = tf.abstractDataType(ts, "Message"); + private static final io.usethesource.vallang.type.Type Message_info = tf.constructor(ts, Message, "info", tf.stringType(), "msg", tf.sourceLocationType(), "at"); + private static final io.usethesource.vallang.type.Type Message_warning = tf.constructor(ts, Message, "warning", tf.stringType(), "msg", tf.sourceLocationType(), "at"); + private static final io.usethesource.vallang.type.Type Message_error = tf.constructor(ts, Message, "error", tf.stringType(), "msg", tf.sourceLocationType(), "at"); + + public static IValue info(String message, ISourceLocation loc) { + return vf.constructor(Message_info, vf.string(message), loc); + } + + public static IValue warning(String message, ISourceLocation loc) { + return vf.constructor(Message_warning, vf.string(message), loc); + } + + public static IValue error(String message, ISourceLocation loc) { + return vf.constructor(Message_error, vf.string(message), loc); + } + + public static void write(IList messages, PrintWriter out) { + int maxLine = 0; + int maxColumn = 0; + + for (IValue error : messages) { + ISourceLocation loc = (ISourceLocation) ((IConstructor) error).get("at"); + if (loc.hasLineColumn()) { + maxLine = Math.max(loc.getBeginLine(), maxLine); + maxColumn = Math.max(loc.getBeginColumn(), maxColumn); + } + } + + int lineWidth = (int) Math.log10(maxLine + 1) + 1; + int colWidth = (int) Math.log10(maxColumn + 1) + 1; + + Stream sortedStream = messages.stream() + .map(IConstructor.class::cast) + .sorted((m1, m2) -> { + ISourceLocation l1 = (ISourceLocation) m1.get("at"); + ISourceLocation l2 = (ISourceLocation) m2.get("at"); + + if (!l1.getScheme().equals(l2.getScheme())) { + return l1.getScheme().compareTo(l2.getScheme()); + } + + if (!l1.getAuthority().equals(l2.getAuthority())) { + return l1.getAuthority().compareTo(l2.getAuthority()); + } + + if (!l1.getPath().equals(l2.getPath())) { + return l1.getPath().compareTo(l2.getPath()); + } + + if (l1.hasLineColumn() && l2.hasLineColumn()) { + if (l1.getBeginLine() == l2.getBeginLine()) { + return Integer.compare(l1.getBeginColumn(), l2.getBeginColumn()); + } + else { + return Integer.compare(l1.getBeginLine(), l2.getBeginLine()); + } + } + else if (l1.hasOffsetLength() && l2.hasOffsetLength()) { + return Integer.compare(l1.getOffset(), l2.getOffset()); + } + else if (l1.hasOffsetLength()) { + return -1; + } + else if (l2.hasOffsetLength()) { + return 1; + } + + return 0; + }); + + for (IConstructor msg : sortedStream.collect(Collectors.toList())) { + String type = msg.getName(); + boolean isError = type.equals("error"); + boolean isWarning = type.equals("warning"); + + ISourceLocation loc = (ISourceLocation) msg.get("at"); + int col = 0; + int line = 0; + if (loc.hasLineColumn()) { + col = loc.getBeginColumn(); + line = loc.getBeginLine(); + } + + String output + = loc.getPath() + + ":" + + String.format("%0" + lineWidth + "d", line) + + ":" + + String.format("%0" + colWidth + "d", col) + + ": " + + ((IString) msg.get("msg")).getValue() + ; + + if (isError) { + out.println("[ERROR] " + output); + } + else if (isWarning) { + out.println("[WARNING] " + output); + } + else { + out.println("[INFO] " + output); + } + } + + out.flush(); + return; + } +} diff --git a/src/org/rascalmpl/library/Prelude.java b/src/org/rascalmpl/library/Prelude.java index a28ca8f0d79..20e4a0377ea 100644 --- a/src/org/rascalmpl/library/Prelude.java +++ b/src/org/rascalmpl/library/Prelude.java @@ -85,6 +85,8 @@ import org.rascalmpl.uri.ISourceLocationWatcher.ISourceLocationChangeType; import org.rascalmpl.uri.ISourceLocationWatcher.ISourceLocationChanged; import org.rascalmpl.uri.ISourceLocationWatcher.ISourceLocationType; +import org.rascalmpl.uri.file.MavenRepositoryURIResolver; +import org.rascalmpl.uri.jar.JarURIResolver; import org.rascalmpl.uri.LogicalMapResolver; import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; @@ -3673,6 +3675,14 @@ public ISourceLocation resolveLocation(ISourceLocation loc) { } } + public ISourceLocation mavenize(ISourceLocation jar) { + return MavenRepositoryURIResolver.mavenize(jar); + } + + public ISourceLocation jarify(ISourceLocation jar) { + return JarURIResolver.jarify(jar); + } + public ISet findResources(IString fileName) { return resourceProvider.findResources(fileName.getValue()).stream().collect(values.setWriter()); } diff --git a/src/org/rascalmpl/library/lang/csv/IO.java b/src/org/rascalmpl/library/lang/csv/IO.java index 09ba995b919..dbdeeaab489 100644 --- a/src/org/rascalmpl/library/lang/csv/IO.java +++ b/src/org/rascalmpl/library/lang/csv/IO.java @@ -387,7 +387,7 @@ public IValue visitSet(Type type) throws RuntimeException { } @Override public IValue visitSourceLocation(Type type) throws RuntimeException { - return values.sourceLocation(URIUtil.invalidURI()); + return URIUtil.unknownLocation(); } @Override public IValue visitString(Type type) throws RuntimeException { diff --git a/src/org/rascalmpl/library/lang/java/m3/AST.rsc b/src/org/rascalmpl/library/lang/java/m3/AST.rsc index c427c772edf..d1f362b666e 100644 --- a/src/org/rascalmpl/library/lang/java/m3/AST.rsc +++ b/src/org/rascalmpl/library/lang/java/m3/AST.rsc @@ -414,7 +414,7 @@ public set[Declaration] createAstsFromMavenProject(loc project, bool collectBind throw " is not a valid directory"; } - classPaths = getProjectPathConfig(project).javaCompilerPath; + classPaths = getProjectPathConfig(project).libs; sourcePaths = getPaths(project, "java"); return createAstsFromFiles({ p | sp <- sourcePaths, p <- find(sp, "java"), isFile(p)}, collectBindings, sourcePath = [*findRoots(sourcePaths)], classPath = classPaths, errorRecovery = errorRecovery, javaVersion = javaVersion); } diff --git a/src/org/rascalmpl/library/lang/java/m3/Core.rsc b/src/org/rascalmpl/library/lang/java/m3/Core.rsc index 634ced10f28..b79ee0755a2 100644 --- a/src/org/rascalmpl/library/lang/java/m3/Core.rsc +++ b/src/org/rascalmpl/library/lang/java/m3/Core.rsc @@ -264,7 +264,7 @@ M3 createM3FromMavenProject(loc project, bool errorRecovery = false, bool includ throw " is not a valid directory"; } - list[loc] classPaths = getProjectPathConfig(project).javaCompilerPath; + list[loc] classPaths = getProjectPathConfig(project).libs; sourcePaths = getPaths(project, "java"); M3 result = composeJavaM3(project, createM3sFromFiles({p | sp <- sourcePaths, p <- find(sp, "java"), isFile(p)}, errorRecovery = errorRecovery, sourcePath = [*findRoots(sourcePaths)], classPath = classPaths, javaVersion = javaVersion)); diff --git a/src/org/rascalmpl/library/lang/rascal/tests/basic/Locations.rsc b/src/org/rascalmpl/library/lang/rascal/tests/basic/Locations.rsc index 3843d3081d3..8ff62ef516a 100644 --- a/src/org/rascalmpl/library/lang/rascal/tests/basic/Locations.rsc +++ b/src/org/rascalmpl/library/lang/rascal/tests/basic/Locations.rsc @@ -578,7 +578,7 @@ test bool mvnSchemeTest() { // check whether the implementation of the scheme holds the contract specified in the assert for (jar <- jarFiles, path(groupId, artifactId, version) := parseMavenLocalRepositoryPath(jar)) { // this is the contract: - mvnLoc = |mvn://!!|; + mvnLoc = |mvn://----|; assert resolveLocation(mvnLoc) == resolveLocation(jar) : " != ' jar: diff --git a/src/org/rascalmpl/library/util/Eval.java b/src/org/rascalmpl/library/util/Eval.java index 6da394f8621..a73acca0f0a 100644 --- a/src/org/rascalmpl/library/util/Eval.java +++ b/src/org/rascalmpl/library/util/Eval.java @@ -14,12 +14,10 @@ *******************************************************************************/ package org.rascalmpl.library.util; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URISyntaxException; -import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; @@ -51,7 +49,6 @@ import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.IInteger; -import io.usethesource.vallang.IList; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.IString; import io.usethesource.vallang.IValue; @@ -227,7 +224,7 @@ public RascalRuntime(PathConfig pcfg, InputStream input, OutputStream stderr, Ou eval.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); eval.setMonitor(services); - eval.getConfiguration().setRascalJavaClassPathProperty(javaCompilerPathAsString(pcfg.getJavaCompilerPath())); + eval.getConfiguration().setRascalJavaClassPathProperty(PathConfig.resolveCurrentRascalRuntimeJar().getPath()); eval.setMonitor(services); if (!pcfg.getSrcs().isEmpty()) { @@ -246,7 +243,7 @@ public RascalRuntime(PathConfig pcfg, InputStream input, OutputStream stderr, Ou eval.addRascalSearchPath((ISourceLocation) path); } - ClassLoader cl = new SourceLocationClassLoader(pcfg.getClassloaders(), ShellEvaluatorFactory.class.getClassLoader()); + ClassLoader cl = new SourceLocationClassLoader(pcfg.getLibsAndTarget(), ShellEvaluatorFactory.class.getClassLoader()); eval.addClassLoader(cl); } @@ -282,28 +279,6 @@ private static ISourceLocation inferProjectRoot(ISourceLocation member) { return current; } - - private String javaCompilerPathAsString(IList javaCompilerPath) { - StringBuilder b = new StringBuilder(); - - for (IValue elem : javaCompilerPath) { - ISourceLocation loc = (ISourceLocation) elem; - - if (b.length() != 0) { - b.append(File.pathSeparatorChar); - } - - // this is the precondition - assert loc.getScheme().equals("file"); - - // this is robustness in case of experimentation in pom.xml - if ("file".equals(loc.getScheme())) { - b.append(Paths.get(loc.getURI()).toAbsolutePath().toString()); - } - } - - return b.toString(); - } public void reset() { eval.getCurrentModuleEnvironment().reset(); diff --git a/src/org/rascalmpl/library/util/PathConfig.java b/src/org/rascalmpl/library/util/PathConfig.java index 131c3e31c93..56c205af84c 100644 --- a/src/org/rascalmpl/library/util/PathConfig.java +++ b/src/org/rascalmpl/library/util/PathConfig.java @@ -4,24 +4,28 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; +import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.net.URISyntaxException; +import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.jar.Manifest; import org.rascalmpl.interpreter.Configuration; import org.rascalmpl.interpreter.utils.RascalManifest; -import org.rascalmpl.uri.ILogicalSourceLocationResolver; +import org.rascalmpl.library.Messages; import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; -import org.rascalmpl.uri.jar.JarURIResolver; +import org.rascalmpl.uri.file.MavenRepositoryURIResolver; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.values.ValueFactoryFactory; @@ -39,57 +43,45 @@ import io.usethesource.vallang.type.TypeStore; public class PathConfig { - - private static final IValueFactory vf = ValueFactoryFactory.getValueFactory(); - private final TypeFactory tf = TypeFactory.getInstance(); - private final TypeStore store = new TypeStore(); - - // WARNING: these definitions must reflect the definitions in util::Reflective.rsc - private final Type PathConfigType = tf.abstractDataType(store, "PathConfig"); - private final Type pathConfigConstructor = tf.constructor(store, PathConfigType, "pathConfig"); - - private final List srcs; // List of locations to search for source files - private final List libs; // List of (library) locations to search for derived files - private final List ignores; // List of (library) locations to ignore while compiling - private final List javaCompilerPath; // List of (library) locations to use for the compiler path of generated parsers - private final List classloaders; // List of (library) locations to use to bootstrap classloaders from - - private final ISourceLocation bin; // Global location for derived files outside projects or libraries - - private static ISourceLocation defaultStd; - private static List defaultIgnores; - private static List defaultJavaCompilerPath; - private static List defaultClassloaders; - private static ISourceLocation defaultBin; + private static final IValueFactory vf = ValueFactoryFactory.getValueFactory(); + private final TypeFactory tf = TypeFactory.getInstance(); + private final TypeStore store = new TypeStore(); + // WARNING: these definitions must reflect the definitions in `util::Reflective` + private final Type PathConfigType = tf.abstractDataType(store, "PathConfig"); + private final Type pathConfigConstructor = tf.constructor(store, PathConfigType, "pathConfig"); + + private final List srcs; + private final List libs; + private final ISourceLocation bin; + private final List ignores; + private final ISourceLocation generatedSources; + private final List messages; + + + // defaults are shared here because they occur in different use places. + private static final List defaultIgnores = Collections.emptyList(); + private static final ISourceLocation defaultGeneratedSources = URIUtil.unknownLocation(); + private static final List defaultMessages = Collections.emptyList(); + private static final ISourceLocation defaultBin = URIUtil.unknownLocation(); + private static final List defaultLibs = Collections.emptyList(); + + /** implementation detail of communicating with the `mvn` command */ private static final String WINDOWS_ROOT_TRUSTSTORE_TYPE_DEFINITION = "-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT"; - public static enum RascalConfigMode { - INTERPETER, + public static enum RascalConfigMode { + INTERPRETER, COMPILER } - static { - try { - // Defaults should be in sync with util::Reflective - defaultStd = vf.sourceLocation("lib", "rascal", ""); - defaultBin = vf.sourceLocation("tmp", "", "default-rascal-bin"); - defaultIgnores = Collections.emptyList(); - defaultJavaCompilerPath = computeDefaultJavaCompilerPath(); - defaultClassloaders = computeDefaultClassLoaders(); - } catch (URISyntaxException e) { - e.printStackTrace(); - } - } - - public PathConfig() { - srcs = Collections.emptyList(); - ignores = defaultIgnores; - bin = defaultBin; - libs = Arrays.asList(defaultStd); - javaCompilerPath = defaultJavaCompilerPath; - classloaders = defaultClassloaders; - } + public PathConfig() { + srcs = Collections.emptyList(); + ignores = defaultIgnores; + bin = defaultBin; + libs = Collections.emptyList(); + generatedSources = defaultGeneratedSources; + messages = defaultMessages; + } public PathConfig(IConstructor pcfg) throws IOException { this( @@ -97,129 +89,97 @@ public PathConfig(IConstructor pcfg) throws IOException { libs(pcfg), bin(pcfg), ignores(pcfg), - javaCompilerPath(pcfg), - classloaders(pcfg) + generatedSources(pcfg), + messages(pcfg) ); } - - private static IList classloaders(IConstructor pcfg) { - return getListValueFromConstructor(pcfg, defaultClassloaders, "classloaders"); - } - - private static IList javaCompilerPath(IConstructor pcfg) { - return getListValueFromConstructor(pcfg, defaultJavaCompilerPath, "javaCompilerPath"); - } - private static IList ignores(IConstructor pcfg) { - return getListValueFromConstructor(pcfg, defaultIgnores, "ignores"); + public PathConfig(List srcs, List libs, ISourceLocation bin) { + this(srcs, libs, bin, defaultIgnores); } - - private static IList getListValueFromConstructor(IConstructor pcfg, List def, String label) { - IList val = (IList) pcfg.asWithKeywordParameters().getParameter(label); - return val == null ? def.stream().collect(vf.listWriter()) : val; - } - - private static ISourceLocation bin(IConstructor pcfg) { - ISourceLocation val = (ISourceLocation) pcfg.asWithKeywordParameters().getParameter("bin"); - return val == null ? defaultBin : val; + + public PathConfig(List srcs, List libs, ISourceLocation bin, List ignores) { + this(srcs, libs, bin, ignores, defaultGeneratedSources); } - - private static IList libs(IConstructor pcfg) { - return getListValueFromConstructor(pcfg, Arrays.asList(defaultStd), "libs"); + + public PathConfig(List srcs, List libs, ISourceLocation bin, List ignores, ISourceLocation generatedSources) { + this(srcs, libs, bin, ignores, generatedSources, defaultMessages); } - - private static IList srcs(IConstructor pcfg) { - return getListValueFromConstructor(pcfg, Collections.emptyList(), "srcs"); + + public PathConfig(List srcs, List libs, ISourceLocation bin, List ignores, ISourceLocation generatedSources, List messages) { + this.srcs = dedup(srcs); + this.ignores = dedup(ignores); + this.libs = dedup(libs); + this.bin = bin; + this.generatedSources = generatedSources; + this.messages = messages; } - - public PathConfig(List srcs, List libs, ISourceLocation bin) throws IOException { - this(srcs, libs, bin, defaultIgnores); - } - - public PathConfig(List srcs, List libs, ISourceLocation bin, List ignores) throws IOException { - this(srcs, libs, bin, ignores, defaultJavaCompilerPath); - } - - public PathConfig(List srcs, List libs, ISourceLocation bin, List ignores, List javaCompilerPath) throws IOException { - this(srcs, libs, bin, ignores, javaCompilerPath, defaultClassloaders); - } - - public PathConfig(List srcs, List libs, ISourceLocation bin, List ignores, List javaCompilerPath, List classloaders) throws IOException { - this.srcs = dedup(srcs); - this.ignores = dedup(ignores); - this.libs = dedup(libs); - this.bin = bin; - this.javaCompilerPath = dedup(javaCompilerPath); - this.classloaders = dedup(classloaders); - } - - public PathConfig(IList srcs, IList libs, ISourceLocation bin) throws IOException{ + + public PathConfig(IList srcs, IList libs, ISourceLocation bin) { this.srcs = initializeLocList(srcs); this.libs = initializeLocList(libs); this.bin = bin; this.ignores = defaultIgnores; - this.javaCompilerPath = defaultJavaCompilerPath; - this.classloaders = defaultClassloaders; + this.generatedSources = defaultGeneratedSources; + this.messages = defaultMessages; } - - public PathConfig(IList srcs, IList libs, ISourceLocation bin, IList ignores) throws IOException{ + + public PathConfig(IList srcs, IList libs, ISourceLocation bin, IList ignores) { this.srcs = initializeLocList(srcs); this.libs = initializeLocList(libs); this.bin = bin; this.ignores = initializeLocList(ignores); - this.javaCompilerPath = defaultJavaCompilerPath; - this.classloaders = defaultClassloaders; + this.generatedSources = defaultGeneratedSources; + this.messages = defaultMessages; } - - public PathConfig(IList srcs, IList libs, ISourceLocation bin, IList ignores, IList javaCompilerPath) throws IOException{ + + public PathConfig(IList srcs, IList libs, ISourceLocation bin, IList ignores, ISourceLocation generatedSources) { this.srcs = initializeLocList(srcs); this.libs = initializeLocList(libs); this.bin = bin; this.ignores = initializeLocList(ignores); - this.javaCompilerPath = initializeLocList(javaCompilerPath); - this.classloaders = defaultClassloaders; + this.generatedSources = generatedSources; + this.messages = defaultMessages; } - - public PathConfig(IList srcs, IList libs, ISourceLocation bin, IList ignores, IList javaCompilerPath, IList classloaders) throws IOException { + + public PathConfig(IList srcs, IList libs, ISourceLocation bin, IList ignores, ISourceLocation generatedSources, IList messages) { this.srcs = initializeLocList(srcs); this.libs = initializeLocList(libs); this.bin = bin; this.ignores = initializeLocList(ignores); - this.javaCompilerPath = initializeLocList(javaCompilerPath); - this.classloaders = initializeLocList(classloaders); + this.generatedSources = generatedSources; + this.messages = convertMessages(messages); } - - private static ISourceLocation parseSourceLocation(String recLib) throws IOException { - return (ISourceLocation) new StandardTextReader().read(vf, new StringReader(recLib)); + + private static IList messages(IConstructor pcfg) { + return getListValueFromConstructor(pcfg, defaultMessages, "messages"); } - - public PathConfig(IList srcs, IList libs, ISourceLocation bin, IList ignores, IList javaCompilerPath, IList classloaders, ISourceLocation repo) throws IOException{ - this.srcs = initializeLocList(srcs); - this.libs = initializeLocList(libs); - this.bin = bin; - this.ignores = initializeLocList(ignores); - this.javaCompilerPath = initializeLocList(javaCompilerPath); - this.classloaders = initializeLocList(classloaders); + + private static ISourceLocation generatedSources(IConstructor pcfg) { + ISourceLocation val = (ISourceLocation) pcfg.asWithKeywordParameters().getParameter("generatedSources"); + return val == null ? defaultGeneratedSources : val; } - public PathConfig parse(String pathConfigString) throws IOException { - try { - IConstructor cons = (IConstructor) new StandardTextReader().read(vf, store, PathConfigType, new StringReader(pathConfigString)); - IWithKeywordParameters kwp = cons.asWithKeywordParameters(); + private static IList ignores(IConstructor pcfg) { + return getListValueFromConstructor(pcfg, defaultIgnores, "ignores"); + } - IList srcs = (IList) kwp.getParameter("srcs"); - IList libs = (IList) kwp.getParameter("libs"); - ISourceLocation bin = (ISourceLocation) kwp.getParameter("bin"); + private static IList getListValueFromConstructor(IConstructor pcfg, List def, String label) { + IList val = (IList) pcfg.asWithKeywordParameters().getParameter(label); + return val == null ? def.stream().collect(vf.listWriter()) : val; + } - return new PathConfig( - srcs != null ? srcs : vf.list(), - libs != null ? libs : vf.list(), - bin != null ? bin : URIUtil.rootLocation("cwd") - ); - } - catch (FactTypeUseException e) { - throw new IOException(e); - } + private static ISourceLocation bin(IConstructor pcfg) { + ISourceLocation val = (ISourceLocation) pcfg.asWithKeywordParameters().getParameter("bin"); + return val == null ? defaultBin : val; + } + + private static IList libs(IConstructor pcfg) { + return getListValueFromConstructor(pcfg, defaultLibs, "libs"); + } + + private static IList srcs(IConstructor pcfg) { + return getListValueFromConstructor(pcfg, Collections.emptyList(), "srcs"); } private static List initializeLocList(IList srcs) { @@ -237,169 +197,111 @@ private static List dedup(List list) { return filtered; } - - private static List convertLocs(IList locs){ - List result = new ArrayList<>(); - for(IValue p : locs){ - if(p instanceof ISourceLocation){ - result.add((ISourceLocation) p); - } else { - throw new RuntimeException("Path should contain source locations and not " + p.getClass().getName()); - } - } - - return result; - } - - private static List computeDefaultClassLoaders() throws URISyntaxException { + + private static List convertLocs(IList locs){ List result = new ArrayList<>(); - String javaClasspath = System.getProperty("java.class.path"); - if (javaClasspath != null) { - for (String path : javaClasspath.split(File.pathSeparator)) { - result.add(URIUtil.createFileLocation(new File(path).getAbsolutePath())); + for(IValue p : locs){ + if(p instanceof ISourceLocation){ + result.add((ISourceLocation) p); + } else { + throw new RuntimeException("Path should contain source locations and not " + p.getClass().getName()); } } - else { - result.add(URIUtil.correctLocation("system", "", "")); - } + return result; } - private static List computeDefaultJavaCompilerPath() throws URISyntaxException { - List result = new ArrayList<>(); - String classPath = System.getProperty("java.class.path"); - - if (classPath != null) { - for (String path : classPath.split(File.pathSeparator)) { - result.add(URIUtil.createFileLocation(new File(path).getAbsolutePath())); + private static List convertMessages(IList locs){ + List result = new ArrayList<>(); + for(IValue p : locs){ + if(p instanceof IConstructor){ + result.add((IConstructor) p); + } else { + throw new RuntimeException("Messages should contain message constructors and not " + p.getClass().getName()); } } return result; } - - String makeFileName(String qualifiedModuleName) { - return makeFileName(qualifiedModuleName, "rsc"); - } - - public static ISourceLocation getDefaultStd(){ - return defaultStd; - } - - public static ISourceLocation getDefaultBin(){ + + String makeFileName(String qualifiedModuleName) { + return makeFileName(qualifiedModuleName, "rsc"); + } + + public static ISourceLocation getDefaultBin(){ return defaultBin; } - - public static List getDefaultJavaCompilerPath() { - return Collections.unmodifiableList(defaultJavaCompilerPath); - } - - public static IList getDefaultJavaCompilerPathList() { - return convertLocs(defaultJavaCompilerPath); - } - - public static IList getDefaultIgnoresList() { - return convertLocs(defaultIgnores); - } - - public static IList getDefaultClassloadersList() { - return convertLocs(defaultClassloaders); - } - - private static IList convertLocs(List locs) { - IListWriter w = vf.listWriter(); - w.appendAll(locs); - return w.done(); + + public static ISourceLocation getDefaultGeneratedSources() { + return defaultGeneratedSources; + } + + public static IList getDefaultIgnoresList() { + return convertLocs(defaultIgnores); + } + + private static IList convertLocs(List locs) { + IListWriter w = vf.listWriter(); + w.appendAll(locs); + return w.done(); } public static List getDefaultIgnores(){ - return Collections.unmodifiableList(defaultIgnores); - } - - public static List getDefaultClassloaders() { - return Collections.unmodifiableList(defaultClassloaders); - } - - public IValueFactory getValueFactory() { - return vf; - } - - public IList getSrcs() { - return vf.list(srcs.toArray(new IValue[0])); - } - - public IList getJavaCompilerPath() { - return vf.list(javaCompilerPath.toArray(new IValue[0])); - } - - public IList getClassloaders() { - return vf.list(classloaders.toArray(new IValue[classloaders.size()])); - } - - public PathConfig addSourceLoc(ISourceLocation dir) { - List extendedsrcs = new ArrayList(srcs); - extendedsrcs.add(dir); - try { - return new PathConfig(extendedsrcs, libs, bin, ignores, javaCompilerPath, classloaders); - } - catch (IOException e) { - assert false; - return this; - } - } - - public PathConfig addJavaCompilerPath(ISourceLocation dir) { - List extended = new ArrayList(javaCompilerPath); - extended.add(dir); - try { - return new PathConfig(srcs, libs, bin, ignores, extended, classloaders); - } - catch (IOException e) { - assert false; - return this; - } - } - - public PathConfig addClassloader(ISourceLocation dir) { - List extended = new ArrayList(classloaders); - extended.add(dir); - try { - return new PathConfig(srcs, libs, bin, ignores, javaCompilerPath, extended); - } - catch (IOException e) { - assert false; - return this; - } + return Collections.unmodifiableList(defaultIgnores); } - - public IList getIgnores() { - return vf.list(ignores.toArray(new IValue[0])); - } - - public PathConfig addIgnoreLoc(ISourceLocation dir) { - List extendedignores = new ArrayList(ignores); - extendedignores.add(dir); - try { - return new PathConfig(srcs, libs, bin, extendedignores, javaCompilerPath, classloaders); - } - catch (IOException e) { - assert false; - return this; - } - } - - public IList getLibs() { + + public IValueFactory getValueFactory() { + return vf; + } + + public IList getSrcs() { + return vf.list(srcs.toArray(new IValue[0])); + } + + public ISourceLocation getGeneratedSources() { + return generatedSources; + } + + public IList getMessages() { + return vf.list(messages.toArray(new IValue[messages.size()])); + } + + public PathConfig addSourceLoc(ISourceLocation dir) throws IOException { + List extendedsrcs = new ArrayList(srcs); + extendedsrcs.add(dir); + return new PathConfig(extendedsrcs, libs, bin, ignores, generatedSources, messages); + } + + public PathConfig setGeneratedSources(ISourceLocation dir) throws IOException { + return new PathConfig(srcs, libs, bin, ignores, dir, messages); + } + + public IList getIgnores() { + return vf.list(ignores.toArray(new IValue[0])); + } + + public PathConfig addIgnoreLoc(ISourceLocation dir) throws IOException { + List extendedignores = new ArrayList(ignores); + extendedignores.add(dir); + return new PathConfig(srcs, libs, bin, extendedignores, generatedSources, messages); + } + + public IList getLibs() { return vf.list(libs.toArray(new IValue[0])); } - - public PathConfig addLibLoc(ISourceLocation dir) throws IOException { - List extendedlibs = new ArrayList(libs); - extendedlibs.add(dir); - return new PathConfig(srcs, extendedlibs, bin, ignores, javaCompilerPath, classloaders); - } - + + public IList getLibsAndTarget() { + return getLibs().append(getBin()); + } + + public PathConfig addLibLoc(ISourceLocation dir) throws IOException { + List extendedlibs = new ArrayList(libs); + extendedlibs.add(dir); + return new PathConfig(srcs, extendedlibs, bin, ignores, generatedSources, messages); + } + /** - * This will create a PathConfig by learning from the MANIFEST/RASCAL.MF file where the sources + * This will create a PathConfig by learning from the MANIFEST/RASCAL.MF file where the sources * are, which libraries to reference and which classpath entries to add. If this PathConfig is * for the interpreter it adds more folders to the source path than if its for the compiler. * @@ -407,18 +309,76 @@ public PathConfig addLibLoc(ISourceLocation dir) throws IOException { * correspondence, then the target and source folders of the projects are added rather then * the jar files. For compiler configs this works differently than for interpreter configs. * The latter adds source folders to the sources while the former adds target folders to the libraries. - * - * @param manifest the source location of the folder which contains MANIFEST/RASCAL.MF. - * @return + * + * @param manifest the source location of the folder which contains MANIFEST/RASCAL.MF. + * @return * @throws URISyntaxException - */ - public static PathConfig fromSourceProjectMemberRascalManifest(ISourceLocation projectMember, RascalConfigMode mode) throws IOException { + */ + public static PathConfig fromSourceProjectMemberRascalManifest(ISourceLocation projectMember, RascalConfigMode mode) throws IOException { if (!URIResolverRegistry.getInstance().isDirectory(projectMember)) { projectMember = URIUtil.getParentLocation(projectMember); } return fromSourceProjectRascalManifest(inferProjectRoot(projectMember), mode); } + /* + * Sometimes we need access to a library that is already on the classpath, for example this could + * be rascal-.jar or typepal.jar or rascal-core or rascal-lsp. The IDE or current runtime + * environment of Rascal has provided these dependencies while booting up the JVM using the `-cp` + * parameter. + * + * This function searches for the corresponding jar file by looking for instances of RASCAL.MF files + * with their Project-Name property set to the parameter `projectName`. Then it uses the actual URL + * of the location of the RASCAL.MF file (inside jar or a target folder) to derive the root of the + * given project. + * + * Benefit: After this resolution code has executed, the resulting explicit and transparant jar location is + * useful as an entry in PathConfig instances. + * + * Pitfall: Note however that the current JVM instance can never escape + * from loading classes from the rascal.jar that was given on its classpath. + */ + public static ISourceLocation resolveProjectOnClasspath(String projectName) throws IOException { + RascalManifest mf = new RascalManifest(); + Enumeration mfs = PathConfig.class.getClassLoader().getResources(RascalManifest.META_INF_RASCAL_MF); + + for (URL url : Collections.list(mfs)) { + try { + String libName = mf.getProjectName(url.openStream()); + + if (libName != null && libName.equals(projectName)) { + ISourceLocation loc; + + if (url.getProtocol().equals("jar") && url.getPath().startsWith("file:/")) { + // these are the weird jar URLs we get from `getResources` sometimes. We use the URL + // parser to make sense of it and then convert it to an ISourceLocation + loc = vf.sourceLocation("file", null, URIUtil.fromURL(new URL(url.getPath())).getPath()); + + // unjarify the path + loc = URIUtil.changePath(loc, loc.getPath().replace("!/" + RascalManifest.META_INF_RASCAL_MF, "")); + } + else { + // this is typically a target folder + loc = vf.sourceLocation(URIUtil.fromURL(url)); + loc = URIUtil.getParentLocation(URIUtil.getParentLocation(loc)); + } + + + return loc; + } + } + catch (IOException | URISyntaxException e) { + throw new FileNotFoundException(e.getMessage()); + } + } + + throw new FileNotFoundException(projectName + " jar could not be located in the current runtime classpath"); + } + + public static ISourceLocation resolveCurrentRascalRuntimeJar() throws IOException { + return resolveProjectOnClasspath("rascal"); + } + private static ISourceLocation inferProjectRoot(ISourceLocation member) { ISourceLocation current = member; URIResolverRegistry reg = URIResolverRegistry.getInstance(); @@ -437,181 +397,290 @@ private static ISourceLocation inferProjectRoot(ISourceLocation member) { return current; } - /** - * This will create a PathConfig by learning from the MANIFEST/RASCAL.MF file where the sources - * are, which libraries to reference and which classpath entries to add. If this PathConfig is - * for the interpreter it adds more folders to the source path than if its for the compiler. + + public PathConfig parse(String pathConfigString) throws IOException { + try { + IConstructor cons = (IConstructor) new StandardTextReader().read(vf, store, PathConfigType, new StringReader(pathConfigString)); + IWithKeywordParameters kwp = cons.asWithKeywordParameters(); + + IList srcs = (IList) kwp.getParameter("srcs"); + IList libs = (IList) kwp.getParameter("libs"); + IList ignores = (IList) kwp.getParameter("ignores"); + ISourceLocation generated = (ISourceLocation) kwp.getParameter("generatedSources"); + IList messages = (IList) kwp.getParameter("message"); + + ISourceLocation bin = (ISourceLocation) kwp.getParameter("bin"); + + PathConfig pcfg = new PathConfig( + srcs != null ? srcs : vf.list(), + libs != null ? libs : vf.list(), + bin != null ? bin : URIUtil.rootLocation("cwd"), + ignores != null ? ignores : vf.list(), + generated != null ? generated : null, + messages != null ? messages : vf.list() + ); + + return pcfg; + } + catch (FactTypeUseException e) { + throw new IOException(e); + } + } + + /** + * This creates a PathConfig instance by learning from the `MANIFEST/RASCAL.MF` file where the sources + * are, and from `pom.xml` which libraries to reference. If this PathConfig is + * for the interpreter it adds more folders to the source path than if its for the compiler. * - * If library dependencies exist for open projects in the same IDE, via the lib://libName, project://libName - * correspondence, then the target and source folders of the projects are added rather then - * the jar files. For compiler configs this works differently than for interpreter configs. - * The latter adds source folders to the sources while the former adds target folders to the libraries. - * - * @param manifest the source location of the folder which contains MANIFEST/RASCAL.MF. - * @return - * @throws URISyntaxException - */ - public static PathConfig fromSourceProjectRascalManifest(ISourceLocation manifestRoot, RascalConfigMode mode) throws IOException { + * In the future + * we'd like the source folders also configured by the pom.xml but for now we read it from RASCAL.MF for + * the sake of efficiency (reading pom.xml requires an XML parse and DOM traversal.) Also we need + * some level of backward compatibility until everybody has moved to using pom.xml. + * + * If library dependencies exist for _open_ projects in the same IDE, via the `project://` + * correspondence with `mvn://~~`, then the target and source folders of + * those projects are added to the configuration instead of the jar files. + * For compiler configs this works differently than for interpreter configs. + * The latter adds source folders to the `srcs` while the former adds target folders to the `libs`. + * + * If the current project is the rascal project itself, then precautions are taken to avoid double + * entries in libs and srcs, always promoting the current project over the released rascal version. + * + * If the current project depends on a rascal project (e.g. for compiling Java code against Rascal's + * and vallang's run-time classes), then this dependency is ignored and the current JVM's rascal version + * is used instead. The literal jar file is put in the pathConfig for transparancy's sake, and a + * warning is added to the `messages` list. + * + * This code also checks for existence of the actual jar files and source folders that are depended on. + * If the files or folders do not exist, an an error is added to the messages field. + * + * Clients of this method must promote the messages list to a UI facing log, such as the diagnostics + * or problems view in an IDE, an error LOG for a CI and stderr or stdout for console applications. + * + * @param manifest the source location of the folder which contains MANIFEST/RASCAL.MF. + * @param RascalConfigMode.INTERPRETER | RascalConfigMode.COMPILER + * @return a PathConfig instance, fully informed to start initializing a Rascal compiler or interpreter, and including a list of revelant info, warning and error messages. + * @throws nothing, because all errors are collected in a messages field of the PathConfig. + */ + public static PathConfig fromSourceProjectRascalManifest(ISourceLocation manifestRoot, RascalConfigMode mode) { RascalManifest manifest = new RascalManifest(); URIResolverRegistry reg = URIResolverRegistry.getInstance(); - Set loaderSchemes = reg.getRegisteredClassloaderSchemes(); IRascalValueFactory vf = IRascalValueFactory.getInstance(); String projectName = manifest.getProjectName(manifestRoot); IListWriter libsWriter = vf.listWriter(); IListWriter srcsWriter = vf.listWriter(); - IListWriter classloaders = vf.listWriter(); - Map mavenLibs = new HashMap<>(); + IListWriter messages = vf.listWriter(); if (!projectName.equals("rascal")) { // always add the standard library but not for the project named "rascal" // which contains the source of the standard library - libsWriter.append(URIUtil.correctLocation("lib", "rascal", "")); + try { + libsWriter.append(resolveCurrentRascalRuntimeJar()); + } + catch (IOException e) { + messages.append(Messages.error(e.getMessage(), manifestRoot)); + } } - // target classes always go first - ISourceLocation target = URIUtil.correctLocation("target", projectName, "/"); - classloaders.append(target); + ISourceLocation target = URIUtil.correctLocation("project", projectName, "target/classes"); + ISourceLocation generatedSources = URIUtil.correctLocation("project", projectName, "target/generatedSources"); - // for the Rascal run-time - classloaders.append(URIUtil.correctLocation("system", "", "")); + // This later holds a location of the boot project rascal, in case we depend on that directly in the pom.xml + // Depending on which mode we are in, the configuration will run more (version) sanity checks and configure + // the `libs` differently; all to avoid implicit duplicate and/or inconsistent entries on the `libs` path. + ISourceLocation rascalProject = null; - IList mavenClasspath = getPomXmlCompilerClasspath(manifestRoot); - - // the dependencies in the back - classloaders.appendAll(mavenClasspath); + try { + IList mavenClasspath = getPomXmlCompilerClasspath(manifestRoot); - // this collects Rascal libraries we can find in maven dependencies - for (IValue elem : mavenClasspath) { - ISourceLocation dep = (ISourceLocation) elem; - String libProjectName = manifest.getManifestProjectName(manifest.manifest(dep)); - - if (libProjectName != null) { - mavenLibs.put(libProjectName, JarURIResolver.jarify(dep)); - } - } - - for (String lib : manifest.getRequiredLibraries(manifestRoot)) { - try { - ISourceLocation jar = lib.startsWith("|") ? parseSourceLocation(lib) : URIUtil.getChildLocation(manifestRoot, lib); - ISourceLocation projectLoc = URIUtil.correctLocation("project", jar.getAuthority(), "/"); - - assert jar != null; + // This processes Rascal libraries we can find in maven dependencies, + // adding them to libs or srcs depending on which mode we are in; interpreted or compiled. + // + // * If a current project is open with the same name, we defer to its + // srcs (interpreter mode only) and target folder (both modes) instead, + // for easy development of cross-project features in the IDE. + // * Only the rascal project itself is never used from source project, to avoid + // complex bootstrapping situations. + + + for (IValue elem : mavenClasspath) { + ISourceLocation dep = (ISourceLocation) elem; + String libProjectName = manifest.getManifestProjectName(manifest.manifest(dep)); + ISourceLocation projectLoc = URIUtil.correctLocation("project", libProjectName, ""); + + if (libProjectName.equals("rascal")) { + rascalProject = dep; + } - if (jar.getScheme().equals("lib") && reg.exists(projectLoc)) { - // library dependency to open peer project in the workspace + // Rascal LSP is special because the VScode extension pre-loads it into the parametric DSL VM. + // If the version is different, then the debugger may point to the wrong code, and also the Rascal + // IDE features like "jump-to-definition" could be off. + if (libProjectName.equals("rascal-lsp")) { + try { + var loadedRascalLsp = resolveProjectOnClasspath("rascal-lsp"); - PathConfig childConfig = fromSourceProjectRascalManifest(projectLoc, mode); + try (InputStream in = reg.getInputStream(loadedRascalLsp); InputStream in2 = reg.getInputStream(dep)) { + var version = new Manifest(in).getMainAttributes().getValue("Specification-Version"); + var otherVersion = new Manifest(in2).getMainAttributes().getValue("Specification-Version"); - switch (mode) { - case INTERPETER: - srcsWriter.appendAll(childConfig.getSrcs()); - break; - case COMPILER: - libsWriter.append(setTargetScheme(projectLoc)); - break; + if (version.equals(otherVersion)) { + messages.append(Messages.warning("Pom.xml dependency on rascal-lsp has version " + otherVersion + " while the effective version in the VScode extension is " + version + ". This can have funny effects in the IDE while debugging or code browsing.", getPomXmlLocation(manifestRoot))); + } + } + } + catch (FileNotFoundException e) { + // this is ok. there is not a duplicate presence of rascal-lsp. } - // TODO: do we really want to expose all transitive libraries to the type-checker? - libsWriter.appendAll(childConfig.getLibs()); - classloaders.appendAll(childConfig.getClassloaders()); } - else { - ISourceLocation jarLoc = jar; - - if (jar.getScheme().equals("lib")) { - String libraryName = jar.getAuthority(); - if (libraryName.equals("rascal")) { - // ignore ourselves - continue; - } - ISourceLocation libraryLoc = mavenLibs.get(libraryName); - if (libraryLoc != null) { - jarLoc = libraryLoc; + if (libProjectName != null) { + if (reg.exists(projectLoc) && dep != rascalProject) { + // The project we depend on is available in the current workspace. + // so we configure for using the current state of that project. + PathConfig childConfig = fromSourceProjectRascalManifest(projectLoc, mode); + + switch (mode) { + case INTERPRETER: + srcsWriter.appendAll(childConfig.getSrcs()); + libsWriter.append(childConfig.getBin()); + break; + case COMPILER: + libsWriter.append(setTargetScheme(projectLoc)); + break; } - } - if (!reg.exists(jarLoc)) { - throw new FileNotFoundException(jarLoc.toString()); - } - - switch (mode) { - case COMPILER: - libsWriter.append(jarLoc); - break; - case INTERPETER: - addLibraryToSourcePath(manifest, reg, srcsWriter, jarLoc); - break; - default: - throw new IOException("unknown configuration mode: " + mode); - } + // libraries are transitively collected + libsWriter.appendAll(childConfig.getLibs()); - // if this is not a jar file from the pom, its a new place to find classes - if (jar == jarLoc && loaderSchemes.contains(jar.getScheme())) { - classloaders.append(jar); + // error messages are transitively collected + messages.appendAll(childConfig.getMessages()); + } + else if (dep == rascalProject) { + // not adding it again (we added rascal already above) + } + else { + // just a pre-installed dependency in the local maven repository + if (!reg.exists(dep)) { + messages.append(Messages.error("Declared dependency does not exist: " + dep, getPomXmlLocation(manifestRoot))); + } + else { + switch (mode) { + case COMPILER: + libsWriter.append(dep); + break; + case INTERPRETER: + libsWriter.append(dep); + addLibraryToSourcePath(reg, srcsWriter, messages, dep); + break; + default: + messages.append(Messages.error("Can not recognize configuration mode (should be COMPILER or INTERPRETER):" + mode, getRascalMfLocation(manifestRoot))); + } + } } } } - catch (StackOverflowError e) { - // cyclic project dependencies may cause a stackoverflow - throw new IOException("WARNING: cyclic project dependency between projects " + projectName + " and " + lib, e); + } + catch (IOException e) { + messages.append(Messages.warning(e.getMessage(), getPomXmlLocation(manifestRoot))); + } + + try { + if (!projectName.equals("rascal") && rascalProject == null) { + // always add the standard library but not for the project named "rascal" + // which contains the (source of) the standard library, and if we already + // have a dependency on the rascal project we don't add it here either. + var rascalLib = resolveCurrentRascalRuntimeJar(); + messages.append(Messages.info("Effective rascal library: " + rascalLib, getPomXmlLocation(manifestRoot))); + libsWriter.append(rascalLib); } - catch (IOException e) { - System.err.println("WARNING: could not resolve dependency on: " + lib + " because: " + e.getMessage()); - continue; + else if (projectName.equals("rascal")) { + messages.append(Messages.info("detected rascal self-application", getPomXmlLocation(manifestRoot))); + } + else if (rascalProject != null) { + // The Rascal interpreter can not escape its own classpath, whether + // or not we configure a different version in the current project's + // pom.xml or not. So that pom dependency is always ignored! + + // We check this also in COMPILED mode, for the sake of consistency, + // but it is not strictly necessary since the compiler can check and compile + // against any standard library on the libs path, even if it's running + // itself against a different rascal runtime and standard library. + + RascalManifest rmf = new RascalManifest(); + var builtinVersion = RascalManifest.getRascalVersionNumber(); + var dependentRascalProject = reg.logicalToPhysical(rascalProject); + var dependentVersion = rmf.getManifestVersionNumber(dependentRascalProject); + + if (!builtinVersion.equals(dependentVersion)) { + messages.append(Messages.info("Effective rascal version: " + builtinVersion, getPomXmlLocation(manifestRoot))); + messages.append(Messages.warning("Different rascal dependency is not used: " + dependentVersion, getPomXmlLocation(manifestRoot))); + } + } + + ISourceLocation projectLoc = URIUtil.correctLocation("project", projectName, ""); + + if (!projectLoc.equals(manifestRoot) && !projectName.equals(URIUtil.getLocationName(manifestRoot))) { + messages.append(Messages.error("Project-Name in RASCAL.MF (" + projectName + ") should be equal to folder name (" + URIUtil.getLocationName(manifestRoot) + ")", getRascalMfLocation(manifestRoot))); + } + + try (InputStream mfi = manifest.manifest(manifestRoot)) { + var reqlibs = new Manifest(mfi).getMainAttributes().getValue("Require-Libraries"); + if (reqlibs != null && !reqlibs.isEmpty()) { + messages.append(Messages.info("Require-Libraries in RASCAL.MF are not used anymore. Please use Maven dependencies in pom.xml.", getRascalMfLocation(manifestRoot))); + } } } - + catch (IOException e) { + messages.append(Messages.error(e.getMessage(), getRascalMfLocation(manifestRoot))); + } + + for (String srcName : manifest.getSourceRoots(manifestRoot)) { - srcsWriter.append(URIUtil.getChildLocation(manifestRoot, srcName)); + var srcFolder = URIUtil.getChildLocation(manifestRoot, srcName); + + if (!reg.exists(srcFolder) || !reg.isDirectory(srcFolder)) { + messages.append(Messages.error("Source folder " + srcFolder + " does not exist.", getRascalMfLocation(rascalProject))); + } + + srcsWriter.append(srcFolder); } return new PathConfig( srcsWriter.done(), libsWriter.done(), target, - vf.list(), - getDefaultJavaCompilerPathList(), - classloaders.done()); - } - - public static class LibResolverForMavenDependencies implements ILogicalSourceLocationResolver { - private final String libraryName; - private final ISourceLocation jarLoc; - - public LibResolverForMavenDependencies(String libraryName, ISourceLocation jarLoc) { - this.libraryName = libraryName; - this.jarLoc = jarLoc; - } + vf.list(), + generatedSources, + messages.done()); + } - @Override - public ISourceLocation resolve(ISourceLocation input) throws IOException { - if (!libraryName.equals(input.getAuthority())) { - return input; - } - return URIUtil.getChildLocation(jarLoc, input.getPath()); + private static void addLibraryToSourcePath(URIResolverRegistry reg, IListWriter srcsWriter, IListWriter messages, ISourceLocation jar) { + if (!reg.exists(URIUtil.getChildLocation(jar, RascalManifest.META_INF_RASCAL_MF))) { + // skip all the non Rascal libraries + return; } - @Override - public String scheme() { - return "lib"; - } + + var manifest = new RascalManifest(); - @Override - public String authority() { - return libraryName; + // the rascal dependency leads to a dependency on the std:/// location, somewhere _inside_ of the rascal jar + if (manifest.getProjectName(jar).equals("rascal")) { + srcsWriter.append(URIUtil.rootLocation("std")); + return; } - } - private static void addLibraryToSourcePath(RascalManifest manifest, URIResolverRegistry reg, IListWriter srcsWriter, - ISourceLocation jar) { boolean foundSrc = false; + for (String src : manifest.getSourceRoots(jar)) { ISourceLocation srcLib = URIUtil.getChildLocation(jar, src); if (reg.exists(srcLib)) { srcsWriter.append(srcLib); foundSrc = true; } + else { + messages.append(Messages.error(srcLib + " source folder does not exist.", URIUtil.getChildLocation(jar, RascalManifest.META_INF_RASCAL_MF))); + } } if (!foundSrc) { @@ -629,65 +698,75 @@ private static ISourceLocation setTargetScheme(ISourceLocation projectLoc) { return projectLoc; } } - + + private static ISourceLocation getRascalMfLocation(ISourceLocation project) { + return URIUtil.getChildLocation(project, RascalManifest.META_INF_RASCAL_MF); + } + + private static ISourceLocation getPomXmlLocation(ISourceLocation project) { + try { + ISourceLocation pomxml = URIUtil.getChildLocation(project, "pom.xml"); + return URIResolverRegistry.getInstance().logicalToPhysical(pomxml); + } + catch (IOException e) { + assert false : e.getMessage(); + return URIUtil.correctLocation("unknown", "", "pom.xml"); + } + } + /** * See if there is a pom.xml and extract the compile-time classpath from a mvn run * if there is such a file. * @param manifestRoot * @return + * @throws IOException */ - private static IList getPomXmlCompilerClasspath(ISourceLocation manifestRoot) { - try { - ISourceLocation pomxml = URIUtil.getChildLocation(manifestRoot, "pom.xml"); - pomxml = URIResolverRegistry.getInstance().logicalToPhysical(pomxml); - manifestRoot = URIResolverRegistry.getInstance().logicalToPhysical(manifestRoot); + private static IList getPomXmlCompilerClasspath(ISourceLocation manifestRoot) throws IOException { + var pomxml = getPomXmlLocation(manifestRoot); + manifestRoot = URIResolverRegistry.getInstance().logicalToPhysical(manifestRoot); - if (!"file".equals(manifestRoot.getScheme())) { - return vf.list(); - } + if (!"file".equals(manifestRoot.getScheme())) { + return vf.list(); + } - if (!URIResolverRegistry.getInstance().exists(pomxml)) { - return vf.list(); - } + if (!URIResolverRegistry.getInstance().exists(pomxml)) { + return vf.list(); + } - String mvnCommand = computeMavenCommandName(); + String mvnCommand = computeMavenCommandName(); - installNecessaryMavenPlugins(mvnCommand); + installNecessaryMavenPlugins(mvnCommand); - // Note how we try to do this "offline" using the "-o" flag - ProcessBuilder processBuilder = new ProcessBuilder(mvnCommand, - "--batch-mode", - "-o", - "dependency:build-classpath", - "-DincludeScope=compile", - trustStoreFix() - ); + // Note how we try to do this "offline" using the "-o" flag + ProcessBuilder processBuilder = new ProcessBuilder(mvnCommand, + "--batch-mode", + "-o", + "dependency:build-classpath", + "-DincludeScope=compile", + trustStoreFix() + ); - processBuilder.directory(new File(manifestRoot.getPath())); - processBuilder.environment().put("JAVA_HOME", System.getProperty("java.home", System.getenv("JAVA_HOME"))); + processBuilder.directory(new File(manifestRoot.getPath())); + processBuilder.environment().put("JAVA_HOME", System.getProperty("java.home", System.getenv("JAVA_HOME"))); - Process process = processBuilder.start(); + Process process = processBuilder.start(); - try (BufferedReader processOutputReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { - return processOutputReader.lines() - .filter(line -> !line.startsWith("[")) - .filter(line -> !line.contains("-----")) - .flatMap(line -> Arrays.stream(line.split(File.pathSeparator))) - .filter(fileName -> new File(fileName).exists()) - .map(elem -> { - try { - return URIUtil.createFileLocation(elem); - } - catch (URISyntaxException e) { - return null; - } - }) - .filter(e -> e != null) - .collect(vf.listWriter()); - } - } - catch (IOException e) { - return vf.list(); + try (BufferedReader processOutputReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + return processOutputReader.lines() + .filter(line -> !line.startsWith("[")) + .filter(line -> !line.contains("-----")) + .flatMap(line -> Arrays.stream(line.split(File.pathSeparator))) + .filter(fileName -> new File(fileName).exists()) + .map(elem -> { + try { + return MavenRepositoryURIResolver.mavenize(URIUtil.createFileLocation(elem)); + } + catch (URISyntaxException e) { + return null; + } + }) + .filter(e -> e != null) + .collect(vf.listWriter()); } } @@ -704,7 +783,33 @@ private static String computeMavenCommandName() { } } - private static void installNecessaryMavenPlugins(String mvnCommand) { + /** + * Prints what users need to know about how the interpreter is configured with this PathConfig + */ + public void printInterpreterConfigurationStatus(PrintWriter out) { + out.println("Module paths:"); + getSrcs().forEach((f) -> out.println(" ".repeat(4) + f)); + out.println("JVM library classpath:"); + getLibsAndTarget().forEach((l) -> out.println(" ".repeat(4) + l)); + out.flush(); + } + + /** + * Prints what users need to know about how the compiler is configured with this PathConfig + */ + public void printCompilerConfigurationStatus(PrintWriter out) { + out.println("Source paths:"); + getSrcs().forEach((f) -> out.println(" ".repeat(4) + f)); + out.println("Compiler target folder:"); + out.println(" ".repeat(4) + getBin()); + out.println("Compiler generated sources folder:"); + out.println(" ".repeat(4) + getGeneratedSources()); + out.println("Rascal and Java library classpath:"); + getLibsAndTarget().forEach((l) -> out.println(" ".repeat(4) + l)); + out.flush(); + } + + private static void installNecessaryMavenPlugins(String mvnCommand) throws IOException { try { ProcessBuilder processBuilder = new ProcessBuilder(mvnCommand, "-q", @@ -720,8 +825,8 @@ private static void installNecessaryMavenPlugins(String mvnCommand) { throw new IOException("mvn dependency:get returned non-zero"); } } - catch (IOException | InterruptedException e) { - System.err.println("[WARNING] Could not install exec-maven-plugin; classpath resolution may be incomplete hereafter: " + e.getMessage()); + catch (InterruptedException e) { + throw new IOException(e); } } @@ -732,81 +837,55 @@ private static String trustStoreFix() { public ISourceLocation getBin() { return bin; } - - String makeFileName(String qualifiedModuleName, String extension) { - return qualifiedModuleName.replaceAll("::", "/") + "." + extension; - } - - ISourceLocation getModuleLoc(String qualifiedModuleName) throws IOException { - ISourceLocation result = resolveModule(qualifiedModuleName); - if(result == null){ - throw new IOException("Module " + qualifiedModuleName + " not found"); - } - return result; - } - - public ISourceLocation resolveModule(String qualifiedModuleName) { - String fileName = makeFileName(qualifiedModuleName); + + String makeFileName(String qualifiedModuleName, String extension) { + return qualifiedModuleName.replaceAll("::", "/") + "." + extension; + } + + public String getModuleName(ISourceLocation moduleLoc) throws IOException{ + String modulePath = moduleLoc.getPath(); + if(!modulePath.endsWith(".rsc")){ + throw new IOException("Not a Rascal source file: " + moduleLoc); + } + + if (moduleLoc.getScheme().equals("std") || moduleLoc.getScheme().equals("mvn")) { + return pathToModulename(modulePath, "/"); + } + for(ISourceLocation dir : srcs){ - ISourceLocation fileLoc; - try { - getFullURI(fileName, dir); - fileLoc = getFullURI(fileName, dir); - if(URIResolverRegistry.getInstance().exists(fileLoc)){ - return fileLoc; - } - } - catch (URISyntaxException e) { - return null; + if(modulePath.startsWith(dir.getPath()) && moduleLoc.getScheme() == dir.getScheme()){ + return pathToModulename(modulePath, dir.getPath()); } } - return null; - } - - public String getModuleName(ISourceLocation moduleLoc) throws IOException{ - String modulePath = moduleLoc.getPath(); - if(!modulePath.endsWith(".rsc")){ - throw new IOException("Not a Rascal source file: " + moduleLoc); - } - - if (moduleLoc.getScheme().equals("std") || moduleLoc.getScheme().equals("lib")) { - return pathToModulename(modulePath, "/"); - } - - for(ISourceLocation dir : srcs){ - if(modulePath.startsWith(dir.getPath()) && moduleLoc.getScheme() == dir.getScheme()){ - return pathToModulename(modulePath, dir.getPath()); - } - } - - for (ISourceLocation dir : libs) { - if(modulePath.startsWith(dir.getPath()) && moduleLoc.getScheme() == dir.getScheme()){ + + for (ISourceLocation dir : libs) { + if(modulePath.startsWith(dir.getPath()) && moduleLoc.getScheme() == dir.getScheme()){ return pathToModulename(modulePath, dir.getPath()); } - } - - throw new IOException("No module name found for " + moduleLoc + "\n" + this); - - } + } + + throw new IOException("No module name found for " + moduleLoc + "\n" + this); + + } private String pathToModulename(String modulePath, String folder) { String moduleName = modulePath.replaceFirst(folder, "").replace(".rsc", ""); if(moduleName.startsWith("/")){ moduleName = moduleName.substring(1, moduleName.length()); } - return moduleName.replace("/", "::"); + return moduleName.replace(Configuration.RASCAL_PATH_SEP, Configuration.RASCAL_MODULE_SEP); } - - private String moduleToDir(String module) { + + private String moduleToDir(String module) { return module.replaceAll(Configuration.RASCAL_MODULE_SEP, Configuration.RASCAL_PATH_SEP); } - + private ISourceLocation getFullURI(String path, ISourceLocation dir) throws URISyntaxException { return URIUtil.getChildLocation(dir, path); } - - public List listModuleEntries(String moduleRoot) { - assert !moduleRoot.endsWith("::"); + + public List listModuleEntries(String moduleRoot) { + assert !moduleRoot.endsWith(Configuration.RASCAL_MODULE_SEP); final URIResolverRegistry reg = URIResolverRegistry.getInstance(); try { String modulePath = moduleToDir(moduleRoot); @@ -825,7 +904,7 @@ public List listModuleEntries(String moduleRoot) { } else if (module.indexOf('.') == -1 && reg.isDirectory(getFullURI(module, full))) { // a sub folder path - result.add(module + "::"); + result.add(module + Configuration.RASCAL_MODULE_SEP); } } } @@ -841,28 +920,37 @@ else if (module.indexOf('.') == -1 && reg.isDirectory(getFullURI(module, full))) return null; } } - - public IConstructor asConstructor() { - Map config = new HashMap<>(); - - config.put("srcs", getSrcs()); - config.put("ignores", getIgnores()); - config.put("bin", getBin()); - config.put("libs", getLibs()); - config.put("javaCompilerPath", getJavaCompilerPath()); - config.put("classloaders", getClassloaders()); - - return vf.constructor(pathConfigConstructor, new IValue[0], config); - } - - public String toString(){ - StringWriter w = new StringWriter(); - w.append("srcs: ").append(getSrcs().toString()).append("\n") - .append("ignores: ").append(getIgnores().toString()).append("\n") - .append("libs: ").append(getLibs().toString()).append("\n") - .append("bin: ").append(getBin().toString()).append("\n") - .append("classpath: ").append(getJavaCompilerPath().toString()).append("\n") - .append("loaders: ").append(getClassloaders().toString()).append("\n") + + /** + * Convert PathConfig Java object to pathConfig Rascal constructor for use + * in Rascal code or for serialization and printing. + */ + public IConstructor asConstructor() { + Map config = new HashMap<>(); + + config.put("srcs", getSrcs()); + config.put("ignores", getIgnores()); + config.put("bin", getBin()); + config.put("libs", getLibs()); + config.put("generatedSources", getGeneratedSources()); + config.put("messages", getMessages()); + + return vf.constructor(pathConfigConstructor, new IValue[0], config); + } + + /** + * Overview of the contents of the current configuration for debugging purposes. + * Not necessarily for end-user UI, although it's better than nothing. + */ + public String toString(){ + StringWriter w = new StringWriter(); + w.append("Path configuration items:") + .append("srcs: ").append(getSrcs().toString()).append("\n") + .append("ignores: ").append(getIgnores().toString()).append("\n") + .append("libs: ").append(getLibs().toString()).append("\n") + .append("bin: ").append(getBin().toString()).append("\n") + .append("generatedSources:").append(getGeneratedSources().toString()).append("\n") + .append("messages: ").append(getMessages().toString()).append("\n") ; return w.toString(); diff --git a/src/org/rascalmpl/library/util/Reflective.java b/src/org/rascalmpl/library/util/Reflective.java index 44bb23ce3b1..4a0e51ce106 100644 --- a/src/org/rascalmpl/library/util/Reflective.java +++ b/src/org/rascalmpl/library/util/Reflective.java @@ -81,6 +81,15 @@ public IString getRascalVersion() { return values.string(RascalManifest.getRascalVersionNumber()); } + public ISourceLocation resolveProjectOnClasspath(IString projectName) { + try { + return PathConfig.resolveProjectOnClasspath(projectName.getValue()); + } + catch (IOException e) { + throw RuntimeExceptionFactory.io(e.getMessage()); + } + } + public IString getLineSeparator() { return values.string(System.lineSeparator()); } @@ -88,7 +97,7 @@ public IString getLineSeparator() { public IConstructor getProjectPathConfig(ISourceLocation projectRoot, IConstructor mode) { try { if (URIResolverRegistry.getInstance().exists(projectRoot)) { - return PathConfig.fromSourceProjectRascalManifest(projectRoot, mode.getName().equals("compiler") ? RascalConfigMode.COMPILER : RascalConfigMode.INTERPETER).asConstructor(); + return PathConfig.fromSourceProjectRascalManifest(projectRoot, mode.getName().equals("compiler") ? RascalConfigMode.COMPILER : RascalConfigMode.INTERPRETER).asConstructor(); } else { throw new FileNotFoundException(projectRoot.toString()); diff --git a/src/org/rascalmpl/library/util/Reflective.rsc b/src/org/rascalmpl/library/util/Reflective.rsc index 07bfeb7c626..bdb354a73bc 100644 --- a/src/org/rascalmpl/library/util/Reflective.rsc +++ b/src/org/rascalmpl/library/util/Reflective.rsc @@ -18,6 +18,7 @@ import List; import ParseTree; import String; import util::FileSystem; +import Message; import lang::rascal::\syntax::Rascal; import lang::manifest::IO; @@ -41,15 +42,28 @@ data RascalConfigMode | interpreter() ; +@synopsis{General configuration (via path references) of a compiler or interpreter.} +@description{ +A PathConfig is the result of dependency resolution and other configuration steps. Typically, +IDEs produce the information to fill a PathConfig, such that language tools can consume it +transparantly. A PathConfig is also a log of the configuration process. + +* `srcs` list of root directories to search for source files; to interpret or to compile. +* `ignores` list of directories and files to not compile or not interpret (these are typically subtracted from the `srcs` tree, or skipped when the compiler arives there.) +* `bin` is the target root directory for the output of a compiler. Typically this directory would be linked into a zip or a jar or an executable file later. +* `libs` is a list of binary dependency files (typically jar files or target folders) on other projects, for checking and linking purposes. +* `generatedSources` is where generated (intermediate) source code that has to be compiled further is located. +* `messages` is a list of info, warning and error messages informing end-users about the quality of the configuration process. Typically missing dependencies would be reported here, and clashing versions. +} data PathConfig - // Defaults should be in sync with org.rascalmpl.library.util.PathConfig - = pathConfig(list[loc] srcs = [|std:///|], // List of directories to search for source files - list[loc] ignores = [], // List of locations to ignore from the source files - loc bin = |home:///bin/|, // Global directory for derived files outside projects - list[loc] libs = [|lib://rascal/|], // List of directories to search source for derived files - list[loc] javaCompilerPath = [], // TODO: must generate the same defaults as in PathConfig - list[loc] classloaders = [|system:///|] // TODO: must generate the same defaults as in PathConfig - ); + = pathConfig( + list[loc] srcs = [], + list[loc] ignores = [], + loc bin = |unknown:///|, + loc generatedSources = |unknown:///|, + list[loc] libs = [], + list[Message] messages = [] + ); data RascalManifest = rascalManifest( @@ -75,7 +89,25 @@ data JavaBundleManifest list[str] \Bundle-ClassPath = [], list[str] \Import-Package = [] ); - + +// @synopsis{Makes the location of a jar file explicit, based on the project name} +// @description{ +// The classpath of the current JVM is searched and jar files are searched that contain +// META-INF/MANIFEST.MF file that match the given `projectName`. +// } +// @benefits{ +// * This is typically used to link bootstrap libraries such as rascal.jar and rascal-lsp.jar +// into testing ((PathConfig))s. +// * The classpath is not used implicitly in this way, but rather explicitly. This helps +// in making configuration issues tractable. +// * The resulting `loc` value can be used to configure a ((PathConfig)) instance directly. +// } +@javaClass{org.rascalmpl.library.util.Reflective} +java loc resolveProjectOnClasspath(str projectName); + +// @synopsis{Makes the location of the currently running rascal jar explicit.} +loc resolvedCurrentRascalJar() = resolveDependencyFromResourcesOnCurrentClasspath("rascal"); + loc metafile(loc l) = l + "META-INF/RASCAL.MF"; @synopsis{Converts a PathConfig and replaces all references to roots of projects or bundles @@ -97,9 +129,6 @@ PathConfig applyManifests(PathConfig cfg) { cfg.libs = [*expandlibs(p) | p <- cfg.libs]; cfg.bin = expandBin(cfg.bin); - // TODO: here we add features for Require-Libs by searching in a repository of installed - // jars. This has to be resolved recursively. - return cfg; } @@ -360,10 +389,10 @@ void newRascalProject(loc folder, str group="org.rascalmpl", str version="0.1.0- throw "Folder should have only lowercase characters, digits and dashes from [a-z0-9\\-]"; } - mkDirectory(pomFile(folder).parent); - writeFile(pomFile(folder), pomXml(name, group, version)); - mkDirectory(metafile(folder).parent); - writeFile(metafile(folder), rascalMF(name)); + + newRascalPomFile(folder, name=name, group=group, version=version); + newRascalMfFile(folder, name=name); + mkDirectory(folder + "src/main/rascal"); writeFile((folder + "src/main/rascal") + "Main.rsc", emptyModule()); } @@ -384,8 +413,34 @@ private str rascalMF(str name) = "Manifest-Version: 0.0.1 'Project-Name: 'Source: src/main/rascal - 'Require-Libraries: - "; + ' + '"; + +@synopsis{Create a new META-INF/RASCAL.MF file.} +@description{ +The `folder` parameter should point to the root of a project folder. +The name of the project will be derived from the name of that folder +and a META-INF/RASCAL.MF file will be generated and written. + +The folder is created if it does not exist already. +} +void newRascalMfFile(loc folder, str name=folder.file) { + mkDirectory(rascalMF(name).parent); + writeFile(metafile(folder), rascalMF(name)); +} + +@synopsis{Create a new pom.xml for a Rascal project} +@description{ +The `folder` parameter should point to the root of a project folder. +The name of the project will be derived from the name of that folder +and a pom.xml file will be generated and written. + +The folder is created if it does not exist already. +} +void newRascalPomFile(loc folder, str name=folder.file, str group="org.rascalmpl", str version="0.1.0-SNAPSHOT") { + mkDirectory(folder); + writeFile(pomFile(folder), pomXml(name, group, version)); +} private str pomXml(str name, str group, str version) = "\ diff --git a/src/org/rascalmpl/repl/TerminalProgressBarMonitor.java b/src/org/rascalmpl/repl/TerminalProgressBarMonitor.java index e155bc26c48..1467dccb770 100644 --- a/src/org/rascalmpl/repl/TerminalProgressBarMonitor.java +++ b/src/org/rascalmpl/repl/TerminalProgressBarMonitor.java @@ -19,6 +19,7 @@ import jline.Terminal; import jline.internal.Configuration; + /** * The terminal progress bar monitor wraps the standard output stream to be able to monitor * output and keep the progress bars at the same place in the window while other prints happen @@ -439,14 +440,6 @@ static String moveUp(int n) { return "\u001B[" + n + "F"; } - static String overlined() { - return "\u001B[53m"; - } - - static String underlined() { - return "\u001B[4m"; - } - public static String printCursorPosition() { return "\u001B[6n"; } @@ -458,11 +451,7 @@ public static String noBackground() { public static String normal() { return "\u001B[0m"; } - - public static String lightBackground() { - return "\u001B[48;5;250m"; - } - + static String moveDown(int n) { return "\u001B[" + n + "E"; } diff --git a/src/org/rascalmpl/semantics/dynamic/Module.java b/src/org/rascalmpl/semantics/dynamic/Module.java index 00b88c2cf80..8a58e7a6e3f 100644 --- a/src/org/rascalmpl/semantics/dynamic/Module.java +++ b/src/org/rascalmpl/semantics/dynamic/Module.java @@ -26,8 +26,6 @@ import org.rascalmpl.interpreter.result.Result; import org.rascalmpl.interpreter.result.ResultFactory; import org.rascalmpl.interpreter.utils.Names; -import org.rascalmpl.uri.URIUtil; - import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.IValue; diff --git a/src/org/rascalmpl/shell/CommandOptions.java b/src/org/rascalmpl/shell/CommandOptions.java index 5ef047b73f3..525290e3567 100644 --- a/src/org/rascalmpl/shell/CommandOptions.java +++ b/src/org/rascalmpl/shell/CommandOptions.java @@ -71,12 +71,11 @@ enum OptionType {INT, STR, BOOL, LOCS, LOC}; * */ public class CommandOptions { - private static final String CLASSLOADERS_PATH_CONFIG_OPTION = "classloaders"; - private static final String JAVA_COMPILER_PATH_PATH_CONFIG_OPTION = "javaCompilerPath"; private static final String IGNORES_PATH_CONFIG_OPTION = "ignores"; private static final String BIN_PATH_CONFIG_OPTION = "bin"; private static final String LIB_PATH_CONFIG_OPTION = "lib"; - private static final String PROJECT_PATH_CONFIG_OPTION = "project"; + private static final String GENERATED_SOURCES_PATH_CONFIG_OPTION = "generated-sources"; + private static final String PROJECT_PATH_CONFIG_OPTION = "project"; private static final String SRC_PATH_CONFIG_OPTION = "src"; protected TypeFactory tf; @@ -568,9 +567,8 @@ public PathConfig getPathConfig(PathConfig.RascalConfigMode mode) throws IOExcep return new PathConfig(getCommandLocsOption(SRC_PATH_CONFIG_OPTION), getCommandLocsOption(LIB_PATH_CONFIG_OPTION), getCommandLocOption(BIN_PATH_CONFIG_OPTION), - getCommandLocsOption(IGNORES_PATH_CONFIG_OPTION), - getCommandLocsOption(JAVA_COMPILER_PATH_PATH_CONFIG_OPTION), - getCommandLocsOption(CLASSLOADERS_PATH_CONFIG_OPTION)); + getCommandLocsOption(IGNORES_PATH_CONFIG_OPTION) + ); } } @@ -604,13 +602,9 @@ public CommandOptions pathConfigOptions() { .locsDefault(PathConfig.getDefaultIgnoresList()) .help("Add new ignored locations, use multiple --ignores arguments for multiple locations") - .locsOption(JAVA_COMPILER_PATH_PATH_CONFIG_OPTION) - .locsDefault(PathConfig.getDefaultJavaCompilerPathList()) - .help("Add new java classpath location, use multiple --javaCompilerPath options for multiple locations") - - .locsOption(CLASSLOADERS_PATH_CONFIG_OPTION) - .locsDefault(PathConfig.getDefaultClassloadersList()) - .help("Add new java classloader location, use multiple --classloader options for multiple locations") + .locOption(GENERATED_SOURCES_PATH_CONFIG_OPTION) + .locDefault(PathConfig.getDefaultGeneratedSources()) + .help("Add new target location for generated sources") ; diff --git a/src/org/rascalmpl/shell/ShellEvaluatorFactory.java b/src/org/rascalmpl/shell/ShellEvaluatorFactory.java index 63eafa3da38..095bc7705b9 100644 --- a/src/org/rascalmpl/shell/ShellEvaluatorFactory.java +++ b/src/org/rascalmpl/shell/ShellEvaluatorFactory.java @@ -1,7 +1,6 @@ package org.rascalmpl.shell; import java.io.File; -import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URISyntaxException; @@ -12,6 +11,7 @@ import org.rascalmpl.interpreter.env.ModuleEnvironment; import org.rascalmpl.interpreter.load.StandardLibraryContributor; import org.rascalmpl.interpreter.utils.RascalManifest; +import org.rascalmpl.library.Messages; import org.rascalmpl.library.util.PathConfig; import org.rascalmpl.library.util.PathConfig.RascalConfigMode; import org.rascalmpl.uri.URIResolverRegistry; @@ -68,23 +68,20 @@ public static void configureProjectEvaluator(Evaluator evaluator, ISourceLocatio reg.registerLogical(new ProjectURIResolver(projectRoot, projectName)); reg.registerLogical(new TargetURIResolver(projectRoot, projectName)); - try { - PathConfig pcfg = PathConfig.fromSourceProjectRascalManifest(projectRoot, RascalConfigMode.INTERPETER); - - for (IValue path : pcfg.getSrcs()) { - evaluator.addRascalSearchPath((ISourceLocation) path); - } + PathConfig pcfg = PathConfig.fromSourceProjectRascalManifest(projectRoot, RascalConfigMode.INTERPRETER); - for (IValue path : pcfg.getLibs()) { - evaluator.addRascalSearchPath((ISourceLocation) path); - } - - ClassLoader cl = new SourceLocationClassLoader(pcfg.getClassloaders(), ShellEvaluatorFactory.class.getClassLoader()); - evaluator.addClassLoader(cl); + for (IValue path : pcfg.getSrcs()) { + evaluator.addRascalSearchPath((ISourceLocation) path); } - catch (IOException e) { - System.err.println(e); + + for (IValue path : pcfg.getLibs()) { + evaluator.addRascalSearchPath((ISourceLocation) path); } + + ClassLoader cl = new SourceLocationClassLoader(pcfg.getLibsAndTarget(), ShellEvaluatorFactory.class.getClassLoader()); + evaluator.addClassLoader(cl); + + Messages.write(pcfg.getMessages(), evaluator.getOutPrinter()); } /** diff --git a/src/org/rascalmpl/test/infrastructure/RascalJUnitTestRunner.java b/src/org/rascalmpl/test/infrastructure/RascalJUnitTestRunner.java index e2330988525..5af8f2200c1 100644 --- a/src/org/rascalmpl/test/infrastructure/RascalJUnitTestRunner.java +++ b/src/org/rascalmpl/test/infrastructure/RascalJUnitTestRunner.java @@ -34,6 +34,7 @@ import org.rascalmpl.interpreter.load.StandardLibraryContributor; import org.rascalmpl.interpreter.result.AbstractFunction; import org.rascalmpl.interpreter.utils.RascalManifest; +import org.rascalmpl.library.Messages; import org.rascalmpl.library.util.PathConfig; import org.rascalmpl.library.util.PathConfig.RascalConfigMode; import org.rascalmpl.shell.RascalShell; @@ -108,26 +109,26 @@ public static void configureProjectEvaluator(Evaluator evaluator, ISourceLocatio reg.registerLogical(new TargetURIResolver(projectRoot, projectName)); try { - PathConfig pcfg = PathConfig.fromSourceProjectRascalManifest(projectRoot, RascalConfigMode.INTERPETER); + PathConfig pcfg = PathConfig.fromSourceProjectRascalManifest(projectRoot, RascalConfigMode.INTERPRETER); for (IValue path : pcfg.getSrcs()) { - evaluator.addRascalSearchPath((ISourceLocation) path); } - ClassLoader cl = new SourceLocationClassLoader(pcfg.getClassloaders(), ShellEvaluatorFactory.class.getClassLoader()); + ClassLoader cl = new SourceLocationClassLoader(pcfg.getLibsAndTarget(), ShellEvaluatorFactory.class.getClassLoader()); evaluator.addClassLoader(cl); + + Messages.write(pcfg.getMessages(), evaluator.getOutPrinter()); + } catch (AssertionError e) { e.printStackTrace(); throw e; } - catch (IOException e) { - System.err.println(e); - } } public static ISourceLocation inferProjectRoot(Class clazz) { + try { String file = clazz.getProtectionDomain().getCodeSource().getLocation().getPath(); if (file.endsWith(".jar")) { diff --git a/src/org/rascalmpl/uri/StandardLibraryURIResolver.java b/src/org/rascalmpl/uri/StandardLibraryURIResolver.java index dd748e4a3fc..ddb1514fc75 100644 --- a/src/org/rascalmpl/uri/StandardLibraryURIResolver.java +++ b/src/org/rascalmpl/uri/StandardLibraryURIResolver.java @@ -1,11 +1,60 @@ package org.rascalmpl.uri; -import org.rascalmpl.uri.libraries.ClassResourceInput; +import java.io.IOException; -public class StandardLibraryURIResolver extends ClassResourceInput { +import org.rascalmpl.library.util.PathConfig; +import org.rascalmpl.uri.jar.JarURIResolver; +import io.usethesource.vallang.ISourceLocation; - public StandardLibraryURIResolver() { - super("std", StandardLibraryURIResolver.class, "/org/rascalmpl/library"); +/** + * Provides transparant access to the source code of the one and only standard library + * that should be on the source path of the interpreter, which is contained in the same + * jar as the current interpreter is from. + * + * The std:/// scheme is mainly used by the interpreter, to load library modules; but it is also the location + * of distributed sources of the standard library for use in the debugger. The references + * that the type-checker produces for UI feature in the IDE also depend on this scheme. + * + * This is accomplished by: + * 1. rewriting all the references to the source locations of the library in + * project://rascal/src/org/rascalmpl/library/...` to `std:///...`. This is done by the "packager" + * 2. copying all the .rsc files of the library to the jar in the right location (mvn resources plugin) + * 3. resolving std:/// to the same location inside of the jar + * 4. **not** having more than one standard library in the classpath of the JVM, or more than one + * standard library in the libs path of a PathConfig. + */ +public class StandardLibraryURIResolver implements ILogicalSourceLocationResolver { + private static final ISourceLocation currentRascalJar = + URIUtil.getChildLocation( + JarURIResolver.jarify( + resolveCurrentRascalJar() + ), + "org/rascalmpl/library" + ); + + private static ISourceLocation resolveCurrentRascalJar() { + try { + return PathConfig.resolveCurrentRascalRuntimeJar(); + } + catch (IOException e) { + // this will be reported elsewhere in PathConfig.messages - + return URIUtil.unknownLocation(); + } + } + + @Override + public ISourceLocation resolve(ISourceLocation input) throws IOException { + return URIUtil.getChildLocation(currentRascalJar, input.getPath()); + } + + @Override + public String scheme() { + return "std"; + } + + @Override + public String authority() { + return ""; } } diff --git a/src/org/rascalmpl/uri/URIUtil.java b/src/org/rascalmpl/uri/URIUtil.java index 1b0d4ee5566..f413c6c3f66 100644 --- a/src/org/rascalmpl/uri/URIUtil.java +++ b/src/org/rascalmpl/uri/URIUtil.java @@ -172,21 +172,11 @@ private static ISourceLocation createLocation(String scheme, String authority, String path) throws URISyntaxException { return vf.sourceLocation(scheme, authority, path); } - - private static final URI invalidURI = URI.create("unknown:///"); - /** - * Returns an URI which cannot be read/write to. - * @return - */ - public static URI invalidURI() { - return invalidURI; - } - - private static final ISourceLocation invalidLocation = vf.sourceLocation(invalidURI); + private static final ISourceLocation unknownLocation = rootLocation("unknown"); - public static ISourceLocation invalidLocation() { - return invalidLocation; + public static ISourceLocation unknownLocation() { + return unknownLocation; } /** @@ -337,7 +327,7 @@ public static String getURIName(URI uri) { File file = new File(uri.getPath()); return file.getName(); } - + public static String getLocationName(ISourceLocation uri) { File file = new File(uri.getPath()); return file.getName(); @@ -433,6 +423,32 @@ public static ISourceLocation removeOffset(ISourceLocation prev) { public static ISourceLocation createFromURI(String value) throws URISyntaxException { return vf.sourceLocation(createFromEncoded(value)); } + + /** + * Extracts the extension (the characters after the last . in the file name), + * if any, and otherwise returns the empty string. + * @param loc + * @return + */ + public static String getExtension(ISourceLocation loc) { + String path = loc.getPath(); + boolean endsWithSlash = path.endsWith(URIUtil.URI_PATH_SEPARATOR); + if (endsWithSlash) { + path = path.substring(0, path.length() - 1); + } + + if (path.length() > 1) { + int slashIndex = path.lastIndexOf(URIUtil.URI_PATH_SEPARATOR); + int index = path.substring(slashIndex).lastIndexOf('.'); + + if (index != -1) { + return path.substring(slashIndex + index + 1); + } + } + + return ""; + } + public static ISourceLocation changeExtension(ISourceLocation location, String ext) throws URISyntaxException { String path = location.getPath(); boolean endsWithSlash = path.endsWith(URIUtil.URI_PATH_SEPARATOR); diff --git a/src/org/rascalmpl/uri/classloaders/PathConfigClassLoader.java b/src/org/rascalmpl/uri/classloaders/PathConfigClassLoader.java index f51e91ced27..9b9b1b89b5c 100644 --- a/src/org/rascalmpl/uri/classloaders/PathConfigClassLoader.java +++ b/src/org/rascalmpl/uri/classloaders/PathConfigClassLoader.java @@ -16,11 +16,11 @@ /** * A ClassLoader which finds classes and resources in the - * classloader location list of a PathConfig. @see IClassloaderLocationResolver + * libs location list of a PathConfig. @see IClassloaderLocationResolver * for more information on how we transform ISourceLocations to Classloaders. */ public class PathConfigClassLoader extends SourceLocationClassLoader { public PathConfigClassLoader(PathConfig pcfg, ClassLoader parent) { - super(pcfg.getClassloaders(), parent); + super(pcfg.getLibsAndTarget(), parent); } } diff --git a/src/org/rascalmpl/uri/file/MavenRepositoryURIResolver.java b/src/org/rascalmpl/uri/file/MavenRepositoryURIResolver.java index 45ea030e0ba..7a16ec7d58b 100644 --- a/src/org/rascalmpl/uri/file/MavenRepositoryURIResolver.java +++ b/src/org/rascalmpl/uri/file/MavenRepositoryURIResolver.java @@ -4,9 +4,11 @@ import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.net.URISyntaxException; import java.util.regex.Pattern; import java.util.stream.Collectors; +import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; import org.rascalmpl.uri.jar.JarURIResolver; @@ -16,9 +18,9 @@ * Finds jar files (and what's inside) relative to the root of the LOCAL Maven repository. * For a discussion REMOTE repositories see below. * - * We use `mvn://!!/` as the general scheme; - * also `mvn://!!/!/` is allowed to make sure the - * root `mvn://!!/` remains a jar file unambiguously. + * We use `mvn://----/` as the general scheme; + * also `mvn://----/!/` is allowed to make sure the + * root `mvn://----/` remains a jar file unambiguously. * * So the authority encodes the identity of the maven project and the path encodes * what's inside the respective jar file. This is analogous to other schemes for projects @@ -28,7 +30,7 @@ * Here `version` is an arbitrary string with lots of numbers, dots, dashed and underscores. * Typically we'd expect the semantic versioning scheme here with some release tag, but * real maven projects frequently do not adhere to that standard. Hence we have to be "free" - * here and allow lots of funny version strings. This is also why we use ! again to separate + * here and allow lots of funny version strings. This is also why we use -- to separate * the version from the artifactId. * * Locations with the `mvn` scheme are typically produced by configuration code that uses @@ -77,9 +79,11 @@ * idea; but there is currently no way to register read-only logical schemes. */ public class MavenRepositoryURIResolver extends AliasedFileResolver { + private static String localRepoLocationCache; + private final Pattern authorityRegEx - = Pattern.compile("^([a-zA-Z0-9-_.]+?)[!]([a-zA-Z0-9-_.]+)([!][a-zA-Z0-9\\-_.]+)$"); - // groupId ! artifactId ! optionAlComplexVersionString + = Pattern.compile("^([a-zA-Z0-9-_.]+?)[\\-][\\-]([a-zA-Z0-9-_.]+)[\\-][\\-]([a-zA-Z0-9\\-_.]+)$"); + // groupId -- artifactId -- optionAlComplexVersionString public MavenRepositoryURIResolver() throws IOException { super("mvn", inferMavenRepositoryLocation()); @@ -111,7 +115,7 @@ private static String inferMavenRepositoryLocation() { // note that since it does not exist this will make all downstream resolutions fail // to "file does not exist" } - + return m2HomeFolder; } @@ -130,9 +134,14 @@ private static String computeMavenCommandName() { /** * This (slow) code runs only if the ~/.m2 folder does not exist and nobody -D'ed its location either. * That is not necessarily how mvn prioritizes its configuration steps, but it is the way we can - * get a quick enough answer most of the time. + * get a quick enough answer most of the time. It caches its result to make sure repeated calls + * to here are faster than the first. */ private static String getLocalRepositoryLocationFromMavenCommand() { + if (localRepoLocationCache != null) { + return localRepoLocationCache; + } + try { ProcessBuilder processBuilder = new ProcessBuilder(computeMavenCommandName(), "-q", @@ -148,7 +157,7 @@ private static String getLocalRepositoryLocationFromMavenCommand() { } try (var reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { - return reader.lines().collect(Collectors.joining()).trim(); + return (localRepoLocationCache = reader.lines().collect(Collectors.joining()).trim()); } } catch (IOException | InterruptedException e) { @@ -157,7 +166,6 @@ private static String getLocalRepositoryLocationFromMavenCommand() { } } - @Override public ISourceLocation resolve(ISourceLocation input) throws IOException { String authority = input.getAuthority(); @@ -169,7 +177,7 @@ public ISourceLocation resolve(ISourceLocation input) throws IOException { } else { // the authority encodes the group, name and version of a maven dependency - // org.rascalmpl.rascal-34.2.0-RC2 + // org.rascalmpl--rascal--34.2.0-RC2 var m = authorityRegEx.matcher(authority); if (m.matches()) { @@ -177,7 +185,7 @@ public ISourceLocation resolve(ISourceLocation input) throws IOException { String name = m.group(2); String version = m.group(3); - version = version == null ? "" : version.substring(1); + // version = version == null ? "" : version.substring(1); String jarPath = group.replaceAll("\\.", "/") @@ -217,4 +225,39 @@ public ISourceLocation resolve(ISourceLocation input) throws IOException { } } } + + public static ISourceLocation make(String groupId, String artifactId, String version, String path) { + return URIUtil.correctLocation("mvn", groupId + "--" + artifactId + "--" + version, path); + } + + /** + * Shortens a location of a jar file that points into a local maven repository, and + * leaves all other locations as-is. + * */ + public static ISourceLocation mavenize(ISourceLocation loc) { + try { + loc = URIResolverRegistry.getInstance().logicalToPhysical(loc); + + if (!URIUtil.getExtension(loc).equals("jar")) { + return loc; + } + + ISourceLocation repo = URIUtil.createFileLocation(inferMavenRepositoryLocation()); + ISourceLocation relative = URIUtil.relativize(repo, loc); + boolean isFileInRepo = loc.getScheme().equals("file") && relative.getScheme().equals("relative"); + + if (isFileInRepo) { + String groupId = URIUtil.getParentLocation(URIUtil.getParentLocation(URIUtil.getParentLocation(relative))).getPath().substring(1).replaceAll("/", "."); + String artifactId = URIUtil.getLocationName(URIUtil.getParentLocation(URIUtil.getParentLocation(relative))); + String version = URIUtil.getLocationName(URIUtil.getParentLocation(relative)); + + return make(groupId, artifactId, version, ""); + } + + return loc; + } + catch (IOException | URISyntaxException e) { + return loc; + } + } } diff --git a/src/org/rascalmpl/uri/libraries/RascalLibraryURIResolver.java b/src/org/rascalmpl/uri/libraries/RascalLibraryURIResolver.java deleted file mode 100644 index f1c8b1dd656..00000000000 --- a/src/org/rascalmpl/uri/libraries/RascalLibraryURIResolver.java +++ /dev/null @@ -1,262 +0,0 @@ -/** - * Copyright (c) 2019, Jurgen J. Vinju, Centrum Wiskunde & Informatica (NWOi - CWI) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package org.rascalmpl.uri.libraries; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.Charset; -import java.util.Collections; -import java.util.Enumeration; -import java.util.concurrent.ConcurrentHashMap; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.rascalmpl.interpreter.utils.RascalManifest; -import org.rascalmpl.uri.ISourceLocationInput; -import org.rascalmpl.uri.URIResolverRegistry; -import org.rascalmpl.uri.URIUtil; -import org.rascalmpl.uri.classloaders.IClassloaderLocationResolver; -import org.rascalmpl.values.IRascalValueFactory; -import org.rascalmpl.values.ValueFactoryFactory; - -import io.usethesource.vallang.ISourceLocation; -import io.usethesource.vallang.IValueFactory; - -/** - * The goal of this resolver is to provide |lib://<libName>/| for every Rascal library available in the current run-time environment. - * To do this, it searches for META-INF/RASCAL.MF files in 2 places, and checks if the Project-Name inside of that file is equal to <libName>: - *
    - *
  • |plugin://<libName>| is probed first, in order to give precedence to plugins loaded by application containers such as OSGI;
  • - *
  • Finally ClassLoader.getResources is probed to resolve to |jar+file://path-to-jar-on-classpath!/| if a RASCAL.MF can be found there with the proper Project-Name in it. So this only searches in the JVM start-up classpath via its URLClassLoaders, ignoring plugin mechanisms like OSGI and the like.
  • - *
- *

CAVEAT 1: this resolver caches the first resolution (plugin or jarfile) and does not rescind it afterwards even if the locations - * cease to exist. This might happen due to plugin unloading. To re-initialize an - * already resolved |lib://<libName>| path, either the JVM must be reloaded (restart the IDE) or the current class must be reloaded - * (restart the plugin which loaded the Rascal run-time). TODO FIXME by allowing re-initialization of this entire resolver by the URIResolverRegistry.

- *

CAVEAT 2: it is up to the respective run-time environments (Eclipse, OSGI, MVN, Spring, etc.) to provide the respective implementations - * of ISourceLocation input for the plugin:// scheme. If it is not provided, this resolver only resolves to resources - * which can be found via the System classloader.

- */ -public class RascalLibraryURIResolver implements ISourceLocationInput, IClassloaderLocationResolver { - private final ConcurrentHashMap classpathLibraries = new ConcurrentHashMap<>(); - private final ConcurrentHashMap resolvedLibraries = new ConcurrentHashMap<>(); - private final URIResolverRegistry reg; - - public RascalLibraryURIResolver(URIResolverRegistry reg) { - this.reg = reg; - - try { - IValueFactory vf = ValueFactoryFactory.getValueFactory(); - RascalManifest mf = new RascalManifest(); - Enumeration mfs = getClass().getClassLoader().getResources(RascalManifest.META_INF_RASCAL_MF); - - Collections.list(mfs).forEach(url -> { - try { - String libName = mf.getProjectName(url.openStream()); - - if (libName != null && !libName.isEmpty()) { - ISourceLocation loc; - - if (url.getProtocol().equals("jar") && url.getPath().startsWith("file:/")) { - loc = vf.sourceLocation("jar+file", null, URIUtil.fromURL(new URL(url.getPath())).getPath()); - } - else { - loc = vf.sourceLocation(URIUtil.fromURL(url)); - } - - loc = URIUtil.changePath(loc, loc.getPath().replace(RascalManifest.META_INF_RASCAL_MF, "")); - - registerLibrary("detected", classpathLibraries, libName, loc); - } - } - catch (IOException | URISyntaxException e) { - System.err.println("WARNING: could not load Rascal manifest for library resolution of: " + url); - e.printStackTrace(); - } - }); - } - catch (IOException e) { - System.err.println("WARNING: could not resolve any Rascal library locations"); - e.printStackTrace(); - } - } - - private void registerLibrary(String event, ConcurrentHashMap libs, String libName, ISourceLocation loc) { - /* we want the first match, just like the classpath, so always using the old value */ - if (libs.merge(libName, loc, (o, n) -> o) == loc) { - /* now we have a new one: report it */ - System.err.println("INFO: " + event + " |lib://" + libName + "| at " + loc); - } - } - - /** - * Resolve a lib location to either a plugin or a local classpath location, in that order of precedence. - */ - private @Nullable ISourceLocation resolve(ISourceLocation uri) { - String libName = uri.getAuthority(); - - if (libName == null || libName.isEmpty()) { - return null; - } - - // if we resolved this library before, we stick with that initial resolution for efficiency's sake - ISourceLocation resolved = resolvedLibraries.get(libName); - if (resolved != null) { - return inheritPositions(uri, URIUtil.getChildLocation(resolved, uri.getPath())); - } - - // then we try plugin libraries, taking precedence over classpath libraries - ISourceLocation plugin = deferToScheme(uri, "plugin"); - if (plugin != null) { - return plugin; - } - - // finally we try the classpath libraries - ISourceLocation classpath = classpathLibraries.get(libName); - if (classpath != null) { - return resolvedLocation(uri, libName, classpath); - } - - return null; - } - - /** - * Tries to find a RASCAL.MF file in the deferred scheme's root and if it's present, the - * prefix is cached and the child location is returned. - */ - private ISourceLocation deferToScheme(ISourceLocation uri, String scheme) { - String libName = uri.getAuthority(); - ISourceLocation libRoot = URIUtil.correctLocation(scheme, libName, ""); - - if (isValidLibraryRoot(libRoot)) { - return resolvedLocation(uri, libName, libRoot); - } - else { - return null; - } - } - - /** - * Check if this root contains a valid RASCAL.MF file - */ - private boolean isValidLibraryRoot(ISourceLocation libRoot) { - if (reg.exists(URIUtil.getChildLocation(libRoot, RascalManifest.META_INF_RASCAL_MF))) { - assert new RascalManifest().getProjectName(libRoot).equals(libRoot.getAuthority()) - : "Project-Name in RASCAL.MF does not align with authority of the " + libRoot.getScheme() + " scheme"; - return true; - } - - return false; - } - - /** - * compute the resolved child location and cache the prefix as a side-effect for a future fast path - */ - private ISourceLocation resolvedLocation(ISourceLocation uri, String libName, ISourceLocation deferredLoc) { - registerLibrary("resolved", resolvedLibraries, libName, deferredLoc); - return inheritPositions(uri, URIUtil.getChildLocation(deferredLoc, uri.getPath())); - } - - private static ISourceLocation inheritPositions(ISourceLocation uri, ISourceLocation resolved) { - if (uri.hasOffsetLength()) { - if (uri.hasLineColumn()) { - return IRascalValueFactory.getInstance().sourceLocation(resolved, uri.getOffset(), uri.getLength(), uri.getBeginLine(), uri.getEndLine(), uri.getBeginColumn(), uri.getEndColumn()); - } - else { - return IRascalValueFactory.getInstance().sourceLocation(resolved, uri.getOffset(), uri.getLength()); - } - } - else { - return resolved; - } - } - - /** - * Resolve a location and if not possible throw an exception - */ - private ISourceLocation safeResolve(ISourceLocation uri) throws IOException { - ISourceLocation resolved = resolve(uri); - if (resolved == null) { - throw new IOException("lib:// resolver could not resolve " + uri); - } - return resolved; - } - - @Override - public InputStream getInputStream(ISourceLocation uri) throws IOException { - return reg.getInputStream(safeResolve(uri)); - } - - @Override - public Charset getCharset(ISourceLocation uri) throws IOException { - return reg.getCharset(safeResolve(uri)); - } - - @Override - public boolean exists(ISourceLocation uri) { - ISourceLocation resolved = resolve(uri); - if (resolved == null) { - return false; - } - return reg.exists(resolved); - } - - @Override - public long lastModified(ISourceLocation uri) throws IOException { - return reg.lastModified(safeResolve(uri)); - } - - @Override - public boolean isDirectory(ISourceLocation uri) { - try { - return URIResolverRegistry.getInstance().isDirectory(safeResolve(uri)); - } catch (IOException e) { - return false; - } - } - - @Override - public boolean isFile(ISourceLocation uri) { - try { - return URIResolverRegistry.getInstance().isFile(safeResolve(uri)); - } catch (IOException e) { - return false; - } - } - - @Override - public String[] list(ISourceLocation uri) throws IOException { - return reg.listEntries(safeResolve(uri)); - } - - @Override - public String scheme() { - return "lib"; - } - - @Override - public boolean supportsHost() { - return false; - } - - @Override - public ClassLoader getClassLoader(ISourceLocation loc, ClassLoader parent) throws IOException { - ISourceLocation resolved = resolve(loc); - if (resolved != null) { - return reg.getClassLoader(resolved, parent); - } - - throw new IOException("Can not resolve classloader for " + loc); - } -} diff --git a/src/org/rascalmpl/uri/resolvers.config b/src/org/rascalmpl/uri/resolvers.config index a0f096180ce..2b599b78ccc 100644 --- a/src/org/rascalmpl/uri/resolvers.config +++ b/src/org/rascalmpl/uri/resolvers.config @@ -14,5 +14,4 @@ org.rascalmpl.uri.file.CurrentWorkingDriveResolver org.rascalmpl.uri.file.UNCResolver org.rascalmpl.uri.file.SystemPathURIResolver org.rascalmpl.uri.libraries.MemoryResolver -org.rascalmpl.uri.libraries.RascalLibraryURIResolver diff --git a/src/org/rascalmpl/values/RascalFunctionValueFactory.java b/src/org/rascalmpl/values/RascalFunctionValueFactory.java index 268d049479f..5e77d2cf679 100644 --- a/src/org/rascalmpl/values/RascalFunctionValueFactory.java +++ b/src/org/rascalmpl/values/RascalFunctionValueFactory.java @@ -500,7 +500,7 @@ protected IValue parse(String methodName, ISet filters, IString input, ISourceL protected IValue firstAmbiguity(String methodName, IString input) { try { - return parseObject(methodName, URIUtil.invalidLocation(), input.getValue().toCharArray(), false, false, vf.set()); + return parseObject(methodName, URIUtil.unknownLocation(), input.getValue().toCharArray(), false, false, vf.set()); } catch (ParseError pe) { ISourceLocation errorLoc = pe.getLocation(); diff --git a/test/org/rascalmpl/test/parser/StackNodeTest.java b/test/org/rascalmpl/test/parser/StackNodeTest.java index bfc52a4f5b8..6b849ca5fa7 100644 --- a/test/org/rascalmpl/test/parser/StackNodeTest.java +++ b/test/org/rascalmpl/test/parser/StackNodeTest.java @@ -3,7 +3,6 @@ import org.junit.Assert; import org.junit.Test; import org.rascalmpl.parser.gtd.stack.EpsilonStackNode; -import org.rascalmpl.parser.gtd.stack.LiteralStackNode; import org.rascalmpl.parser.gtd.stack.filter.ICompletionFilter; import org.rascalmpl.parser.gtd.stack.filter.IEnterFilter; import org.rascalmpl.parser.gtd.stack.filter.follow.AtEndOfLineRequirement; diff --git a/test/org/rascalmpl/test/util/library/RunRascalTestModules.java b/test/org/rascalmpl/test/util/library/RunRascalTestModules.java new file mode 100644 index 00000000000..b5f36bcf3b0 --- /dev/null +++ b/test/org/rascalmpl/test/util/library/RunRascalTestModules.java @@ -0,0 +1,10 @@ +package org.rascalmpl.test.util.library; + +import org.junit.runner.RunWith; +import org.rascalmpl.test.infrastructure.RascalJUnitTestPrefix; +import org.rascalmpl.test.infrastructure.RascalJUnitTestRunner; + +@RunWith(RascalJUnitTestRunner.class) +@RascalJUnitTestPrefix("lang::rascal::tests::library") +public class RunRascalTestModules { } +