paketo-buildpacks / rfcs

Apache License 2.0
19 stars 33 forks source link

Require a specific java version in the buildplan #268

Closed c0d1ngm0nk3y closed 1 year ago

c0d1ngm0nk3y commented 1 year ago

Readable

When a requires in the buildplan requests a jdk or jre, it could also provide some metadata on which version is needed.

Summary

[[requires]]
name = "jdk"

[requires.metadata]
version = "17"

This would not rely on the default version to be the right one or force the user to provide the needed version.

Use Cases

As discussed here: https://github.com/paketo-buildpacks/maven/issues/217

Checklist

dmikusa commented 1 year ago

My vote on this, for the time being, is that we gather some more evidence/motivation that this is necessary and that there are no other ways that we can tackle this problem. As mentioned here, my concern is that by allowing every buildpack to have a say in the version of Java that gets installed we'll increase the complexity and make it more difficult for users and developer to understand why a particular version of Java has been selected.

To summarize the high-level issue, we already have support for detecting the Java version automatically in a handful of ways however there are some use cases that fall through the cracks and it's difficult for users.

We presently support these use cases:

  1. Spring Boot apps that build with Spring Boot build tools have the version set correctly by the build tools plugin. This happens outside of the buildpack, but it is transparent to the user.

  2. An asset precompiled with Maven will include version information that the precompiled JAR/WAR. The buildpack will read this and pick the requested Java version.

  3. When building from source, if a user has an SDKMAN RC file included with their application the buildpack will read this and use the requested Java version.

  4. You can pick a specific Java version by setting BP_JVM_VERSION.

  5. You can use the default version of Java.

I'd like to capture some more specific information about what types of applications and build use cases are not covered above. Please add comments here with: 1.) the Java build tool, 2.) the build environment (local dev or CI or cloud platform), 3.) the buildpacks build tool (pack, Spring Boot Build tools plugin, kpack, etc...), 4.) is it a precompiled build or from source or are both possible, 5.) what happens presently. That should help to start cataloging this problem.

I'll get the list started:

1.) Spring Boot 3 Applications, Maven & Gradle, local or CI, pack, source, fails to build unless you specifically set BP_JVM_VERSION because Java 17 is required for Spring Boot 3.

Aside from that, it would probably help to start making a list of options where we can detect the Java version. If you have thoughts on this, please add a comment as well. Include 1.) details about where the version number exists, 2.) what tools are required to reliably read that version info, 3.) would it work for builds with precompiled assets, source or both

I'll get this list started as well:

1.) Examine Java class files. a.) Each class file has the Java version used to compile it. This metadata could be read. The trick is that not every class file may have been compiled with the same Java version, so you'd somehow have to reconcile that and pick the right version of Java to use. You'd also have to monitor performance as scanning a lot of files could potentially be slow. Some preliminary work was done on this, but we didn't push forward with the idea. b.) This could be implemented directly in the buildpack Go code. c.) Precompiled assets only.

2.) Read Maven pom.xml a.) A Maven project can have metadata in it that indicates the version of Java to use. The pom.xml file format is complicated and can reference other pom.xml files, so the decision on which version to use is not simple. To only way to consistently get the same answer as Maven is to run Maven and ask it. b.) Would require a JVM and Maven or possibly a version of Maven compiled as a native-image (not sure if this is possible). c.) Source builds only.

3.) Read Gradle pom.xml a.) A Gradle project can have metadata in it that indicates the version of Java to use. The Gradle configuration file format is complicated and includes multiple files, so the decision on which version to use is not simple. To only way to consistently get the same answer as Gradle is to run Gradle and ask it. b.) Would require a JVM and Gradle or possibly a version of Gradle compiled as a native-image (not sure if this is possible). c.) Source builds only.

Please post if you have suggestions for either approach.

c0d1ngm0nk3y commented 1 year ago

... my concern is that by allowing every buildpack to have a say in the version of Java that gets installed we'll increase the complexity and make it more difficult for users and developer to understand why a particular version of Java has been selected.

Am I really the only one that finds the current approach strange/wrong? libjvm will pick a version without "knowing" what the reason for the JRE is. Looking into the jar should a matter to the executable_jar buildpack alone, imho. Currently, the buildpacks seem to "know" of each other.

  1. An asset precompiled with Maven will include version information that the precompiled JAR/WAR. The buildpack will read this and pick the requested Java version.

But NOT the maven buildpack, right?

  1. You can pick a specific Java version by setting BP_JVM_VERSION.

This works of course. But forcing the buildpacks to use a specific version although all information is already known to make a good decision feels not really "buildpack-ish".

  1. You can use the default version of Java.

But why should the user bother if the default version is working for his app if all information is known that it is not.

Please post if you have suggestions for either approach.

Do you think, libjvm would be the right place for this? Wouldn't that couple the buildpacks?

dmikusa commented 1 year ago

Am I really the only one that finds the current approach strange/wrong? libjvm will pick a version without "knowing" what the reason for the JRE is. Looking into the jar should a matter to the executable_jar buildpack alone, imho. Currently, the buildpacks seem to "know" of each other.

I'm not disagreeing with you on this, but I think it's a better approach than introducing more buildplan complexity, the reasons I've cited previously provide my rationale for that. Do we have a perfect decomposition of the buildpacks, no, but there are some functional/complexity trade-offs that I think make sense to do it the way we're presently doing it.

libjvm will pick a version without "knowing" what the reason for the JRE is.

I do disagree with this though. The code in libjvm does know why it's picking something. There are specific signals it reads, so it is not blindly picking a version except for the default. If we moved those signals into a separate buildpack, that separate buildpack would still be reading the same signals and we would still have the same rationale for the version that's selected.

