Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 32 additions & 1 deletion beast-base/src/assembly/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,20 @@
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.1 http://maven.apache.org/xsd/assembly-2.1.1.xsd">
<!-- Builds the installable BEAST.base package zip:
version.xml (the BEAST.base package descriptor, at repo root)
BEAST.base.src.jar the package sources, at the zip root (BEAST2 convention)
lib/beast-base.jar + its third-party runtime dependencies
examples/ example XML (+ nexus data) shown in BEAUti
beast-pkgmgmt is excluded: it is the bootstrap/launcher and is always
provided by the core distribution, so bundling it here would duplicate
the beast.pkgmgmt module on the module path. -->
the beast.pkgmgmt module on the module path.

Examples are shipped *inside the package* (not only in the release
bundle root) so that a BEAST.base point release can update them via the
package manager: seedBundledPackage()/the package manager extract them
to <user package dir>/BEAST.base/examples, which BeautiTabPane and
PackageHealthChecker discover by scanning getBeastDirectories(). The
curated subset mirrors what release/.../assemble-bundle.sh copies into
the bundle root. -->
<id>package</id>
<formats>
<format>zip</format>
Expand All @@ -18,8 +28,29 @@
<source>${project.basedir}/../version.xml</source>
<outputDirectory>.</outputDirectory>
</file>
<!-- Source jar at the zip root as BEAST.base.src.jar, matching the BEAST2
convention (<PackageName>.src.jar). The -sources.jar is built by
maven-source-plugin (jar-no-fork) in the same package phase, ordered
before the assembly execution so it exists when this runs. -->
<file>
<source>${project.build.directory}/${project.build.finalName}-sources.jar</source>
<outputDirectory>.</outputDirectory>
<destName>BEAST.base.src.jar</destName>
</file>
</files>

