Introduce Java builder
authorMathieu Baudier <mbaudier@argeo.org>
Sat, 8 Oct 2022 06:59:17 +0000 (08:59 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Sat, 8 Oct 2022 06:59:17 +0000 (08:59 +0200)
.classpath [new file with mode: 0644]
.gitignore
META-INF/MANIFEST.MF [new file with mode: 0644]
build.properties [new file with mode: 0644]
java/org/argeo/build/Make.java [new file with mode: 0644]
osgi.mk

diff --git a/.classpath b/.classpath
new file mode 100644 (file)
index 0000000..d706197
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
+               <attributes>
+                       <attribute name="module" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="java"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
index 3a4edf690ca5fd2213e31a83ede1503d5c73ccfa..fbfcfb6d32680f4ec3ba72e7464eb97d52b23598 100644 (file)
@@ -1 +1,3 @@
 .project
+*.class
+/bin/
diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF
new file mode 100644 (file)
index 0000000..3e02ca1
--- /dev/null
@@ -0,0 +1,10 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Automatic-Module-Name: argeo-build
+Bundle-Name: argeo-build
+Bundle-RequiredExecutionEnvironment: JavaSE-17
+Bundle-SymbolicName: argeo-build
+Bundle-Version: 2.3.9.next
+Import-Package: aQute.bnd.osgi;version="4.7.0",
+ org.eclipse.jdt.core.compiler
+Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=17))"
diff --git a/build.properties b/build.properties
new file mode 100644 (file)
index 0000000..31bbddc
--- /dev/null
@@ -0,0 +1,4 @@
+additional.bundles = org.slf4j.api
+bin.includes = META-INF/,\
+               java/org/
+source.. = java
\ No newline at end of file
diff --git a/java/org/argeo/build/Make.java b/java/org/argeo/build/Make.java
new file mode 100644 (file)
index 0000000..753f729
--- /dev/null
@@ -0,0 +1,268 @@
+package org.argeo.build;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import aQute.bnd.osgi.Analyzer;
+import aQute.bnd.osgi.Jar;
+
+public class Make {
+       private final static String SDK_MK = "sdk.mk";
+
+       final Path execDirectory;
+       final Path sdkSrcBase;
+       final Path argeoBuildBase;
+       final Path sdkBuildBase;
+       final Path buildBase;
+
+       public Make() throws IOException {
+               execDirectory = Paths.get(System.getProperty("user.dir"));
+               Path sdkMkP = findSdkMk(execDirectory);
+               Objects.requireNonNull(sdkMkP, "No " + SDK_MK + " found under " + execDirectory);
+
+               Map<String, String> context = new HashMap<>();
+               List<String> sdkMkLines = Files.readAllLines(sdkMkP);
+               lines: for (String line : sdkMkLines) {
+                       StringTokenizer st = new StringTokenizer(line, " :=");
+                       if (!st.hasMoreTokens())
+                               continue lines;
+                       String key = st.nextToken();
+                       if (!st.hasMoreTokens())
+                               continue lines;
+                       String value = st.nextToken();
+                       context.put(key, value);
+               }
+
+               sdkSrcBase = Paths.get(context.computeIfAbsent("SDK_SRC_BASE", (key) -> {
+                       throw new IllegalStateException(key + " not found");
+               })).toAbsolutePath();
+               argeoBuildBase = sdkSrcBase.resolve("sdk/argeo-build");
+
+               sdkBuildBase = Paths.get(context.computeIfAbsent("SDK_BUILD_BASE", (key) -> {
+                       throw new IllegalStateException(key + " not found");
+               })).toAbsolutePath();
+               buildBase = sdkBuildBase.resolve(sdkSrcBase.getFileName());
+       }
+
+       /*
+        * ACTIONS
+        */
+
+       void bundle(Map<String, List<String>> options) throws IOException {
+               // generate manifests
+               subDirs: for (Path subDir : Files.newDirectoryStream(buildBase, (p) -> Files.isDirectory(p))) {
+                       String bundleSymbolicName = subDir.getFileName().toString();
+                       if (!bundleSymbolicName.contains("."))
+                               continue subDirs;
+                       generateManifest(bundleSymbolicName, subDir);
+               }
+
+               // create jars
+               subDirs: for (Path subDir : Files.newDirectoryStream(buildBase, (p) -> Files.isDirectory(p))) {
+                       String bundleSymbolicName = subDir.getFileName().toString();
+                       if (!bundleSymbolicName.contains("."))
+                               continue subDirs;
+                       Path source = sdkSrcBase.resolve(subDir.getFileName());
+                       if (!Files.exists(source))
+                               continue subDirs;
+                       Path jarP = buildBase.resolve(subDir.getFileName() + ".jar");
+                       createJar(source, subDir, jarP);
+               }
+
+       }
+
+       /*
+        * BND
+        */
+
+       void generateManifest(String bundleSymbolicName, Path compiled) throws IOException {
+               Properties properties = new Properties();
+               Path argeoBnd = argeoBuildBase.resolve("argeo.bnd");
+               try (InputStream in = Files.newInputStream(argeoBnd)) {
+                       properties.load(in);
+               }
+               // FIXME make it configurable
+               Path branchBnd = sdkSrcBase.resolve("cnf/unstable.bnd");
+               try (InputStream in = Files.newInputStream(branchBnd)) {
+                       properties.load(in);
+               }
+
+               Path bndBnd = compiled.resolve("bnd.bnd");
+               try (InputStream in = Files.newInputStream(bndBnd)) {
+                       properties.load(in);
+               }
+
+               // Normalise
+               properties.put("Bundle-SymbolicName", bundleSymbolicName);
+
+               // Calculate MANIFEST
+               Path binP = compiled.resolve("bin");
+               Manifest manifest;
+               try (Analyzer bndAnalyzer = new Analyzer()) {
+                       bndAnalyzer.setProperties(properties);
+                       Jar jar = new Jar(bundleSymbolicName, binP.toFile());
+                       bndAnalyzer.setJar(jar);
+                       manifest = bndAnalyzer.calcManifest();
+
+//                     keys: for (Object key : manifest.getMainAttributes().keySet()) {
+//                             System.out.println(key + ": " + manifest.getMainAttributes().getValue(key.toString()));
+//                     }
+               } catch (Exception e) {
+                       throw new RuntimeException("Bnd analysis of " + compiled + " failed", e);
+               }
+
+               // Write manifest
+               Path manifestP = compiled.resolve("META-INF/MANIFEST.MF");
+               Files.createDirectories(manifestP.getParent());
+               try (OutputStream out = Files.newOutputStream(manifestP)) {
+                       manifest.write(out);
+               }
+       }
+
+       /*
+        * JAR PACKAGING
+        */
+       void createJar(Path source, Path compiled, Path jarP) throws IOException {
+               // Load manifest
+               Path manifestP = compiled.resolve("META-INF/MANIFEST.MF");
+               if (!Files.exists(manifestP))
+                       throw new IllegalStateException("Manifest " + manifestP + " not found");
+               Manifest manifest;
+               try (InputStream in = Files.newInputStream(manifestP)) {
+                       manifest = new Manifest(in);
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot read manifest " + manifestP, e);
+               }
+
+               // Load excludes
+               List<PathMatcher> excludes = new ArrayList<>();
+               Path excludesP = argeoBuildBase.resolve("excludes.txt");
+               for (String line : Files.readAllLines(excludesP)) {
+                       PathMatcher pathMatcher = excludesP.getFileSystem().getPathMatcher("glob:" + line);
+                       excludes.add(pathMatcher);
+               }
+
+               Files.createDirectories(jarP.getParent());
+               try (JarOutputStream jarOut = new JarOutputStream(Files.newOutputStream(jarP), manifest)) {
+                       // add all classes first
+                       Path binP = compiled.resolve("bin");
+                       Files.walkFileTree(binP, new SimpleFileVisitor<Path>() {
+
+                               @Override
+                               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                                       jarOut.putNextEntry(new JarEntry(binP.relativize(file).toString()));
+                                       Files.copy(file, jarOut);
+                                       return FileVisitResult.CONTINUE;
+                               }
+                       });
+
+                       // add resources
+                       Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
+
+                               @Override
+                               public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+                                       Path relativeP = source.relativize(dir);
+                                       for (PathMatcher exclude : excludes)
+                                               if (exclude.matches(relativeP))
+                                                       return FileVisitResult.SKIP_SUBTREE;
+
+                                       return FileVisitResult.CONTINUE;
+                               }
+
+                               @Override
+                               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                                       Path relativeP = source.relativize(file);
+                                       for (PathMatcher exclude : excludes)
+                                               if (exclude.matches(relativeP))
+                                                       return FileVisitResult.CONTINUE;
+                                       JarEntry entry = new JarEntry(relativeP.toString());
+                                       jarOut.putNextEntry(entry);
+                                       Files.copy(file, jarOut);
+                                       return FileVisitResult.CONTINUE;
+                               }
+
+                       });
+
+                       // add sources
+                       // TODO add effective BND, Eclipse project file, etc., in order to be able to
+                       // repackage
+                       Path srcP = source.resolve("src");
+                       Files.walkFileTree(srcP, new SimpleFileVisitor<Path>() {
+
+                               @Override
+                               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                                       jarOut.putNextEntry(new JarEntry("OSGI-OPT/src/" + srcP.relativize(file).toString()));
+                                       Files.copy(file, jarOut);
+                                       return FileVisitResult.CONTINUE;
+                               }
+                       });
+
+               }
+
+       }
+
+       private Path findSdkMk(Path directory) {
+               Path sdkMkP = directory.resolve(SDK_MK);
+               if (Files.exists(sdkMkP)) {
+                       return sdkMkP.toAbsolutePath();
+               }
+               if (directory.getParent() == null)
+                       return null;
+               return findSdkMk(directory.getParent());
+
+       }
+
+       public static void main(String... args) throws IOException {
+               if (args.length == 0)
+                       throw new IllegalArgumentException("At least an action must be provided");
+               int actionIndex = 0;
+               String action = args[actionIndex];
+               if (args.length > actionIndex + 1 && args[actionIndex + 1].startsWith("-"))
+                       throw new IllegalArgumentException(
+                                       "Action " + action + " must be followed by an option: " + Arrays.asList(args));
+
+               Map<String, List<String>> options = new HashMap<>();
+               String currentOption = null;
+               List<String> currentOptionValues = null;
+               for (int i = actionIndex + 1; i < args.length; i++) {
+                       if (args[i].startsWith("-")) {
+                               if (currentOption != null) {
+                                       assert currentOptionValues != null;
+                                       if (!options.containsKey(currentOption))
+                                               options.put(currentOption, new ArrayList<>());
+                                       options.get(currentOption).addAll(currentOptionValues);
+                               }
+                               currentOption = args[i];
+                               currentOptionValues = new ArrayList<>();
+                       } else {
+                               currentOptionValues.add(args[i]);
+                       }
+               }
+               Make argeoBuild = new Make();
+               switch (action) {
+               case "bundle" -> argeoBuild.bundle(options);
+               default -> throw new IllegalArgumentException("Unkown action: " + action);
+               }
+
+       }
+}
diff --git a/osgi.mk b/osgi.mk
index c647d345abf4758b8c7936911bfdc0bb357e06db..303f5eef4a95acbfba5ca0f2d7231dd06bdfe2ea 100644 (file)
--- a/osgi.mk
+++ b/osgi.mk
@@ -53,11 +53,11 @@ $(BUILD_BASE)/jars-built: $(BNDS)
 
 $(BUILD_BASE)/%/bnd.bnd : %/bnd.bnd $(BUILD_BASE)/java-compiled 
        mkdir -p $(dir $@)bin
