Skip to content
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

JavaFX Incubator Modules #1375

Closed
wants to merge 38 commits into from
Closed
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d19de4d
WIP: 8309381: Create JavaFX incubator module
kevinrushforth Nov 17, 2023
75e99c0
eclipse config
andy-goryachev-oracle Nov 17, 2023
20098e1
Merge branch 'master'
kevinrushforth Feb 20, 2024
0cb3c6b
Change module name to javafx.incubator.myfeature
kevinrushforth Feb 20, 2024
0e6d56b
Rename package to javafx.incubator.scene.mypkg
kevinrushforth Feb 20, 2024
cd63979
Create INCUBATOR-MODULES.md doc
kevinrushforth Feb 20, 2024
0966c55
Fix typos
kevinrushforth Feb 21, 2024
21d7a7c
Merge remote-tracking branch 'upstream/master' into javafx.incubator
kevinrushforth Feb 29, 2024
02a0b49
Change javafx.incubator to jfx.incubator
kevinrushforth Feb 22, 2024
ee76f09
Say that a JEP is needed to finalize or drop an incubating feature
kevinrushforth Feb 22, 2024
a61f395
Clarify that a feature should not incubate indefinitely
kevinrushforth Feb 23, 2024
acfecc2
Change module name to jfx.incubator.myfeature
kevinrushforth Feb 23, 2024
136776c
Rename package to jfx.incubator.scene.mypkg
kevinrushforth Feb 23, 2024
93ef93e
Bump copyright date
kevinrushforth Feb 23, 2024
a4bf721
Additional comments
kevinrushforth Feb 23, 2024
691d52b
WIP: Utility for printing incubator warning
kevinrushforth Feb 24, 2024
d3bf81e
fixup comments in helper class
kevinrushforth Mar 1, 2024
6b8b571
fix typo
kevinrushforth Mar 1, 2024
1de2efe
add missing javadoc
kevinrushforth Mar 1, 2024
97575a1
Update instructions to create a new incubator module
kevinrushforth Mar 1, 2024
8e04e95
Merge remote-tracking branch 'upstream/master' into javafx.incubator.dev
kevinrushforth May 1, 2024
75d0c07
Add missing dependency on incubator module to systemTests so
kevinrushforth May 1, 2024
ccc5c77
Merge branch 'master' into javafx.incubator.merge
kevinrushforth Jun 13, 2024
1fd34b0
Minor formatting update
kevinrushforth May 10, 2024
5626175
Cleanup prior to preparing RFE for incubator dependencies
kevinrushforth May 10, 2024
bb46c46
Add some implementation notes
kevinrushforth May 13, 2024
dd5f351
Merge branch 'master' into javafx.incubator.dev
kevinrushforth Jul 25, 2024
fa50d1e
Fixup javafx.* assumptions
kevinrushforth Jul 25, 2024
b6631ca
8337281: build.gradle assumes all modules are named "javafx.$project"
kevinrushforth Jul 26, 2024
7d25dd8
Remove debug prints
kevinrushforth Jul 26, 2024
32e3e75
Remove BUG comments and debug prints
kevinrushforth Jul 26, 2024
50d9012
Fix bug where String was being used as if it were a Project
kevinrushforth Jul 26, 2024
4bb1b59
Merge branch '8337281-module-name' into javafx.incubator.dev
kevinrushforth Jul 26, 2024
d8e1f09
Merge remote-tracking branch 'upstream/master' into javafx.incubator
kevinrushforth Oct 18, 2024
6659c48
Comment changes
kevinrushforth Oct 22, 2024
f24f700
Update NOTES-INCUBATOR.md
kevinrushforth Oct 22, 2024
3a7d4bf
Update INCUBATOR-MODULES.md
kevinrushforth Oct 23, 2024
63f768e
Merge branch 'master' into javafx.incubator
kevinrushforth Oct 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions INCUBATOR-MODULES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# JavaFX Incubator Modules

## Overview