<fileSets>
<fileSet>
<directory>${project.basedir}/src/test/resources/beast.base/examples</directory>
<outputDirectory>examples</outputDirectory>
<includes>
<include>*.xml</include>
<include>spec/*.xml</include>
<include>nexus/**</include>
</includes>
</fileSet>
</fileSets>

<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
Expand Down
17 changes: 14 additions & 3 deletions beast-base/src/main/java/beast/base/minimal/BeastMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ public static void printUsage(final Arguments arguments) {
private static void printVersion() {
Log.info("BEAST " + (new BEASTVersion()).getVersionString());
Log.info("---");
// The same package can be discovered through more than one directory on
// the package path (e.g. BEAST.base is found both in the user package dir
// and via the bundle-root version.xml). List each package once, noting
// where it was found; later duplicates are reported as skipped, but only
// in verbose mode.
java.util.Set<String> printed = new java.util.HashSet<>();
for (String jarDirName : PackageManager.getBeastDirectories()) {
File versionFile = new File(jarDirName + "/version.xml");
if (versionFile.exists()) {
Expand All @@ -77,9 +83,14 @@ private static void printVersion() {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document doc = factory.newDocumentBuilder().parse(versionFile);
Element packageElement = doc.getDocumentElement();
Log.info.print(packageElement.getAttribute("name") + " v" + packageElement.getAttribute("version"));
Log.debug.print(" " + jarDirName);
Log.info.print("\n");
String nameAndVersion = packageElement.getAttribute("name") + " v" + packageElement.getAttribute("version");
if (printed.add(nameAndVersion)) {
Log.info.print(nameAndVersion);
Log.debug.print(" (found in " + jarDirName + ")");
Log.info.print("\n");
} else {
Log.debug.println("Skipping duplicate " + nameAndVersion + " found in " + jarDirName);
}
} catch (IOException| SAXException| ParserConfigurationException e) {
Log.err(e.getMessage());
}
Expand Down
10 changes: 10 additions & 0 deletions beast-fx/src/assembly/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.1 http://maven.apache.org/xsd/assembly-2.1.1.xsd">
<!-- Builds the installable BEAST.app package zip:
version.xml (the BEAST.app package descriptor)
BEAST.app.src.jar the package sources, at the zip root (BEAST2 convention)
lib/beast-fx.jar
Only beast-fx.jar is bundled. Its dependencies are provided at runtime:
beast-base (the BEAST.base package), beast-pkgmgmt (the bootstrap), and
Expand All @@ -26,6 +27,15 @@
<source>${project.basedir}/version.xml</source>
<outputDirectory>.</outputDirectory>
</file>
<!-- Source jar at the zip root as BEAST.app.src.jar, matching the BEAST2
convention (<PackageName>.src.jar). The -sources.jar is built by
maven-source-plugin (jar-no-fork) in the same package phase, ordered
before the assembly execution so it exists when this runs. -->
<file>
<source>${project.build.directory}/${project.build.finalName}-sources.jar</source>
<outputDirectory>.</outputDirectory>
<destName>BEAST.app.src.jar</destName>
</file>
</files>

<dependencySets>
Expand Down
17 changes: 14 additions & 3 deletions beast-fx/src/main/java/beastfx/app/beast/BeastMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ public static void printUsage(final Arguments arguments) {
private static void printVersion() {
Log.info("BEAST " + (new BEASTVersion()).getVersionString());
Log.info("---");
// The same package can be discovered through more than one directory on
// the package path (e.g. BEAST.base is found both in the user package dir
// and via the bundle-root version.xml). List each package once, noting
// where it was found; later duplicates are reported as skipped, but only
// in verbose mode.
java.util.Set<String> printed = new java.util.HashSet<>();
for (String jarDirName : PackageManager.getBeastDirectories()) {
File versionFile = new File(jarDirName + "/version.xml");
if (versionFile.exists()) {
Expand All @@ -111,9 +117,14 @@ private static void printVersion() {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document doc = factory.newDocumentBuilder().parse(versionFile);
Element packageElement = doc.getDocumentElement();
Log.info.print(packageElement.getAttribute("name") + " v" + packageElement.getAttribute("version"));
Log.debug.print(" " + jarDirName);
Log.info.print("\n");
String nameAndVersion = packageElement.getAttribute("name") + " v" + packageElement.getAttribute("version");
if (printed.add(nameAndVersion)) {
Log.info.print(nameAndVersion);
Log.debug.print(" (found in " + jarDirName + ")");
Log.info.print("\n");
} else {
Log.debug.println("Skipping duplicate " + nameAndVersion + " found in " + jarDirName);
}
} catch (IOException| SAXException| ParserConfigurationException e) {
Log.err(e.getMessage());
}
Expand Down
31 changes: 30 additions & 1 deletion beast-pkgmgmt/src/main/java/beast/pkgmgmt/BEASTClassLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ public static Class<?> forName(String className) throws ClassNotFoundException {
ClassLoader loader = class2loaderMap.get(className);
try {
return Class.forName(className, false, loader);
} catch (ClassNotFoundException e) {
// The mapped loader cannot see this class. This happens when the
// provider was registered from a version.xml scan (via
// resolveLoaderFor → fallback system/context loader) before the
// owning package's plugin ModuleLayer was registered: the later
// putIfAbsent in registerPluginLayer() then cannot replace the
// stale fallback loader. Fall through to search the plugin layers
// and correct the mapping below.
} catch (NoClassDefFoundError e) {
String missing = e.getMessage();
if (missing != null && (missing.startsWith("beastfx/") || missing.startsWith("beastfx.")
Expand All @@ -91,8 +99,15 @@ public static Class<?> forName(String className) throws ClassNotFoundException {
// 2. Plugin layers (external BEAST packages)
for (ModuleLayer layer : pluginLayers) {
for (Module module : layer.modules()) {
ClassLoader mcl = module.getClassLoader();
if (mcl == null) continue;
try {
return Class.forName(className, false, module.getClassLoader());
Class<?> c = Class.forName(className, false, mcl);
// Self-heal: cache the loader that actually resolved the
// class so subsequent lookups skip the failing mapped loader
// (and the exception-driven fall-through above).
class2loaderMap.put(className, mcl);
return c;
} catch (ClassNotFoundException | NoClassDefFoundError e) {
// try next module
}
Expand Down Expand Up @@ -559,6 +574,20 @@ private static ClassLoader resolveLoaderFor(String provider) {
if (loader != null) return loader;
}
}
// Also consult already-registered plugin layers: a package's classes
// live in its own plugin ModuleLayer (e.g. beast.fx for the BEAUti
// input editors), not in the boot layer. Without this, providers listed
// in a version.xml would be mapped to the fallback system loader, which
// cannot load them, leaving forName() to fall through on every lookup.
for (ModuleLayer layer : pluginLayers) {
for (Module m : layer.modules()) {
java.lang.module.ModuleDescriptor desc = m.getDescriptor();
if (desc != null && desc.packages().contains(pkg)) {
ClassLoader loader = m.getClassLoader();
if (loader != null) return loader;
}
}
}
return fallbackClassLoader();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.spi.ToolProvider;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
Expand Down Expand Up @@ -64,6 +67,47 @@ public void dependentPackageResolvesAgainstAnotherPluginLayer() throws Exception
assertEquals("base+plugin", result, "cross-layer call should execute");
}

/**
* Regression test for the BEAUti "ClassNotFoundException: beastfx.app.inputeditor.*"
* failure. A package's service providers are listed in its version.xml. If a
* version.xml scan registers a provider (via addServices → resolveLoaderFor)
* before the owning package's plugin ModuleLayer is registered, the provider
* is mapped to the fallback system loader, which cannot load it. registerPluginLayer()
* uses putIfAbsent and so cannot replace that stale mapping. forName() must still
* resolve the class by falling through to the plugin layers.
*/
@Test
public void forNameHealsStaleFallbackLoaderMapping() throws Exception {
ToolProvider javac = ToolProvider.findFirst("javac").orElse(null);
Assumptions.assumeTrue(javac != null, "no javac tool available - skipping");

Path work = Files.createTempDirectory("stale-loader-test");

// A modular jar exporting a provider class, mimicking beast.fx's input editors.
Path editorJar = buildModuleJar(javac, work, "fxstaletest", null,
"module fxstaletest { exports fxstaletest; }",
"fxstaletest", "Editor",
"package fxstaletest; public class Editor { }");

// 1. Simulate the version.xml scan that runs before the plugin layer exists:
// the provider is mapped to the fallback system loader (cannot load it).
Map<String, Set<String>> services = Map.of(
"fxstaletest.InputEditor", Set.of("fxstaletest.Editor"));
BEASTClassLoader.classLoader.addServices("FxStaleTest", services);

// 2. Register the package's real plugin layer. putIfAbsent leaves the
// stale fallback mapping from step 1 in place.
boolean loaded = PackageManager.createAndRegisterModuleLayer(
List.of(editorJar), null, "FxStaleTest", "1.0", "FxStaleTest");
assertTrue(loaded, "provider module should load into a plugin layer");

// 3. Despite the stale mapping, forName must resolve the class via the
// plugin layer (previously threw ClassNotFoundException here).
Class<?> editor = BEASTClassLoader.forName("fxstaletest.Editor");
assertNotNull(editor);
assertEquals("fxstaletest.Editor", editor.getName());
}

