From 7b23ec3c762dca02e0b955c06183d10fb605114b Mon Sep 17 00:00:00 2001 From: Miki Rozloznik Date: Fri, 29 Nov 2024 11:23:37 +0100 Subject: [PATCH] [#674] Implement automatic loading of extensions from working directory --- README.md | 3 +- ant_task/src/zserio/ant/ToolWrapper.java | 20 +-- compiler/core/build.xml | 2 +- .../src/zserio/tools/ExtensionManager.java | 114 +++++++++++++++++- test/core/build.xml | 4 +- test/extensions/build.xml | 4 +- 6 files changed, 124 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index c8b9c7428..a1c4a286a 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,8 @@ not generate anything. Each Zserio extension should be packed in a single jar file. All Zserio extensions which are available on the Java classpath are automatically loaded during Zserio compiler -startup. +startup. Extensions which are located in the directory from which the Zserio jar executable has been run and +which are named with the prefix `zserio_` are automatically loaded as well. More information how to implement a new Zserio extension can be found in the [Zserio extension sample](https://github.com/ndsev/zserio-extension-sample#zserio-extension-sample). diff --git a/ant_task/src/zserio/ant/ToolWrapper.java b/ant_task/src/zserio/ant/ToolWrapper.java index 9de359010..c0a720947 100644 --- a/ant_task/src/zserio/ant/ToolWrapper.java +++ b/ant_task/src/zserio/ant/ToolWrapper.java @@ -1,5 +1,6 @@ package zserio.ant; +import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; @@ -40,25 +41,16 @@ public ToolWrapper(String className, Iterable classPath, boolean ignoreErr String [] files = p.list(); for (String f : files) { - String u = null; try { - if (f.endsWith(".jar")) - { - u = "jar:file:"+f+"!/"; - } - else - { - u = "file:"+f+"/"; - } - - System.out.println("Adding " + u + " to classpath"); - urls.add(new URL(u)); + final URL pathUrl = new File(f).toURI().toURL(); + System.out.println("Adding " + pathUrl + " to classpath"); + urls.add(pathUrl); } - catch (MalformedURLException e) + catch (MalformedURLException | RuntimeException e) { - throw new BuildException("Malformed URL: " + u); + throw new BuildException("Malformed URL from file: " + f + " (" + e + ")"); } } } diff --git a/compiler/core/build.xml b/compiler/core/build.xml index 04dddd2e4..36cdde263 100644 --- a/compiler/core/build.xml +++ b/compiler/core/build.xml @@ -81,7 +81,7 @@ spotbugs.home_dir - Location of the spotbugs tool. If not set, spotbugs is - + diff --git a/compiler/core/src/zserio/tools/ExtensionManager.java b/compiler/core/src/zserio/tools/ExtensionManager.java index 658419e64..f0d7682d0 100644 --- a/compiler/core/src/zserio/tools/ExtensionManager.java +++ b/compiler/core/src/zserio/tools/ExtensionManager.java @@ -1,13 +1,23 @@ package zserio.tools; +import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.ServiceLoader; +import java.util.jar.JarFile; +import java.util.jar.Manifest; import zserio.ast.Root; import zserio.extension.common.ZserioExtensionException; @@ -26,7 +36,7 @@ public ExtensionManager(CommandLineArguments commandLineArguments) { extensions = new ArrayList(); this.commandLineArguments = commandLineArguments; - ServiceLoader loader = ServiceLoader.load(Extension.class, getClass().getClassLoader()); + final ServiceLoader loader = ServiceLoader.load(Extension.class, getClassLoader()); Iterator it = loader.iterator(); while (it.hasNext()) { @@ -94,6 +104,108 @@ public void callExtensions(Root rootNode, ExtensionParameters parameters) throws } } + private ClassLoader getClassLoader() + { + final ClassLoader currentClassLoader = getClass().getClassLoader(); + final File workingDirectory = getWorkingDirectory(); + if (workingDirectory == null) + return currentClassLoader; + + final File[] fileList = workingDirectory.listFiles(); + if (fileList == null) + return currentClassLoader; + + final ArrayList urlArray = new ArrayList(); + try + { + for (File file : fileList) + { + if (isFileZserioExtension(file)) + { + urlArray.add(file.toURI().toURL()); + urlArray.addAll(getDependentJarsFromManifest(file)); + } + } + } + catch (MalformedURLException excpt) + { + return currentClassLoader; + } + + final URLClassLoader urlClassLoader = + new URLClassLoader(urlArray.toArray(new URL[urlArray.size()]), currentClassLoader); + + return urlClassLoader; + } + + private File getWorkingDirectory() + { + try + { + final URI execUri = getClass().getProtectionDomain().getCodeSource().getLocation().toURI(); + final Path execPath = Paths.get(execUri); + final Path execParentPath = execPath.getParent(); + + return (execParentPath == null) ? null : execParentPath.toFile(); + } + catch (SecurityException | URISyntaxException excpt) + { + return null; + } + } + + private boolean isFileZserioExtension(File file) + { + if (!file.isFile()) + return false; + + final String fileName = file.getName(); + if (!fileName.endsWith(".jar")) + return false; + + if (!fileName.startsWith("zserio_")) + return false; + + if (fileName.equals("zserio_core.jar")) + return false; + + if (fileName.endsWith("_javadocs.jar")) + return false; + + if (fileName.endsWith("_sources.jar")) + return false; + + return true; + } + + private List getDependentJarsFromManifest(File file) + { + final ArrayList dependentJars = new ArrayList(); + try (JarFile jarFile = new JarFile(file)) + { + final Manifest manifest = jarFile.getManifest(); + if (manifest != null) + { + final String classPaths = manifest.getMainAttributes().getValue("class-path"); + if (classPaths != null) + { + final File parentFile = file.getParentFile(); + for (String classPath : classPaths.split("\\s+")) + { + final File dependentJarFile = new File(parentFile, classPath); + dependentJars.add(dependentJarFile.toURI().toURL()); + } + } + } + } + catch (IOException excpt) + { + // silently ignored + } + + return dependentJars; + } + private static void check(Root rootNode, Extension extension, ExtensionParameters parameters) throws ZserioExtensionException { diff --git a/test/core/build.xml b/test/core/build.xml index 778f102f2..6f268f785 100644 --- a/test/core/build.xml +++ b/test/core/build.xml @@ -123,9 +123,7 @@ spotbugs.home_dir - Location of the spotbugs tool. If not set, spo - - - + diff --git a/test/extensions/build.xml b/test/extensions/build.xml index 4f3fbced7..4d8f6f2ff 100644 --- a/test/extensions/build.xml +++ b/test/extensions/build.xml @@ -138,9 +138,7 @@ spotbugs.home_dir - Location of the spotbugs tool. If not set, spo - - - +