Skip to content

Commit

Permalink
[#674] Implement automatic loading of extensions from working directory
Browse files Browse the repository at this point in the history
  • Loading branch information
mikir committed Dec 10, 2024
1 parent e0e6c96 commit 7b23ec3
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 23 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
20 changes: 6 additions & 14 deletions ant_task/src/zserio/ant/ToolWrapper.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -40,25 +41,16 @@ public ToolWrapper(String className, Iterable<Path> 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 + ")");
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/core/build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ spotbugs.home_dir - Location of the spotbugs tool. If not set, spotbugs is
<attribute name="package" default="zserio.antlr"/>
<sequential>
<echo message="Generating ANTLR 4 grammar: @{target}"/>
<java classname="org.antlr.v4.Tool" failonerror="true">
<java classname="org.antlr.v4.Tool" failonerror="true" fork="true">
<classpath>
<pathelement location="${3rdparty.jar_dir}/${3rdparty.antlr4.jar_file_name}"/>
</classpath>
Expand Down
114 changes: 113 additions & 1 deletion compiler/core/src/zserio/tools/ExtensionManager.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -26,7 +36,7 @@ public ExtensionManager(CommandLineArguments commandLineArguments)
{
extensions = new ArrayList<Extension>();
this.commandLineArguments = commandLineArguments;
ServiceLoader<Extension> loader = ServiceLoader.load(Extension.class, getClass().getClassLoader());
final ServiceLoader<Extension> loader = ServiceLoader.load(Extension.class, getClassLoader());
Iterator<Extension> it = loader.iterator();
while (it.hasNext())
{
Expand Down Expand Up @@ -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<URL> urlArray = new ArrayList<URL>();
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<URL> getDependentJarsFromManifest(File file)
{
final ArrayList<URL> dependentJars = new ArrayList<URL>();
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
{
Expand Down
4 changes: 1 addition & 3 deletions test/core/build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,7 @@ spotbugs.home_dir - Location of the spotbugs tool. If not set, spo
<zserio srcPath="${@{testName}.zs.in_dir}" srcFile="@{zsFile}" ignoreError="@{ignoreErrors}"
extraArgs="${zserio.extra_args}">
<classpath>
<fileset dir="${zserio.jar_dir}">
<include name="*.jar"/>
</fileset>
<file name="${zserio.jar_dir}/zserio_core.jar"/>
</classpath>
<args/>
</zserio>
Expand Down
4 changes: 1 addition & 3 deletions test/extensions/build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,7 @@ spotbugs.home_dir - Location of the spotbugs tool. If not set, spo
<zserio srcPath="${@{testName}.zs.in_dir}" srcFile="@{zsFile}" ignoreError="@{ignoreErrors}"
extraArgs="${zserio.extra_args}">
<classpath>
<fileset dir="${zserio.jar_dir}">
<include name="*.jar"/>
</fileset>
<file name="${zserio.jar_dir}/zserio_core.jar"/>
</classpath>
<arg name="java" value="${@{testName}.zs.out_dir}"/>
<args/>
Expand Down

0 comments on commit 7b23ec3

Please sign in to comment.