An asset precompiled with Maven will include version information that the precompiled JAR/WAR. The buildpack will read this and pick the requested Java version. But NOT the maven buildpack, right?

Correct, It's precompiled so Maven does not run.

But forcing the buildpacks to use a specific version although all information is already known to make a good decision feels not really "buildpack-ish".

If there are signals we are missing, please list them so I can include them in the notes above.

Do you think, libjvm would be the right place for this? Wouldn't that couple the buildpacks?

I think it depends, so if we start by gathering some information we can discuss some of the concrete scenarios in more detail. For example, if we could come up with a solution to read the version from the class files, I do think that could fit in libjvm. On the other hand, adding information about a particular build tool doesn't make me feel great. That said, I don't think any of the items I listed above are presently actionable because the implementations need to be worked out. That's the other side of this problem. If we can't implement one of these solutions in a clean, consistent, reliable way then this issue is irrelevant.

c0d1ngm0nk3y commented 1 year ago

I do disagree with this though. The code in libjvm does know why it's picking something. There are specific signals it reads, so it is not blindly picking a version except for the default. If we moved those signals into a separate buildpack, that separate buildpack would still be reading the same signals and we would still have the same rationale for the version that's selected.

Now, I got your point. When talking about the java buildpack and the buildpacks contained, that is kind of true. But if I have a custom buildpack that requires a JVM, I cannot just add bellsoft-liberica to provide it, because libjvm will look at some jar it discovers and decides the version. Although this jar might not be the reason to request the JVM in the first place.

But this might be a bit farfetched, agreed.

If we can't implement one of these solutions in a clean, consistent, reliable way then this issue is irrelevant.

Is it? e.g. if it only works if a specific maven plugin is used, it is still some benefit although the version cannot be extracted without the plugin. Don't you think?

c0d1ngm0nk3y commented 1 year ago

I would like to better understand why you would see such a complexity increase?

It would be perfectly testable and quite straight forward.

  1. If the user explicitly requests a version, this is taken.
  2. If the buildplan requests a specific version, it is provided.
  3. Otherwise, the default version is used.

Am I missing something?

Some complexity comes into play if several buildpacks requests different versions. But probably always taking the newer one should be fine.

The "problem" that the user does not know why a certain version was chosen can be solved by the logs. Imho, it is the same in the current approach.

More, if you have a buildplan with different versions, you would realise it and make a decision. Right now, libjvm takes a version it think is suitable.

For the case that the user requests a version and the buildplan requires another version, we could even error out.

Example: The user requests tomcat 10.1, but also request JDK 8 for its app. Right now, it results in a runtime error since this tomcat does not run with Java 8. With this addition to the buildplan, we could even fail during build and provide a meaningful error. Which I think would be some improvement.

Latest when taking any information from the pom.xml, I do not understand why libjvm should be involved and not the buildpack which is looking at the pom.xml anyway.

@dmikusa WDYT?

jpena-r7 commented 1 year ago

Meeting notes. Need communication plan for users that use java 11. New version of spring boot defaults 17.

c0d1ngm0nk3y commented 1 year ago

Meeting notes. Need communication plan for users that use java 11. New version of spring boot defaults 17.

This is probably meant for https://github.com/paketo-buildpacks/rfcs/pull/273, right?

c0d1ngm0nk3y commented 1 year ago

I'd like to capture some more specific information about what types of applications

For me, it is not so much about concrete needs since the current default 11 is a good one at this point in time. But it is more about being future-proof, so that any buildpack can model its requirements.

For example, if tomcat 11 would be released now (requiring Java 17), the apache-tomcat buildpack has no chance including this in a convenient way. Or am I missing something?

anthonydahanne commented 1 year ago

For me, it is not so much about concrete needs since the current default 11 is a good one at this point in time.\

well, with the next LTS (21) being released in 6 months and Spring Boot 3 requiring Java 17 since end of November, I think it's about time 17 should be the default!

if it only works if a specific maven plugin is used, it is still some benefit although the version cannot be extracted without the plugin. Don't you think?

Totally agree, I (and most commenters here) don't think there's a reliable way to detect the required Java version just looking at the source code; but an educated guess that would work for the most popular frameworks would still be better than just a default version

c0d1ngm0nk3y commented 1 year ago

well, with the next LTS (21) being released in 6 months and Spring Boot 3 requiring Java 17 since end of November, I think it's about time 17 should be the default!

That is a completely different topic, imho. But you are completely right. I think it is overdue and there is already an rfc for that -> rfc#273

... I (and most commenters here) ...

most commenters? There has been only one so far, right? So I guess you are technically right :)

don't think there's a reliable way to detect the required Java version just looking at the source code; but an educated guess that would work for the most popular frameworks would still be better than just a default version

I definitely think so. If a buildpack would even only be able to detect the java version in 50% of the scenarios and use the other 50% would use the default version. That would still improve those scenarios, right? Of course as long it is clear what is going on. But I think that could easily be achieved with proper loging.

Maybe that sounds like nit picking, but this RFC ist NOT about any determination of the required java version. I agree, that this might be tricky to achieve reliable in some cases. This is simply to ALLOW other buildpacks to do so. Basically just as the node-engine buildpack does.

It aligns the buildpack, is not that complex to implement and allows other buildpacks to contribute meaningful.

c0d1ngm0nk3y commented 1 year ago

How to continue with that? Does anyone (except me) think that this is worth pursuing?

c0d1ngm0nk3y commented 1 year ago

How to continue with that? Does anyone (except me) think that this is worth pursuing?

I guess no answer is answer enough...