-
Notifications
You must be signed in to change notification settings - Fork 73
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
Add the --enable-preview
flag based on the Baseline-Enable-Preview jar manifest attribute
#1365
base: develop
Are you sure you want to change the base?
Changes from 5 commits
bc31d68
339e8d3
416ddac
7f7fe57
1416193
40ebf8c
f0059cf
a741240
7b0eb60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
type: feature | ||
feature: | ||
description: '`createLaunchConfig` task automatically adds the `--enable-preview` | ||
flag based on the `Baseline-Enable-Preview` jar manifest attribute' | ||
links: | ||
- https://github.com/palantir/sls-packaging/pull/1365 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,14 +16,25 @@ | |
|
||
package com.palantir.gradle.dist.service.tasks; | ||
|
||
import com.google.common.base.Preconditions; | ||
import com.google.common.base.Splitter; | ||
import com.google.common.base.Strings; | ||
import com.google.common.collect.ImmutableList; | ||
import com.google.common.collect.Iterables; | ||
import com.google.common.collect.Maps; | ||
import com.google.common.collect.MultimapBuilder; | ||
import com.google.common.collect.Multimaps; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.util.Collection; | ||
import java.util.LinkedHashMap; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.Map; | ||
import java.util.Map.Entry; | ||
import java.util.Optional; | ||
import java.util.function.Function; | ||
import java.util.jar.JarFile; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
import javax.annotation.Nullable; | ||
import org.gradle.api.JavaVersion; | ||
|
@@ -41,12 +52,6 @@ | |
*/ | ||
final class ModuleArgs { | ||
|
||
private static final String ADD_EXPORTS_ATTRIBUTE = "Add-Exports"; | ||
private static final String ADD_OPENS_ATTRIBUTE = "Add-Opens"; | ||
|
||
private static final Splitter ENTRY_SPLITTER = | ||
Splitter.on(' ').trimResults().omitEmptyStrings(); | ||
|
||
// Exists for backcompat until infrastructure has rolled out with Add-Exports manifest values. | ||
// Support safepoint metrics from the internal sun.management package in production. We prefer not | ||
// to use '--illegal-access=permit' so that we can avoid unintentional and unbounded illegal access | ||
|
@@ -60,28 +65,31 @@ static ImmutableList<String> collectClasspathArgs( | |
return ImmutableList.of(); | ||
} | ||
|
||
ImmutableList<JarManifestModuleInfo> classpathInfo = classpath.getFiles().stream() | ||
.map(file -> { | ||
Map<File, JarManifestModuleInfo> parsedJarManifests = classpath.getFiles().stream() | ||
.<Entry<File, JarManifestModuleInfo>>map(file -> { | ||
try { | ||
if (file.getName().endsWith(".jar") && file.isFile()) { | ||
try (JarFile jar = new JarFile(file)) { | ||
java.util.jar.Manifest maybeJarManifest = jar.getManifest(); | ||
Optional<JarManifestModuleInfo> parsedModuleInfo = parseModuleInfo(maybeJarManifest); | ||
project.getLogger() | ||
.debug("Jar '{}' produced manifest info: {}", file, parsedModuleInfo); | ||
return parsedModuleInfo.orElse(null); | ||
} | ||
Optional<JarManifestModuleInfo> parsedModuleInfo = JarManifestModuleInfo.fromJar(file); | ||
project.getLogger().debug("Jar '{}' produced manifest info: {}", file, parsedModuleInfo); | ||
return Maps.immutableEntry(file, parsedModuleInfo.orElse(null)); | ||
} else { | ||
project.getLogger().info("File {} wasn't a JAR or file", file); | ||
} | ||
return null; | ||
} catch (IOException e) { | ||
project.getLogger().warn("Failed to check jar {} for manifest attributes", file, e); | ||
return null; | ||
} | ||
return Maps.immutableEntry(file, null); | ||
}) | ||
.filter(Objects::nonNull) | ||
.collect(ImmutableList.toImmutableList()); | ||
.filter(entry -> entry.getValue() != null) | ||
.collect(Collectors.toMap( | ||
Entry::getKey, | ||
Entry::getValue, | ||
(_left, _right) -> { | ||
throw new UnsupportedOperationException(); | ||
}, | ||
LinkedHashMap::new)); | ||
|
||
Collection<JarManifestModuleInfo> classpathInfo = parsedJarManifests.values(); | ||
Stream<String> exports = Stream.concat( | ||
DEFAULT_EXPORTS.stream(), classpathInfo.stream().flatMap(info -> info.exports().stream())) | ||
.distinct() | ||
|
@@ -92,23 +100,40 @@ static ImmutableList<String> collectClasspathArgs( | |
.distinct() | ||
.sorted() | ||
.flatMap(ModuleArgs::addOpensArg); | ||
return Stream.concat(exports, opens).collect(ImmutableList.toImmutableList()); | ||
} | ||
Stream<String> enablePreview = enablePreview(javaVersion, parsedJarManifests); | ||
|
||
private static Optional<JarManifestModuleInfo> parseModuleInfo(@Nullable java.util.jar.Manifest jarManifest) { | ||
return Optional.ofNullable(jarManifest) | ||
.<JarManifestModuleInfo>map(manifest -> JarManifestModuleInfo.builder() | ||
.exports(readManifestAttribute(manifest, ADD_EXPORTS_ATTRIBUTE)) | ||
.opens(readManifestAttribute(manifest, ADD_OPENS_ATTRIBUTE)) | ||
.build()) | ||
.filter(JarManifestModuleInfo::isPresent); | ||
return Stream.of(exports, opens, enablePreview) | ||
.flatMap(Function.identity()) | ||
.collect(ImmutableList.toImmutableList()); | ||
} | ||
|
||
private static List<String> readManifestAttribute(java.util.jar.Manifest jarManifest, String attribute) { | ||
return Optional.ofNullable( | ||
Strings.emptyToNull(jarManifest.getMainAttributes().getValue(attribute))) | ||
.map(ENTRY_SPLITTER::splitToList) | ||
.orElseGet(ImmutableList::of); | ||
private static Stream<String> enablePreview( | ||
JavaVersion javaVersion, Map<File, JarManifestModuleInfo> parsedJarManifests) { | ||
Map<String, Collection<String>> enablePreviewFromJar = parsedJarManifests.entrySet().stream() | ||
.filter(entry -> entry.getValue().enablePreview().isPresent()) | ||
.collect(Multimaps.toMultimap( | ||
entry -> entry.getValue().enablePreview().get(), | ||
entry -> entry.getKey().getName(), | ||
() -> MultimapBuilder.hashKeys().arrayListValues().build())) | ||
.asMap(); | ||
|
||
if (enablePreviewFromJar.size() > 1) { | ||
throw new RuntimeException("Unable to add '--enable-preview' because classpath jars have embedded " | ||
+ JarManifestModuleInfo.ENABLE_PREVIEW_ATTRIBUTE + " attribute with different versions:\n" | ||
+ enablePreviewFromJar); | ||
} | ||
|
||
if (enablePreviewFromJar.size() == 1) { | ||
String enablePreviewVersion = Iterables.getOnlyElement(enablePreviewFromJar.keySet()); | ||
Preconditions.checkState( | ||
enablePreviewVersion.equals(javaVersion.toString()), | ||
"Runtime java version (" + javaVersion + ") must match version from embedded " | ||
+ JarManifestModuleInfo.ENABLE_PREVIEW_ATTRIBUTE + " attribute (" + enablePreviewVersion | ||
+ ") from:\n" + Iterables.getOnlyElement(enablePreviewFromJar.values())); | ||
return Stream.of("--enable-preview"); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method is pretty complicated, I think we want to stream through the jars, fail if we see any with ENABLE_PREVIEW_ATTRIBUTE != javaVersion, and add the flag if we've seen any ENABLE_PREVIEW_ATTRIBUTE attrs? imo the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry for the delay, I've updated to use less stream-y stuff... think this should be a bit more readable now! |
||
|
||
return Stream.empty(); | ||
} | ||
|
||
private static Stream<String> addExportArg(String modulePackagePair) { | ||
|
@@ -121,20 +146,60 @@ private static Stream<String> addOpensArg(String modulePackagePair) { | |
|
||
private ModuleArgs() {} | ||
|
||
/** Values extracted from a jar's manifest - see {@link #fromJar}. */ | ||
@Value.Immutable | ||
interface JarManifestModuleInfo { | ||
Splitter ENTRY_SPLITTER = Splitter.on(' ').trimResults().omitEmptyStrings(); | ||
String ADD_EXPORTS_ATTRIBUTE = "Add-Exports"; | ||
String ADD_OPENS_ATTRIBUTE = "Add-Opens"; | ||
String ENABLE_PREVIEW_ATTRIBUTE = "Baseline-Enable-Preview"; | ||
|
||
ImmutableList<String> exports(); | ||
|
||
ImmutableList<String> opens(); | ||
|
||
/** | ||
* Signifies that {@code --enable-preview} should be added at runtime AND the specific java runtime version | ||
* that must be used. (Code compiled with --enable-preview must run on _exactly_ the same java version). | ||
* */ | ||
Optional<String> enablePreview(); | ||
|
||
default boolean isEmpty() { | ||
return exports().isEmpty() && opens().isEmpty(); | ||
return exports().isEmpty() && opens().isEmpty() && enablePreview().isEmpty(); | ||
} | ||
|
||
default boolean isPresent() { | ||
return !isEmpty(); | ||
} | ||
|
||
static Optional<JarManifestModuleInfo> fromJar(File file) throws IOException { | ||
try (JarFile jar = new JarFile(file)) { | ||
java.util.jar.Manifest maybeJarManifest = jar.getManifest(); | ||
return JarManifestModuleInfo.fromJarManifest(maybeJarManifest); | ||
} | ||
} | ||
|
||
private static Optional<JarManifestModuleInfo> fromJarManifest(@Nullable java.util.jar.Manifest jarManifest) { | ||
return Optional.ofNullable(jarManifest) | ||
.<JarManifestModuleInfo>map(manifest -> builder() | ||
.exports(readListAttribute(manifest, ADD_EXPORTS_ATTRIBUTE)) | ||
.opens(readListAttribute(manifest, ADD_OPENS_ATTRIBUTE)) | ||
.enablePreview(readOptionalAttribute(manifest, ENABLE_PREVIEW_ATTRIBUTE)) | ||
.build()) | ||
.filter(JarManifestModuleInfo::isPresent); | ||
} | ||
|
||
private static List<String> readListAttribute(java.util.jar.Manifest jarManifest, String attribute) { | ||
return readOptionalAttribute(jarManifest, attribute) | ||
.map(ENTRY_SPLITTER::splitToList) | ||
.orElseGet(ImmutableList::of); | ||
} | ||
|
||
private static Optional<String> readOptionalAttribute(java.util.jar.Manifest jarManifest, String attribute) { | ||
return Optional.ofNullable( | ||
Strings.emptyToNull(jarManifest.getMainAttributes().getValue(attribute))); | ||
} | ||
|
||
static Builder builder() { | ||
return 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.
this is where I'd intuitively reach for streamex and an entrystream, but I think we've found that including too many deps in our gradle plugins causes sad times right?