Add classpath management to jagidir compiler.
authorFredrik Tolf <fredrik@dolda2000.com>
Thu, 9 Nov 2023 19:33:46 +0000 (20:33 +0100)
committerFredrik Tolf <fredrik@dolda2000.com>
Thu, 9 Nov 2023 19:33:46 +0000 (20:33 +0100)
src/jagi/fs/Compiler.java

index 14b3786..e7a66a1 100644 (file)
@@ -6,9 +6,47 @@ import java.util.regex.*;
 import java.nio.file.*;
 import java.nio.file.attribute.*;
 import java.io.*;
+import java.net.*;
 
 public class Compiler {
     private final Map<Path, File> files = new HashMap<>();
+    private final Map<Path, ClassLoader> libs = new HashMap<>();
+    private final Collection<Path> searchpath = new ArrayList<>();
+
+    public Compiler() {
+       String syspath = System.getenv("PATH");
+       if(syspath != null) {
+           String sep = java.io.File.pathSeparator;
+           int p1 = 0, p2 = syspath.indexOf(sep);
+           do {
+               String el;
+               if(p2 >= 0) {
+                   el = syspath.substring(p1, p2);
+                   p1 = p2 + sep.length();
+                   p2 = syspath.indexOf(sep, p1);
+               } else {
+                   el =syspath.substring(p1);
+               }
+               try {
+                   Path p = Paths.get(el);
+                   if(p.getParent() != null)
+                       searchpath.add(p.getParent().resolve("share").resolve("java"));
+               } catch(InvalidPathException e) {
+                   continue;
+               }
+           } while(p2 >= 0);
+       }
+       String proppath = System.getProperty("jagi.search-path");
+       if(proppath != null) {
+           for(String el : proppath.split(":")) {
+               try {
+                   searchpath.add(Paths.get(el));
+               } catch(InvalidPathException e) {
+                   continue;
+               }
+           }
+       }
+    }
 
     public static class ClassOutput {
        public final String name;
@@ -46,8 +84,9 @@ public class Compiler {
     }
 
     public static class Compilation implements AutoCloseable {
-       private final Path dir, srcdir, outdir;
+       public final Path dir, srcdir, outdir;
        private final List<Path> infiles = new ArrayList<>();
+       private final List<Path> classpath = new ArrayList<>();
        private List<String> output = null;
 
        public Compilation() throws IOException {
@@ -62,11 +101,25 @@ public class Compiler {
            infiles.add(p);
        }
 
+       public void classpath(Path p) {
+           classpath.add(p);
+       }
+
        public boolean compile() throws IOException {
            List<String> args = new ArrayList<>();
            args.add("javac");
            args.add("-d");
            args.add(outdir.toString());
+           if(!classpath.isEmpty()) {
+               StringBuilder buf = new StringBuilder();
+               for(Path cp : classpath) {
+                   if(buf.length() > 0)
+                       buf.append(":");
+                   buf.append(cp.toString());
+               }
+               args.add("-cp");
+               args.add(buf.toString());
+           }
            for(Path p : infiles)
                args.add(p.toString());
            ProcessBuilder cmd = new ProcessBuilder(args);
@@ -134,7 +187,8 @@ public class Compiler {
     public static class BufferedClassLoader extends ClassLoader {
        public final Map<String, byte[]> contents;
 
-       public BufferedClassLoader(Collection<ClassOutput> contents) {
+       public BufferedClassLoader(ClassLoader parent, Collection<ClassOutput> contents) {
+           super(parent);
            this.contents = new HashMap<>();
            for(ClassOutput clc : contents)
                this.contents.put(clc.name, clc.buf.toByteArray());
@@ -148,27 +202,86 @@ public class Compiler {
        }
     }
 
-    public static class Module {
+    public static class LibClassLoader extends ClassLoader {
+       private final ClassLoader[] classpath;
+
+       public LibClassLoader(ClassLoader parent, Collection<ClassLoader> classpath) {
+           super(parent);
+           this.classpath = classpath.toArray(new ClassLoader[0]);
+       }
+
+       public Class<?> findClass(String name) throws ClassNotFoundException {
+           for(ClassLoader lib : classpath) {
+               try {
+                   return(lib.loadClass(name));
+               } catch(ClassNotFoundException e) {}
+           }
+           throw(new ClassNotFoundException("Could not find " + name + " in any of " + Arrays.asList(classpath).toString()));
+       }
+    }
+
+    public ClassLoader libloader(Path p) {
+       synchronized(libs) {
+           ClassLoader ret = libs.get(p);
+           if(ret == null) {
+               try {
+                   libs.put(p, ret = new URLClassLoader(new URL[] {p.toUri().toURL()}));
+               } catch(MalformedURLException e) {
+                   throw(new RuntimeException(e));
+               }
+           }
+           return(ret);
+       }
+    }
+
+    private Path findlib(String nm) {
+       for(Path dir : searchpath) {
+           Path jar = dir.resolve(nm + ".jar");
+           if(Files.isRegularFile(jar))
+               return(jar);
+       }
+       return(null);
+    }
+
+    private static final Pattern classpat = Pattern.compile("^((public|abstract)\\s+)*(class|interface)\\s+(\\S+)");
+    private static final Pattern libpat = Pattern.compile("\\$use\\s*:\\s*(\\S+)");
+    public class Module {
        public final Path file;
        public final ClassLoader code;
+       public final Collection<Path> classpath = new ArrayList<>();
 
        public Module(Path file) throws IOException {
            this.file = file;
            try(Compilation c = new Compilation()) {
                split(c);
+               for(Path cp : classpath)
+                   c.classpath(cp);
                if(!c.compile())
                    throw(new CompilationException(file, c.output()));
-               code = new BufferedClassLoader(c.classes());
+               ClassLoader parent = Compiler.class.getClassLoader();
+               if(!classpath.isEmpty()) {
+                   Collection<ClassLoader> libs = new ArrayList<>();
+                   for(Path cp : classpath)
+                       libs.add(libloader(cp));
+                   parent = new LibClassLoader(parent, libs);
+               }
+               code = new BufferedClassLoader(parent, c.classes());
            }
        }
 
-       private static final Pattern classpat = Pattern.compile("^((public|abstract)\\s+)*(class|interface)\\s+(\\S+)");
        public void split(Compilation c) throws IOException {
            StringBuilder head = new StringBuilder();
            BufferedWriter cur = null;
            try(BufferedReader fp = Files.newBufferedReader(file)) {
                for(String ln = fp.readLine(); ln != null; ln = fp.readLine()) {
-                   Matcher m = classpat.matcher(ln);
+                   Matcher m = libpat.matcher(ln);
+                   if(m.find()) {
+                       Path lib = findlib(m.group(1));
+                       if(lib == null)
+                           throw(new CompilationException(file, Arrays.asList("no such library: " + m.group(1))));
+                       classpath.add(lib);
+                   }
+                   m = classpat.matcher(ln);
                    if(m.find()) {
                        String clnm = m.group(4);
                        Path sp = c.srcdir.resolve(clnm + ".java");
@@ -191,7 +304,7 @@ public class Compiler {
        }
     }
 
-    public static class File {
+    public class File {
        public final Path name;
        private FileTime mtime = null;
        private Module mod = null;