Design-time classpath of a solution, referenced from language

Hi!

I have a project structure like this:
1. Core-solution with java-stubs for my core java functionality
2. Implementation-solution with java-stubs for my custom java functionality (classes implement interfaces from "core" library)
3. Language describing how to work with core functionality (depends on core-solution)
4. Working-solution (depends on language and implementation-solution)

My working-solution "connects" language that works with interfaces with a stub-solution that contains actual classes implementing these interfaces. So later there may be multiple different implementation-solutions, and multiple working-solutions for each implementation-solutions.

In my language I have a concept that has a reference of type ClassConcept. Language checks it against interfaces A from core-solution, and working-solutions specify any class from implementation-solution that implements A.

But my language at design-time needs to create an instance of actual class, specified by working-solution, to get some data from it, describing what user can or cannot write.

Can I specify a .jar dependency (runtime/design-time) to my implementation-solution or working-solution, so that when it instantiates a concept - language will be able to see the specified class, and to instantiate it?

Thanks in advance!

P.S.

I use MPS 3.2.3, and I already tried this:

I added my core-java-library to my core-solution as "java dependency" (Module properties -> Java -> Libraries as "default") and now language successfully executes Class.forName for one of core classes, but it's cuz language is directly depends on core-colution.

Then I tried to add my implementation-java-library as "java dependency" to my implementation-solution, so I thought when working-solution (depending on implementation-solution) will call a language concepts - java classes will be in the classpath, and available to the language.

For reference (sorry for X-Files, work-project):
Screenshot_1.png


But when language tries to execute Class.forName for one of the implementation classes - it gets jetbrains.mps.classloading.ModuleClassNotFoundException: Unable to load class: storyengine.dx.specs.service.IssueOrder using ModuleClassLoader of storyengine.storyteller module

But it works fine, if I add implementation-java-library to my core-solution, or language itself. It seems like when language tries to find a class, it uses only its own classpath, without libraries added to a solution that actualy calls the language.

I thought I will be able to create a build of my language depending on core-java-library and solution with its stubs, and then just include it to builds of specific solutions, depending on specific java-libraries. But either I do something completely wrong, or I'll have to create a separate build of my language with each new specific java-library.

(Sorry for longpost, wanted to be sure I mentioned all the details, since they might be important.)
0
6 comments
TL;DR version:

1. Solution "sA" imports java-library "jA"
2. Language "L" depends on solution "sA" and successfully sees classes from "jA"
3. Solution "sB" depends on "sA" and imports java-library "jB"
4. Solution "sW" depends on "sB" and uses "L"
5. "L" doesn't see classes from "jB" when called from "sW"
0
Tracker issue created (with reproduction instructions and example project): https://youtrack.jetbrains.com/issue/MPS-23005
0
Reported issue was confirmed with major priority.
0
You are right, code, which is a part of a language, uses exactly classpath of this language when executed.

In your case, IF (see below) you need to load a class from java-impl-solution, you want it because it's in the classpath of your working-solution, so you should try to load this class using working-solution classloader. In case of constraints, you could do it the following way:
((ReloadableModule)this.model/.getModule()).getClass("name")
This means, you use the classpath of the solution which contains the instance of your concept. Two things to mention: this code supposes that a node is already in the working-solution and the solution has its kind set to "other" (properties->Java->Solution kind) to be able to load classes.

However I suppose this is wrong way to do what you need. You see, one could have written the same request even if the runtime of the language would be non-JVM-based. In this case, there would be no ability to "load" anything. How would one perform under these restrictions? The right way would be to analyse the code , not the generated/compiled things . I mean, in your case you should do it the same way.
E.g. if you need to constraint a reference to point only to classes implementing some interfaces, you should get all the classes in current scope (these will be list<node<ClassConcept>>, not list<java.lang.Class>) and filter out those of them, which don't have your interface in their hierarchy.
So, your code will look something like:
workingNode.getExtendedClassifierTypes().contains(interfaceClassNodeFromBaseSolution)


Do not hesitate to ask further questions.
0
Hi! Great thanks for the response!

1. I've tried this code:
((ReloadableModule)this.model/.getModule()).getClass("name")

It works, but only if working solution itself imports java-library.
In terms of the TL;DR version - "sW" imports "jB".
It's much better than importing library directly into language, but still looks weird to me )

Now I have solution "sB" that creates java_stubs from library "jB", and imports "jB" itself.
But solution "sW" that depends on "sB" still have to import "jB" in order to make it work.

2.
You see, one could have written the same request even if the runtime of the language would be non-JVM-based

Though I am planning to provide users of my language with a package of the MPS build that already have all dependencies configured, and also my language would too greatly depend on java for it to be launched in non-JVM.

Also I don't really get how language that's generated into baseLanguage may have non-JVM runtime. O_o
How would it be executed, if I would create main methods in generated classes?

3.
The right way would be to analyse the code , not the generated/compiled things

This is true, and I'm trying to do this for as far as I can )

But I have Java-API (non-MPS) where, for example, there's an interface Spec with a method #getParameterNames(), and my implementation-library (also Java) have lots of implementations for this interface.

So in my lang I have a concept SpecInstance that allows to choose a class, and to specify parameters. I configured ClassConcept scope so it uses typesystem to filter only subtypes of the Spec and it works great. But later I need to create an instance of the specified class (actual implementation of the Spec), so I could call its #getParameterNames and validate user input.

There's no other way to tell implementors, how to provide spec implementations, apart from an interface (except maybe, a convention, but it seems ephemeral), and there's no other way to work with an interface implementation, apart from instantiating it.
0
1. That's true, the lib in this case still needs to be in scope of working-solution. This is how MPS works, its classloaders structure can't be changed from "code" (solutions)
However, if you want to break this dependency, you still have to have some "configuration descriptor" to tell which modules are available and can obtain the solution to load impl class from from it.

2. I thought you don't need to execute code from impl-solution in MPS, so my example was about such a case; I meant to show that loading classes is not really needed. Now I understand it's not your case.

3. Now I get it. Actually, it's not only a runtime solution for your language, but also an "extension" that provides some methods to be executed at design-time. For this case, you can use "extensions" in language's plugin model (see j.m.lang.extension language). This allows to describe an "extension point" in your language, from which you'll be able to get "extensions", contributed there by solutions (solution must have plugin kind=other in this case).
If I understood it right, your impl-solution now has two roles in your project: a runtime for the language and an "extension" to the language. I would then suggest to split this functionality into two separate solutions not to have a dependency mess in the project.
0

Please sign in to leave a comment.