In [JEP 11](https://openjdk.org/jeps/11), the JDK provides incubator modules as a means of putting non-final API in the hands of developers, while the API progresses towards either finalization or removal in a future release.

Similarly, some JavaFX APIs would benefit from spending a period of time in a JavaFX release prior to being deemed stable. Being in the mainline `jfx` repository, and thus in downstream binaries such as those at jdk.java.net, makes it easier for interested parties outside of the immediate OpenJDK Community to use the new feature. Experience gained and fed back through the usual channels such as blogs, mailing lists, outreach programs, and conferences can then be acted upon before finalizing, or else removing, the feature in a future release.

This is especially useful for complex features with a large API surface. Such features are nearly impossible to get right the first time, even after an extensive review. Using an incubator module will allow the API to evolve in future releases without the strict compatibility constraints that core JavaFX modules have.

## Description

An incubating feature is an API of non-trivial size, that is under development for eventual inclusion in the core set of JavaFX APIs. The API is not yet sufficiently proven, so it is desirable to defer finalization for a small number of feature releases in order to gain additional experience and feedback.

See [JEP 11](https://openjdk.org/jeps/11) for a description of incubator modules.

JavaFX incubator modules have a few differences from JDK incubator modules:

- A JavaFX incubator module is identified by the `jfx.incubator.` prefix in its module name.
- A JavaFX incubating API is identified by the `jfx.incubator.` prefix in its exported package names. An incubating API is exported only by an incubator module.
- Incubator modules must export incubating APIs only, i.e., packages in the `jfx.incubator` namespace. Consequently:
- JavaFX incubator modules must not export core JavaFX APIs in the `javafx.` namespace. This distinguishes incubator modules from core modules such as `javafx.base`.
- JavaFX non-incubator modules must not specify `requires transitive` dependences upon incubator modules, or otherwise expose types exported from incubator modules in their own exported APIs. In exceptional cases, it may be acceptable for non-incubator modules to specify `requires` dependences (as opposed to `requires transitive`) upon incubator modules.
- JavaFX incubator modules can specify requires or requires transitive dependences upon other incubator modules.
- A warning must be issued when first loading a class from a publicly exported package in a JavaFX incubator module, even if the module is not jlinked into the JDK. We will provide a utility method in `javafx.base` to facilitate this.
- To either make a JavaFX incubating API final, or to remove it, a new JEP should be submitted, referencing the original incubator JEP.
- By default, a JavaFX feature that is delivered in an incubator module will re-incubate in subsequent versions (the default in the JDK is to drop the feature). If any changes are needed to the API, they will be done with new JBS enhancement along with an associated CSR. However, this is not intended to suggest the possibility of a permanently incubating feature. As with incubating features in the JDK, if an incubating API is not promoted to final status after a reasonably small number of JavaFX feature releases, then it will be dropped: its packages and incubator module will be removed. As a guideline:
- An incubating API that was not updated in the current shipping feature release and has not been updated in the feature release being developed, is either stable or is not being actively developed. Such an API should either be finalized or dropped.
- An incubating API that spans beyond a 24-month period (4 feature releases), and is not yet ready to be finalized, will need explicit approval from a Project Lead to remain incubating for some additional period at the discretion of a Project Lead. Otherwise, a Project Lead will submit a removal JEP.
- The submitter of the original JEP can propose to remove it at any time.

## How to add a new incubator module

Use [this patch](https://github.com/openjdk/jfx/pull/1375.diff) as a starting point for your incubator module. Then do the following:
- Rename `modules/jfx.incubator.myfeature` to the desired name of your module, keeping the `jfx.incubator. prefix`
- Modify `build.gradle`, `settings.gradle`, and `modules/javafx.base/src/main/java/module-info.java` to update the name of your module. Look for comments of the form `// TODO: incubator template` for where to make the changes.
- Develop your module as you would with any JavaFX module, keeping in mind the rules in this JEP about public exports and dependencies.

FIXME: find a permanent home for the incubator module template patch, possibly in the jfx-sandbox repo.
181 changes: 164 additions & 17 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,11 @@ List<String> computeLibraryPath(boolean working) {
def modulesLibsDir = "${bundledSdkDir}/modules_libs"

modsWithNative.each() { m ->
lp << cygpath("${modulesLibsDir}/javafx.${m}")
// TODO: incubator dependency
if (m.hasProperty("moduleName")) {
def moduleName = m.ext.moduleName
lp << cygpath("${modulesLibsDir}/${moduleName}")
}
}
} else {
def platformPrefix = ""
Expand Down Expand Up @@ -1676,6 +1680,16 @@ void addMavenPublication(Project project, List<String> projectDependencies) {
return
}

// TODO: incubator dependency
if (!m.hasProperty("moduleName")) {
fail("Project ${m} has no module name")
}
projectDependencies.each { dep ->
if (!dep.hasProperty("moduleName")) {
fail("Project ${m} dependency ${dep} has no module name")
}
}

project.apply plugin: 'maven-publish'

project.group = MAVEN_GROUP_ID
Expand Down Expand Up @@ -1726,7 +1740,9 @@ void addMavenPublication(Project project, List<String> projectDependencies) {
project.publishing {
publications {
maven(MavenPublication) {
artifactId = "javafx-${project.name}"
// TODO: incubator dependency
def artifactName = project.moduleName.replace('.', '-')
artifactId = artifactName

afterEvaluate {
artifact project.tasks."moduleEmptyPublicationJar$t.capital"
Expand All @@ -1745,15 +1761,19 @@ void addMavenPublication(Project project, List<String> projectDependencies) {

Node projectDependencyPlatform = dependencies.appendNode("dependency")
projectDependencyPlatform.appendNode("groupId", MAVEN_GROUP_ID)
projectDependencyPlatform.appendNode("artifactId", "javafx-${project.name}")
// TODO: incubator dependency
projectDependencyPlatform.appendNode("artifactId", artifactName)
projectDependencyPlatform.appendNode("version", MAVEN_VERSION)
projectDependencyPlatform.appendNode("classifier", "\${javafx.platform}")

if (!projectDependencies.empty) {
projectDependencies.each { dep ->
// TODO: incubator dependency
def depName = dep.moduleName.replace('.', '-')
Node projectDependency = dependencies.appendNode("dependency")
projectDependency.appendNode("groupId", MAVEN_GROUP_ID)
projectDependency.appendNode("artifactId", "javafx-$dep")
// TODO: incubator dependency
projectDependency.appendNode("artifactId", depName)
projectDependency.appendNode("version", MAVEN_VERSION)
}
}
Expand Down Expand Up @@ -2752,6 +2772,99 @@ project(":controls") {
addValidateSourceSets(project, sourceSets)
}

// TODO: incubator template
// To create an incubator module:
// 1) apply the changes from this patch
// 2) Look for "TODO: incubator template" comments, and replace "myfeature"
// with the name of your feature
// 3) Refactor / rename the files under "modules/javafx.incubator.myfeature"
// to match the name of your feature.
project(":incubator.myfeature") {
project.ext.buildModule = true
project.ext.includeSources = true
project.ext.moduleRuntime = true
project.ext.moduleName = "jfx.incubator.myfeature"
project.ext.incubating = true

sourceSets {
main
shims {
java {
compileClasspath += sourceSets.main.output
runtimeClasspath += sourceSets.main.output
}
}
test {
java {
compileClasspath += sourceSets.shims.output
runtimeClasspath += sourceSets.shims.output
}
}
}

project.ext.moduleSourcePath = defaultModuleSourcePath
project.ext.moduleSourcePathShim = defaultModuleSourcePathShim

commonModuleSetup(project, [ 'base', 'graphics', 'controls', 'incubator.myfeature' ])

dependencies {
testImplementation project(":base").sourceSets.test.output
testImplementation project(":graphics").sourceSets.test.output
testImplementation project(":controls").sourceSets.test.output
implementation project(':base')
implementation project(':graphics')
implementation project(':controls')
}

test {
jvmArgs "-Djavafx.toolkit=test.com.sun.javafx.pgstub.StubToolkit"
}

def modulePath = "${project.sourceSets.main.java.getDestinationDirectory().get().getAsFile()}"
modulePath += File.pathSeparator + "${rootProject.projectDir}/modules/javafx.controls/build/classes/java/main"
modulePath += File.pathSeparator + "${rootProject.projectDir}/modules/javafx.graphics/build/classes/java/main"
modulePath += File.pathSeparator + "${rootProject.projectDir}/modules/javafx.base/build/classes/java/main"

// TODO: incubator template
// The following block is used if and only if you have .css resource files
// in your incubator module. If you do, uncomment the block and make the
// appropriate changes for the location of your resource files. Otherwise,
// delete the block.

// processResources {
// doLast {
// def cssFiles = fileTree(dir: "$moduleDir/com/sun/javafx/scene/control/skin")
// cssFiles.include "**/*.css"
// cssFiles.each { css ->
// logger.info("converting CSS to BSS ${css}");
//
// javaexec {
// executable = JAVA
// workingDir = project.projectDir
// jvmArgs += patchModuleArgs
// jvmArgs += "--module-path=$modulePath"
// jvmArgs += "--add-modules=javafx.graphics"
// mainClass = "com.sun.javafx.css.parser.Css2Bin"
// args css
// }
// }
// }
// }
//
// def copyShimBssTask = project.task("copyShimBss", type: Copy,
// dependsOn: [project.tasks.getByName("compileJava"),
// project.tasks.getByName("processResources")]) {
// from project.moduleDir
// into project.moduleShimsDir
// include "**/*.bss"
// }
// processShimsResources.dependsOn(copyShimBssTask)

addMavenPublication(project, [ 'graphics' , 'controls'])

addValidateSourceSets(project, sourceSets)
}

project(":swing") {

// We need to skip setting compiler.options.release for this module,
Expand Down Expand Up @@ -3863,7 +3976,18 @@ project(":systemTests") {
testImplementation project(":swing").sourceSets.test.output
}

def dependentProjects = [ 'base', 'graphics', 'controls', 'media', 'web', 'swing', 'fxml' ]
// TODO: incubator dependency
def dependentProjects = [
'base',
'graphics',
'controls',
// TODO: incubator template
'incubator.myfeature',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change fixed the jenkins build of the rich text area incubator, thank you!

'media',
'web',
'swing',
'fxml'
]
commonModuleSetup(project, dependentProjects)

File testJavaPolicyFile = new File(rootProject.buildDir, TESTJAVAPOLICYFILE);
Expand Down Expand Up @@ -4242,9 +4366,19 @@ task javadoc(type: Javadoc, dependsOn: createMSPfile) {
group = "Basic"
description = "Generates the JavaDoc for all the public API"
executable = JAVADOC
// TODO: incubator dependency
def projectsToDocument = [
project(":base"), project(":graphics"), project(":controls"), project(":media"),
project(":swing"), /*project(":swt"),*/ project(":fxml"), project(":web")]
project(":base"),
project(":graphics"),
project(":controls"),
// TODO: incubator template
project(":incubator.myfeature"),
project(":media"),
project(":swing"),
/*project(":swt"),*/
project(":fxml"),
project(":web")
]
source(projectsToDocument.collect({
[it.sourceSets.main.java]
}));
Expand Down Expand Up @@ -5567,6 +5701,8 @@ compileTargets { t ->
def modnames = []
moduleProjList.each { project ->
if (project.hasProperty("moduleName") && project.buildModule) {
// TODO: incubator dependency
def incubating = project.hasProperty("incubating") && project.ext.incubating
modnames << project.ext.moduleName
File dir;
if (project.sourceSets.hasProperty('shims')) {
Expand All @@ -5578,16 +5714,19 @@ compileTargets { t ->
def dstModuleDir = cygpath(dir.path)
modpath << "${dstModuleDir}"

String themod = dir.toURI()
testJavaPolicyFile << "grant codeBase \"${themod}\" {\n" +
" permission java.security.AllPermission;\n" +
"};\n"

dir = new File(rootProject.buildDir, "sdk/lib/${project.ext.moduleName}.jar")
themod = dir.toURI()
runJavaPolicyFile << "grant codeBase \"${themod}\" {\n" +
" permission java.security.AllPermission;\n" +
"};\n"
// TODO: incubator dependency
if (!incubating) {
String themod = dir.toURI()
testJavaPolicyFile << "grant codeBase \"${themod}\" {\n" +
" permission java.security.AllPermission;\n" +
"};\n"

dir = new File(rootProject.buildDir, "sdk/lib/${project.ext.moduleName}.jar")
themod = dir.toURI()
runJavaPolicyFile << "grant codeBase \"${themod}\" {\n" +
" permission java.security.AllPermission;\n" +
"};\n"
}
}
}

Expand Down Expand Up @@ -5633,6 +5772,9 @@ compileTargets { t ->
def jmodName = "${moduleName}.jmod"
def jmodFile = "${jmodsDir}/${jmodName}"

// TODO: incubator dependency
def incubating = project.hasProperty("incubating") && project.ext.incubating

// On Windows, copy the native libraries in the jmod image
// to a "javafx" subdir to avoid conflicting with the Microsoft
// DLLs that are shipped with the JDK
Expand Down Expand Up @@ -5674,6 +5816,11 @@ compileTargets { t ->
if (sourceDateEpoch != null) {
args("--date", extendedTimestamp)
}
// TODO: incubator dependency
if (incubating) {
args("--do-not-resolve-by-default")
args("--warn-if-resolved=incubating")
}
args(jmodFile)
}
}
Expand Down
87 changes: 87 additions & 0 deletions modules/javafx.base/src/main/java/com/sun/javafx/ModuleUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

// TODO: incubator dependency
package com.sun.javafx;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashSet;
import java.util.Set;

/**
* Module utilities.
*/
public class ModuleUtil {

private static final Set<Module> warnedModules = new HashSet<>();
private static final Set<Package> warnedPackages = new HashSet<>();

private static final Module MODULE_JAVA_BASE = Module.class.getModule();

@SuppressWarnings("removal")
public static void incubatorWarning() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps we can add a javadoc to this method explaining why it is here and giving a usage example?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea.

AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
var stackWalker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
var callerClass = stackWalker.walk(s ->
s.dropWhile(f -> {
var clazz = f.getDeclaringClass();
return ModuleUtil.class.equals(clazz) || MODULE_JAVA_BASE.equals(clazz.getModule());
})
.map(StackWalker.StackFrame::getDeclaringClass)
.findFirst()
.orElseThrow(IllegalStateException::new));
//System.err.println("callerClass = " + callerClass);
var callerModule = callerClass.getModule();

// If we are using incubating API from the unnamed module, issue
// a warning one time for each package. This is not a supported
// mode, but can happen if the modular jar is put on the classpath.
if (!callerModule.isNamed()) {
var callerPackage = callerClass.getPackage();
if (!warnedPackages.contains(callerPackage)) {
System.err.println("WARNING: Using incubating API from an unnamed module: " + callerPackage);
warnedPackages.add(callerPackage);
}
return null;
}

// Issue warning one time for this module
if (!warnedModules.contains(callerModule)) {
// FIXME: Check whether this module is jlinked into the runtime
// and thus has already printed a warning. Skip the warning in that
// case to avoid duplicate warnings.
System.err.println("WARNING: Using incubator modules: " + callerModule.getName());
warnedModules.add(callerModule);
}

return null;
});
}

// Prevent instantiation
private ModuleUtil() {
}
}
Loading