>
>
> Could that work if we complemented the "instanceof URLClassLoader" with 
> some check for "is the system classloader" and then use 
> System.getProperty("java.class.path") (or the equivalent 
> ManagementFactory.getRuntimeMXBean().getClassPath()) to get the system 
> classpath entries? (do we also need the bootclasspath?)
>


Here is the hack in full.  Could be better using a more standard "get my 
classpath" mechanism (though, to be honest, I think it might be better to 
have an alternate means of specifying classpath for Gwt; for example, 
specifying maven-repo coordinates and resolving them via maven cli)

String java9Modules = System.getProperty("gwt.java9.modules");
for (ClassLoader candidate = wrapped; candidate != null; candidate = 
candidate.getParent()) {
  if (candidate instanceof URLClassLoader) {
    for (URL url : ((URLClassLoader) candidate).getURLs()) {
      result.put(url.toExternalForm(), url);
    }
  } else  if (java9Modules != null) {
  // If the user specified java 9 modules to load for classpath,
  // we'll want to do it all through reflection,
  // to avoid losing support for java 8
    try {
      final Class<?> modRef = 
candidate.loadClass("java.lang.module.ModuleReference");
      final Method getLocation = modRef.getMethod("location");
      // In the case of java 9, the only way to scrape classloaders is this 
ugly reflection
      // on an internal class (used to be a URLClassLoader).
      // you will need to add `--add-opens 
java.base/jdk.internal.loader=myJava9ModuleName`
      // to your gwt runtime as a VM argument
      final Class<?> loader = 
candidate.loadClass("jdk.internal.loader.BuiltinClassLoader");
      // TODO: Don't use classloader for resource loading
      final Method findMod = loader.getDeclaredMethod("findModule", 
String.class);
      // This is why we have to open the package; just being visible is not 
enough
      // to be allowed to act reflectively
      findMod.setAccessible(true);

      for (String source : java9Modules.split(",")) {
        System.out.println("Loading java 9 module " + source);
        Object mod = findMod.invoke(candidate, source);
        // Safe to cast; we must be in java 9 to get here,
        // so we know that this cast should be safe
        Optional<URI> location = (Optional<URI>) getLocation.invoke(mod);
        if (location.isPresent()) {
          final URL url = location.get().toURL();
          result.put(url.toExternalForm(), url);
        }
      }
    } catch (Exception ignored) {
      if (ignored instanceof InterruptedException) {
        Thread.currentThread().interrupt();
        throw new RuntimeException(ignored);
      }
      ignored.printStackTrace();
    }
  }
}



My method uses a system property to specify which java 9 modules to use as 
source, then rips those out of the BuiltinClassloader.
This is obviously too egregious to use in production, but worked enough to 
get a HelloWorld example to compile (w/ new language features for testing).

Due to java 9's encapsulation, we can't just load the whole classpath 
anyway, since the modules we want to read must be opened (thus the 
property, which could be a config prop instead).

Because we will already have to name the modules we want, it's not a far 
cry to specify source coordinates and resolve the jars as well (I have 
other tools that do this nicely).

It's not pretty, but at least it is possible. :-)

>
> IIRC, when CodeServer was added, with its -src argument, I suggested 
> passing classpaths as argument rather than using the system classpath.
> At the time though, there was even more code that relied on the context 
> classloader than today (rather than using the ResourceOracle). This was 
> fixed later for the most part, but I believe there's still code that uses 
> the context classloader (including looking up annotations from 
> com.google.gwt.core.ext.typeinfo, and AFAIK also JDT loading bytecode that 
> ends up being looked up in the context classloader; 
> see 8b584f412d055ff39949d8b28a8e78d6b0bfe9b6 for example)
> I suppose that could be fixed by using a child URLClassLoader from the 
> classpath argument (that could be necessary anyway for generators and 
> linkers)
>  
>

Aye...  the way we load/compile annotations has always irked me.
Java 9 also comes with full-fledged support for dynamically compiling 
arbitrary source, and it is my opinion that we should use this for 
annotations (so we don't need to run javac before gwtc).
Because annotations contain classes and enums, those classes and enums must 
also be loaded to get annotation support, so it gets... sticky, especially 
once java 9 modules get involved.
By running the compiler on source, then the myriad of original modules 
won't really matter...

Another alternative is to go the way of apt and use something akin to 
AnnotationMirror... meaning you can't just do MyAnnoClass anno = 
member.getAnnotation(MyAnnoClass.class), but will instead be stuck 
operating on a generic mirror which can get members by string name.  Not 
exactly pretty, but if it's good enough for apt, it can clearly be made to 
work (plus, with the impetus to ditch generators, we'd be stuck with it 
anyway).

I (also) have another project which converts AnnotationMirror to a proxy of 
MyAnnoClass that can either load a dependent Class/Enum, or fail if that 
class is not on classpath (but only fail if you try to access that member). 
 Adding a dynamic runtime compiler (I didn't have support for that when I 
build the annotation proxy bit) would mean we could fallback to compiling 
the necessary classes, and still get nice typed annotations w/out requiring 
them to be precompiled (and thus remove all need for inspecting 
ClassLoader). 
 

> The one big caveat is code modularity.  We can't release two jars with 
>> code in the same packages (unless recent changes to module system now 
>> allowed "concealed package conflicts").
>>
>
> Apparently not: 
> http://openjdk.java.net/projects/jigsaw/spec/issues/#AvoidConcealedPackageConflicts
> IIUC, they say this can be worked around using classloaders, or 
> shading/shadowing (i.e. what you're doing)
>  
>

Right, if you, yourself, load up a URLClassLoader and use it, it's business 
as usual.  But, that also means we need to populate said URLClassLoader 
correctly, which means either we manage classpath ourselves (then no need 
for URLClassLoader inspection, since we are supplying its URLs), or we 
force end users to figure this out themselves (ick).

The shading I am doing is too hideous to consider for anything serious.  It 
took like, four invocations of maven-dependency-plugin to unzip stuff in 
all the right locations, and resulted in an output artifact containing the 
entire user+dev gwt sdk.
So, the time has finally come that modularization is no longer 
nice-to-have, but now must-have. 

 

> So, that means either one big, ugly uber-jar for compilation (my hack 
>> unzips gwt-user and gwt-dev locally to avoid these issues)... or we 
>> actually, finally modularize the codebase.
>>
>
> Does that also apply to subpackage? Because it looks like the only 
> conflicting packages are c.g.g.core.client and c.g.g.core.shared, so maybe 
> moving classes around could be enough?
>  
>

It could be possible to get away with light refactoring, yourself and Colin 
have more experience in actually making an effort towards this.
I would be happy with a lighter weight solution for now, to make this 
workable, and a full solution later, as time permits.

-- 
You received this message because you are subscribed to the Google Groups "GWT 
Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/google-web-toolkit-contributors/405d8326-332c-45d6-b503-d6a44dfe2bdf%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to