-       rsync -r --exclude "*.java" $(dir  $<)src/ $(dir $@)bin
-       rsync -r --exclude-from $(SDK_SRC_BASE)/sdk/argeo-build/excludes.txt $(dir  $<) $(dir $@)bin
-       if [ -d "$(dir  $<)OSGI-INF" ]; then rsync -r $(dir  $<)OSGI-INF/ $(dir $@)/OSGI-INF; fi
+       #rsync -r --exclude "*.java" $(dir  $<)src/ $(dir $@)bin
+       #rsync -r --exclude-from $(SDK_SRC_BASE)/sdk/argeo-build/excludes.txt $(dir  $<) $(dir $@)bin
+       #if [ -d "$(dir  $<)OSGI-INF" ]; then rsync -r $(dir  $<)OSGI-INF/ $(dir $@)/OSGI-INF; fi
        cp $< $@
-       echo "\n-sourcepath:$(SDK_SRC_BASE)/$(dir  $<)src\n" >> $@
+       #echo "\n-sourcepath:$(SDK_SRC_BASE)/$(dir  $<)src\n" >> $@
 
 $(BUILD_BASE)/java-compiled : $(JAVA_SRCS)
        $(JVM) -jar $(ECJ_JAR) @$(SDK_SRC_BASE)/sdk/argeo-build/ecj.args -cp $(A2_CLASSPATH) $(ECJ_SRCS)