-
-
Notifications
You must be signed in to change notification settings - Fork 302
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#5346 Support for reading MRJs #5564
Conversation
There has been a long history how to support MRJs (Multi Release Jars). I kind of get it since it seems a giant magnet for complications, but alas, progress. The main problem with the current approach is that the support was focused on the Jar file. Imho, (and I think BJ's) this is the wrong place. The Jar abstracts working with the ZIP file but should imho not contain any semantics of the class path. We already placed the Bundle-Classpath (which is a very similar problem) in the Analyzer. This morning I had an interesting discussion with Christopher :-) He really helped me to see the problem more clear (I hope). I think we need to separate this problem strictly in 2 parts. The first is the MJR read support. This is this PR. Write will come later. During the discussion it became clear to me that for our build (remember, I am ignoring writing MJRs) we need a _view_ on the MJR that flattens the namespace of the resources. To be able to flatten, we need a max release for the view. I am a bit fuzzy on the use cases but I am convinced you need to be able specify this max release per JAR if you need to but for convenience, we need a setting per builder/project and default default. I've come up with the following rules: * In a -*path (e.g. -buildpath) you can set a MRJ. * You can add an attribute like `-java.release.max=10` to the file in the buildpath. This will create a view up to and including release 10. I.e. what the VM would see if it was a Java 10 VM. * In absence of this attribute, a default can be set in the workspace or project with the same property `-java.release.max=17` * In absence of a default, the Jar is flattened for all the release it contains. This may mean that the compiler cannot read certain class files. I am open to another strategy if someone has ideas. I am not sure I like the property name. We could use javac.target but I feel a tad uncomfortable with that. However, during the discussion it became crystal clear to me that this was not a Jar quality because the view that is used depends on the triplet of MRJ layout, max release, and the Jar. So I've implemented this accordingly in this PR. Quick feedback is appreciated because Christopher has been hanging in there for a very long time. --- Signed-off-by: Peter Kriens <Peter.Kriens@aQute.biz> Signed-off-by: Peter Kriens <Peter.Kriens@aQute.biz>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor things, I'm not in the space well enough to understand.
/** | ||
* Verify that the manifest overrides a version in a package info | ||
*/ | ||
|
||
@Test | ||
public void testManifestOverridesPackageInfo() throws Exception { | ||
try(Builder source = new Builder() ){ | ||
try (Builder source = new Builder()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would make reviewing easier if these formatting changes were included separately. I know it's annoying to do so and I am often similarly lazy, so this is not a huge criticism...
|
||
UNKNOWN("<UNKNOWN>", "UNKNOWN", "0"); | ||
UNKNOWN("<UNKNOWN>", "UNKNOWN", "0", 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realise that this is a legacy problem, but is there a problem with "unknown" and "1.0" having the same release number? Potential for confusion? Should Unknown be "-1" instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lowest valid release target is 6 .... and there is already a method that handles this see my previous comment.
@@ -24,6 +24,8 @@ public interface Constants { | |||
String IDENTITY_INITIAL_RESOURCE = "<<INITIAL>>"; | |||
String IDENTITY_SYSTEM_RESOURCE = "<<SYSTEM>>"; | |||
|
|||
String MULTI_RElEASE = "Multi-Release"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the "L" in "RELEASE" has snuck in as a lower case?
return -1; | ||
} | ||
}); | ||
System.out.println(names); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove?
It’s nice to see some progress on this issue. I tried to work with @laeubi some months ago on it but there was resistance at the time. A flattened view seems like a good (and necessary) first step. When calculating manifests for multi-release bundles (a future PR) we will need to process the flattened contents at each version increment present in a multi release bundle. |
import aQute.bnd.exceptions.Exceptions; | ||
|
||
/** | ||
* Multi Release jars are another error magnet extension to Java. Instead of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BND would really benefit from describing actual implementation and usage than complains about other techniques ...
I already tried that approach and actually it does not work out well and actually complicates things ... if one compares the necessary changes with my more generic approach that support read AND write, this clearly demonstrate that "flattening" it and handling it on multiple places don't work well. And it will get even more complicated when it comes to generation of MR jar. Also the specification of any "settings" like Putting this handling on the builder will require each and every user to take care of MR (instead of one central place the Jar) ... So if someone wants a ZipFile this should be a class that Jar extends and put in everything "clean Zip" there... The MR-JAR is part of the Java Jar specification and therefore belongs there. |
private transient EnumSet<EE> compatibleSet; | ||
private transient Parameters packages = null; | ||
private transient Parameters modules = null; | ||
|
||
/** | ||
* For use by JavaSE_9 and later. | ||
*/ | ||
EE() { | ||
EE(int release) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EE class already has method for getting the release target:
bnd/biz.aQute.bndlib/src/aQute/bnd/build/model/EE.java
Lines 149 to 162 in f77b676
/** | |
* @return the java release target corresponding to this EE | |
*/ | |
public OptionalInt getReleaseTarget() { | |
Version version = getCapabilityVersion(); | |
int major = version.getMajor(); | |
if (major > 1) { | |
return OptionalInt.of(major); | |
} | |
if (major == 1 && version.getMinor() > 5) { | |
return OptionalInt.of(version.getMinor()); | |
} | |
return OptionalInt.empty(); | |
} |
MRJAR is not a semantic of the classpath it is an inherit semantic of the Jar specification see: https://docs.oracle.com/javase/9/docs/api/java/util/jar/JarFile.html
Also take a look specifically at the method: That extends what a ZipFile does (https://docs.oracle.com/javase/9/docs/api/java/util/jar/JarFile.html#getEntry-java.lang.String-) with the definition of the Multi-Release-Jar specification. Thee runtime handling is what I have actually extended the (bnd)Jar class with for build/analysis time semantic. |
The release for the build that must be used is the release target of the compiler, for analysis the minimum release is the minimum ee-target and the maximum of all dependencies where each distinct release between [min,max] needs to be analyzed and a corresponding supplemental manifest fragment must be generated. |
}; | ||
|
||
if (release < 9) { | ||
release = Integer.MAX_VALUE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if release is below 9, there is nothing to be flatten.. as the lowest possible release for a MJAR is 9.
Using MAX_VALUE is wrong here, as this will create a jar with the HIGHEST java version...
|
||
List<String> names = new ArrayList<>(jar.getResources() | ||
.keySet()); | ||
Collections.sort(names, (a,b)->{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks very confusing? Whats the purpose of this comparator? Why not work on the parsed version directly?
} else | ||
out.putResource(name, resource); | ||
} | ||
return out; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not merge the main manifest with the supplemental fragments
Domain domain = Domain.domain(jar.getManifest()); | ||
return Processor.isTrue(domain.get(Constants.MULTI_RElEASE)); | ||
} catch (Exception e) { | ||
throw Exceptions.duck(e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case it is most probably not even a jar at all and one can return false.
addClasspath(jar, -1); | ||
} | ||
|
||
public void addClasspath(Jar jar, int release) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think, if one want to make it work, that way, doing it at the time of adding classpath won't work well in the long term, but it could work doing it when one is getting the classpath!
So List<Jar> getClasspath(int release)
or even Jar getClasspath(int release)
(as one can actually even flatten all jars into a single linear jar view if we assume manifests do not matter here). That way one don't destroy the original added classpath jars and can later on analyze the classpath for different release levels.
I am deleting this PR because I think I did not understand the specs. After reading the specs again and again and talking to @stbischof I think I have a better understanding.
I plan to push another PR later. |
What spec and what part from it?
This is only true if BND completely ignores the classpath, so why the jars are added to the classpath if the are ignored? So if BND never uses the manifest of a classpath jar and never loads a resource from a classpath jar and never loads a class from the classpath jar, then one can ignore it for basic operations. For plugins (e.g. JPMS) it is not true see #5327 (that was closed with unclear reference to this topic)
This is one part of the problem yes and maybe the most visible one, this problem and aspects where solved by #5359 that includes all codes and techniques to consume and produce jars according to both mentioned specs. |
I can try to adjust my proposed PRs with the solution suggested above (only flatten then Jar for |
@laeubi thanks for the offer but to do this right I think we need to some deep changes. As far as I can see, the JPMSModuleInfoPlugin runs in the verifier stage but adds a resource (module-inf). This will make it miss the name section. I am adding a plugin that allows it to run at the right time: just after the main section is done, just before the name section. I also added a JPMSModule that abstracts the Jar that contains all the JPMS specific stuff. I also think I can use the doConditionalPackage() strategy to handle the few extra classes from the versioned directories efficiently. However, that requires copying the analyzer and its constituents. I find that do it right, I need to touch a lot of places so I will make another attempt. |
There has been a long history how to support MRJs (Multi Release Jars). I kind of get it since it seems a giant magnet for complications, but alas, progress.
The main problem with the current approach is that the support was focused on the Jar file. Imho, (and I think BJ's) this is the wrong place. The Jar abstracts working with the ZIP file but should imho not contain any semantics of the class path. We already placed the Bundle-Classpath (which is a very similar problem) in the Analyzer.
This morning I had an interesting discussion with Christopher :-) He really helped me to see the problem more clear (I hope). I think we need to separate this problem strictly in 2 parts. The first is the MJR read support. This is this PR. Write will come later.
During the discussion it became clear to me that for our build (remember, I am ignoring writing MJRs) we need a view on the MJR that flattens the namespace of the resources. To be able to flatten, we need a max release for the view. I am a bit fuzzy on the use cases but I am convinced you need to be able specify this max release per JAR if you need to but for convenience, we need a setting per builder/project and default default. I've come up with the following rules:
-java.release.max=10
to the file in the buildpath. This will create a view up to and including release 10. I.e. what the VM would see if it was a Java 10 VM.-java.release.max=17
I am open to another strategy if someone has ideas. I am not sure I like the property name. We could use javac.target but I feel a tad uncomfortable with that. However, during the discussion it became crystal clear to me that this was not a Jar quality because the view that is used depends on the triplet of MRJ layout, max release, and the Jar.
So I've implemented this accordingly in this PR. Quick feedback is appreciated because Christopher has been hanging in there for a very long time.
Signed-off-by: Peter Kriens Peter.Kriens@aQute.biz