From: Mathieu Baudier Date: Sat, 8 Oct 2022 06:59:17 +0000 (+0200) Subject: Introduce Java builder X-Git-Tag: v2.3.1~23 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=0579f7f2fbd8d10e0b7e86b92289ab4bc0fab604;p=cc0%2Fargeo-build.git Introduce Java builder --- diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..d706197 --- /dev/null +++ b/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/.gitignore b/.gitignore index 3a4edf6..fbfcfb6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .project +*.class +/bin/ diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000..3e02ca1 --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -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 index 0000000..31bbddc --- /dev/null +++ b/build.properties @@ -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 index 0000000..753f729 --- /dev/null +++ b/java/org/argeo/build/Make.java @@ -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 context = new HashMap<>(); + List 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> 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 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() { + + @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() { + + @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() { + + @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> options = new HashMap<>(); + String currentOption = null; + List 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 c647d34..303f5ee 100644 --- 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)