/** Compile a single-package module and pack it into a jar; return the jar path. */
private static Path buildModuleJar(ToolProvider javac, Path work, String moduleName,
Path modulePathJar, String moduleInfo,
Expand Down
31 changes: 17 additions & 14 deletions release/Linux/assemble-bundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -152,21 +152,24 @@ else
echo " WARNING: DensiTree.jar not found — densitree script will not work"
fi

# ── Step 6: Copy examples ─────────────────────────────────────────────────────
# ── Step 6: Extract examples from the BEAST.base package zip ──────────────────
# The bundle-root examples/ are extracted from the same package zip seeded into
# the user dir (lib/packages/), so the example set has a single source of truth
# (beast-base/src/assembly/package.xml) and the bundle-root and packaged copies
# cannot drift. The bundle-root copy is kept so `beast -validate examples/...`
# works from the install dir before first-run seeding.
echo ""
echo "==> Step 6: Copying examples..."
EXAMPLES_DIR="$REPO_ROOT/beast-base/src/test/resources/beast.base/examples"
if [ -d "$EXAMPLES_DIR" ]; then
find "$EXAMPLES_DIR" -maxdepth 1 -name "*.xml" -exec cp {} "$BUNDLE/examples/" \;
mkdir -p "$BUNDLE/examples/spec"
[ -d "$EXAMPLES_DIR/spec" ] && \
find "$EXAMPLES_DIR/spec" -maxdepth 1 -name "*.xml" -exec cp {} "$BUNDLE/examples/spec/" \;
[ -d "$EXAMPLES_DIR/nexus" ] && cp -r "$EXAMPLES_DIR/nexus" "$BUNDLE/examples/"
EXAMPLE_COUNT=$(find "$BUNDLE/examples" -name "*.xml" | wc -l | tr -d ' ')
echo " Copied ${EXAMPLE_COUNT} example XML files (incl. spec/)"
else
echo " WARNING: examples not found at $EXAMPLES_DIR"
fi
echo "==> Step 6: Extracting examples from $(basename "$BASE_PKG_ZIP")..."
# Extract the full zip to a temp dir then copy examples/ — avoids unzip glob
# portability issues where 'examples/*' may not match subdirs on some platforms.
# Which subdirs are included/excluded is controlled by the include patterns in
# beast-base/src/assembly/package.xml (e.g. spec/*.xml excludes spec/beast2vs1/).
EXAMPLES_TMP=$(mktemp -d)
unzip -o -q "$BASE_PKG_ZIP" -d "$EXAMPLES_TMP"
cp -r "$EXAMPLES_TMP/examples/." "$BUNDLE/examples/"
rm -rf "$EXAMPLES_TMP"
EXAMPLE_COUNT=$(find "$BUNDLE/examples" -name "*.xml" | wc -l | tr -d ' ')
echo " Extracted ${EXAMPLE_COUNT} example XML files (incl. spec/)"

# ── Step 7: Copy version.xml and docs ────────────────────────────────────────
echo ""
Expand Down
4 changes: 2 additions & 2 deletions release/Linux/linuxbin/applauncher
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ else
fi

if [ -n "$BEAST_EXTRA_LIBS" ]; then
exec "$JAVA" --module-path "$APP" --add-modules ALL-MODULE-PATH,javafx.controls,javafx.fxml,javafx.swing,javafx.web,jdk.jsobject \
exec "$JAVA" --module-path "$APP" --add-modules ALL-MODULE-PATH,javafx.controls,javafx.fxml,javafx.swing,javafx.web,jdk.jsobject --enable-native-access=javafx.graphics,javafx.media,javafx.web \
-Xss256m -Xmx8g -Djava.library.path="$BEAST_EXTRA_LIBS" -Duser.language=en -Dfile.encoding=UTF-8 \
-m beast.pkgmgmt/beast.pkgmgmt.launcher.AppLauncherLauncher "$@"
else
exec "$JAVA" --module-path "$APP" --add-modules ALL-MODULE-PATH,javafx.controls,javafx.fxml,javafx.swing,javafx.web,jdk.jsobject \
exec "$JAVA" --module-path "$APP" --add-modules ALL-MODULE-PATH,javafx.controls,javafx.fxml,javafx.swing,javafx.web,jdk.jsobject --enable-native-access=javafx.graphics,javafx.media,javafx.web \
-Xss256m -Xmx8g -Duser.language=en -Dfile.encoding=UTF-8 \
-m beast.pkgmgmt/beast.pkgmgmt.launcher.AppLauncherLauncher "$@"
fi
2 changes: 1 addition & 1 deletion release/Linux/linuxbin/beast
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,6 @@ if [ -n "$BEAST_EXTRA_LIBS" ]; then
fi
fi

exec "$JAVA" --module-path "$APP" --add-modules ALL-MODULE-PATH,javafx.controls,javafx.fxml,javafx.swing,javafx.web,jdk.jsobject \
exec "$JAVA" --module-path "$APP" --add-modules ALL-MODULE-PATH,javafx.controls,javafx.fxml,javafx.swing,javafx.web,jdk.jsobject --enable-native-access=javafx.graphics,javafx.media,javafx.web \
-Xss256m -Xmx8g -Djava.library.path="$LD_LIBRARY_PATH" -Duser.language=en -Dfile.encoding=UTF-8 \
-m beast.pkgmgmt/beast.pkgmgmt.launcher.BeastLauncher "$@"
2 changes: 1 addition & 1 deletion release/Linux/linuxbin/beauti
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ JAVA_HOME="$BUNDLE_HOME/jre"
export JAVA_HOME
JAVA="$JAVA_HOME/bin/java"
APP="$BUNDLE_HOME/lib"
exec "$JAVA" --module-path "$APP" --add-modules ALL-MODULE-PATH,javafx.controls,javafx.fxml,javafx.swing,javafx.web,jdk.jsobject \
exec "$JAVA" --module-path "$APP" --add-modules ALL-MODULE-PATH,javafx.controls,javafx.fxml,javafx.swing,javafx.web,jdk.jsobject --enable-native-access=javafx.graphics,javafx.media,javafx.web \
-Xss256m -Xmx8g -Duser.language=en -Dfile.encoding=UTF-8 \
-m beast.pkgmgmt/beast.pkgmgmt.launcher.BeautiLauncher -capture "$@"
4 changes: 2 additions & 2 deletions release/Linux/linuxbin/loganalyser
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ else
fi

if [ -n "$BEAST_EXTRA_LIBS" ]; then
exec "$JAVA" --module-path "$APP" --add-modules ALL-MODULE-PATH,javafx.controls,javafx.fxml,javafx.swing,javafx.web,jdk.jsobject \
exec "$JAVA" --module-path "$APP" --add-modules ALL-MODULE-PATH,javafx.controls,javafx.fxml,javafx.swing,javafx.web,jdk.jsobject --enable-native-access=javafx.graphics,javafx.media,javafx.web \
-Xss256m -Xmx8g -Djava.library.path="$BEAST_EXTRA_LIBS" -Duser.language=en -Dfile.encoding=UTF-8 \
-m beast.pkgmgmt/beast.pkgmgmt.launcher.AppLauncherLauncher beastfx.app.tools.LogAnalyser "$@"
else
exec "$JAVA" --module-path "$APP" --add-modules ALL-MODULE-PATH,javafx.controls,javafx.fxml,javafx.swing,javafx.web,jdk.jsobject \
exec "$JAVA" --module-path "$APP" --add-modules ALL-MODULE-PATH,javafx.controls,javafx.fxml,javafx.swing,javafx.web,jdk.jsobject --enable-native-access=javafx.graphics,javafx.media,javafx.web \
-Xss256m -Xmx8g -Duser.language=en -Dfile.encoding=UTF-8 \
-m beast.pkgmgmt/beast.pkgmgmt.launcher.AppLauncherLauncher beastfx.app.tools.LogAnalyser "$@"
fi
2 changes: 1 addition & 1 deletion release/Linux/linuxbin/logcombiner
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ JAVA_HOME="$BUNDLE_HOME/jre"
export JAVA_HOME
JAVA="$JAVA_HOME/bin/java"
APP="$BUNDLE_HOME/lib"
exec "$JAVA" --module-path "$APP" --add-modules ALL-MODULE-PATH,javafx.controls,javafx.fxml,javafx.swing,javafx.web,jdk.jsobject \
exec "$JAVA" --module-path "$APP" --add-modules ALL-MODULE-PATH,javafx.controls,javafx.fxml,javafx.swing,javafx.web,jdk.jsobject --enable-native-access=javafx.graphics,javafx.media,javafx.web \
-Xss256m -Xmx8g -Duser.language=en -Dfile.encoding=UTF-8 \
-m beast.pkgmgmt/beast.pkgmgmt.launcher.LogCombinerLauncher "$@"
Loading