X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;ds=sidebyside;f=src%2Forg%2Fargeo%2Fbuild%2FMake.java;h=78310597c15a4982f03dccbaa2b2ecf35965adb2;hb=1d38b1c8155f35271cf20217167eb6901df25272;hp=4fa8808697f54034307dea740ea25f0fee4d6a25;hpb=6953e7b0e4edc156941a94027b1eb9bc4e2a1e95;p=cc0%2Fargeo-build.git diff --git a/src/org/argeo/build/Make.java b/src/org/argeo/build/Make.java index 4fa8808..7831059 100644 --- a/src/org/argeo/build/Make.java +++ b/src/org/argeo/build/Make.java @@ -1,7 +1,9 @@ package org.argeo.build; +import static java.lang.System.Logger.Level.DEBUG; import static java.lang.System.Logger.Level.ERROR; import static java.lang.System.Logger.Level.INFO; +import static java.lang.System.Logger.Level.WARNING; import java.io.File; import java.io.IOException; @@ -21,15 +23,20 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.StringJoiner; import java.util.StringTokenizer; +import java.util.TreeMap; +import java.util.concurrent.CompletableFuture; +import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; +import java.util.zip.Deflater; import org.eclipse.jdt.core.compiler.CompilationProgress; @@ -48,8 +55,23 @@ import aQute.bnd.osgi.Jar; public class Make { private final static Logger logger = System.getLogger(Make.class.getName()); + /** + * Environment variable on whether sources should be packaged separately or + * integrated in the bundles. + */ + private final static String ENV_SOURCE_BUNDLES = "SOURCE_BUNDLES"; + + /** + * Environment variable to override the default location for the Argeo Build + * configuration files. Typically used if Argeo Build has been compiled and + * packaged separately. + */ + private final static String ENV_ARGEO_BUILD_CONFIG = "ARGEO_BUILD_CONFIG"; + /** Name of the local-specific Makefile (sdk.mk). */ final static String SDK_MK = "sdk.mk"; + /** Name of the branch definition Makefile (branch.mk). */ + final static String BRANCH_MK = "branch.mk"; /** The execution directory (${user.dir}). */ final Path execDirectory; @@ -66,36 +88,46 @@ public class Make { final Path buildBase; /** The base of the a2 output for all layers. */ final Path a2Output; + /** The base of the a2 sources when packages separately. */ + final Path a2srcOutput; + + /** Whether sources should be packaged separately */ + final boolean sourceBundles; /** Constructor initialises the base directories. */ public Make() throws IOException { + sourceBundles = Boolean.parseBoolean(System.getenv(ENV_SOURCE_BUNDLES)); + if (sourceBundles) + logger.log(Level.INFO, "Sources will be packaged separately"); + 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); - } - + Map context = readeMakefileVariables(sdkMkP); sdkSrcBase = Paths.get(context.computeIfAbsent("SDK_SRC_BASE", (key) -> { throw new IllegalStateException(key + " not found"); })).toAbsolutePath(); - argeoBuildBase = sdkSrcBase.resolve("sdk/argeo-build"); + + Path argeoBuildBaseT = sdkSrcBase.resolve("sdk/argeo-build"); + if (!Files.exists(argeoBuildBaseT)) { + String fromEnv = System.getenv(ENV_ARGEO_BUILD_CONFIG); + if (fromEnv != null) + argeoBuildBaseT = Paths.get(fromEnv); + if (fromEnv == null || !Files.exists(argeoBuildBaseT)) { + throw new IllegalStateException( + "Argeo Build not found. Did you initialise the git submodules or set the " + + ENV_ARGEO_BUILD_CONFIG + " environment variable?"); + } + } + argeoBuildBase = argeoBuildBaseT; sdkBuildBase = Paths.get(context.computeIfAbsent("SDK_BUILD_BASE", (key) -> { throw new IllegalStateException(key + " not found"); })).toAbsolutePath(); buildBase = sdkBuildBase.resolve(sdkSrcBase.getFileName()); a2Output = sdkBuildBase.resolve("a2"); + a2srcOutput = sdkBuildBase.resolve("a2.src"); } /* @@ -117,7 +149,7 @@ public class Make { List a2Categories = options.getOrDefault("--dep-categories", new ArrayList<>()); List a2Bases = options.getOrDefault("--a2-bases", new ArrayList<>()); - if (a2Bases.isEmpty()) { + if (a2Bases.isEmpty() || !a2Bases.contains(a2Output.toString())) { a2Bases.add(a2Output.toString()); } @@ -128,26 +160,58 @@ public class Make { // classpath if (!a2Categories.isEmpty()) { - compilerArgs.add("-cp"); - StringJoiner classPath = new StringJoiner(File.pathSeparator); + // We will keep only the highest major.minor + // and order by bundle name, for predictability + Map a2Jars = new TreeMap<>(); + +// StringJoiner modulePath = new StringJoiner(File.pathSeparator); for (String a2Base : a2Bases) { - for (String a2Category : a2Categories) { + categories: for (String a2Category : a2Categories) { Path a2Dir = Paths.get(a2Base).resolve(a2Category); if (!Files.exists(a2Dir)) - Files.createDirectories(a2Dir); + continue categories; +// modulePath.add(a2Dir.toString()); for (Path jarP : Files.newDirectoryStream(a2Dir, (p) -> p.getFileName().toString().endsWith(".jar"))) { - classPath.add(jarP.toString()); + A2Jar a2Jar = new A2Jar(jarP); + if (a2Jars.containsKey(a2Jar.name)) { + A2Jar current = a2Jars.get(a2Jar.name); + if (a2Jar.major > current.major) + a2Jars.put(a2Jar.name, a2Jar); + else if (a2Jar.major == current.major // + // if minor equals, we take the last one + && a2Jar.minor >= current.minor) + a2Jars.put(a2Jar.name, a2Jar); + } else { + a2Jars.put(a2Jar.name, a2Jar); + } } } } + + StringJoiner classPath = new StringJoiner(File.pathSeparator); + for (Iterator it = a2Jars.values().iterator(); it.hasNext();) + classPath.add(it.next().path.toString()); + + compilerArgs.add("-cp"); compilerArgs.add(classPath.toString()); +// compilerArgs.add("--module-path"); +// compilerArgs.add(modulePath.toString()); } // sources for (String bundle : bundles) { StringBuilder sb = new StringBuilder(); - sb.append(execDirectory.resolve(bundle).resolve("src")); + Path bundlePath = execDirectory.resolve(bundle); + if (!Files.exists(bundlePath)) { + if (bundles.size() == 1) { + logger.log(WARNING, "Bundle " + bundle + " not found in " + execDirectory + + ", assuming this is this directory, as only one bundle was requested."); + bundlePath = execDirectory; + } else + throw new IllegalArgumentException("Bundle " + bundle + " not found in " + execDirectory); + } + sb.append(bundlePath.resolve("src")); sb.append("[-d"); compilerArgs.add(sb.toString()); sb = new StringBuilder(); @@ -156,6 +220,15 @@ public class Make { compilerArgs.add(sb.toString()); } + if (logger.isLoggable(INFO)) + compilerArgs.add("-time"); + + if (logger.isLoggable(DEBUG)) { + logger.log(DEBUG, "Compiler arguments:"); + for (String arg : compilerArgs) + logger.log(DEBUG, arg); + } + boolean success = org.eclipse.jdt.core.compiler.batch.BatchCompiler.compile( compilerArgs.toArray(new String[compilerArgs.size()]), new PrintWriter(System.out), new PrintWriter(System.err), new MakeCompilationProgress()); @@ -172,22 +245,48 @@ public class Make { return; List categories = options.get("--category"); - Objects.requireNonNull(bundles, "--bundles argument must be set"); + Objects.requireNonNull(bundles, "--category argument must be set"); if (categories.size() != 1) - throw new IllegalArgumentException("One and only one category must be specified"); + throw new IllegalArgumentException("One and only one --category must be specified"); String category = categories.get(0); - // create jars - for (String bundle : bundles) - createBundle(bundle, category); + final String branch; + Path branchMk = sdkSrcBase.resolve(BRANCH_MK); + if (Files.exists(branchMk)) { + Map branchVariables = readeMakefileVariables(branchMk); + branch = branchVariables.get("BRANCH"); + } else { + branch = null; + } + + long begin = System.currentTimeMillis(); + // create jars in parallel + List> toDos = new ArrayList<>(); + for (String bundle : bundles) { + toDos.add(CompletableFuture.runAsync(() -> { + try { + createBundle(branch, bundle, category); + } catch (IOException e) { + throw new RuntimeException("Packaging of " + bundle + " failed", e); + } + })); + } + CompletableFuture.allOf(toDos.toArray(new CompletableFuture[toDos.size()])).join(); + long duration = System.currentTimeMillis() - begin; + logger.log(INFO, "Packaging took " + duration + " ms"); } - /* - * UTILITIES - */ /** Package a single bundle. */ - void createBundle(String bundle, String category) throws IOException { - Path source = execDirectory.resolve(bundle); + void createBundle(String branch, String bundle, String category) throws IOException { + final Path source; + if (!Files.exists(execDirectory.resolve(bundle))) { + logger.log(WARNING, + "Bundle " + bundle + " not found in " + execDirectory + ", assuming this is this directory."); + source = execDirectory; + } else { + source = execDirectory.resolve(bundle); + } + Path compiled = buildBase.resolve(bundle); String bundleSymbolicName = source.getFileName().toString(); @@ -197,19 +296,24 @@ public class Make { 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); + + if (branch != null) { + Path branchBnd = sdkSrcBase.resolve("sdk/branches/" + branch + ".bnd"); + if (Files.exists(branchBnd)) + try (InputStream in = Files.newInputStream(branchBnd)) { + properties.load(in); + } } Path bndBnd = source.resolve("bnd.bnd"); - try (InputStream in = Files.newInputStream(bndBnd)) { - properties.load(in); - } + if (Files.exists(bndBnd)) + try (InputStream in = Files.newInputStream(bndBnd)) { + properties.load(in); + } // Normalise - properties.put("Bundle-SymbolicName", bundleSymbolicName); + if (!properties.containsKey("Bundle-SymbolicName")) + properties.put("Bundle-SymbolicName", bundleSymbolicName); // Calculate MANIFEST Path binP = compiled.resolve("bin"); @@ -225,10 +329,10 @@ public class Make { throw new RuntimeException("Bnd analysis of " + compiled + " failed", e); } - String major = properties.getProperty("MAJOR"); - Objects.requireNonNull(major, "MAJOR must be set"); - String minor = properties.getProperty("MINOR"); - Objects.requireNonNull(minor, "MINOR must be set"); + String major = properties.getProperty("major"); + Objects.requireNonNull(major, "'major' must be set"); + String minor = properties.getProperty("minor"); + Objects.requireNonNull(minor, "'minor' must be set"); // Write manifest Path manifestP = compiled.resolve("META-INF/MANIFEST.MF"); @@ -252,6 +356,7 @@ public class Make { Files.createDirectories(jarP.getParent()); try (JarOutputStream jarOut = new JarOutputStream(Files.newOutputStream(jarP), manifest)) { + jarOut.setLevel(Deflater.DEFAULT_COMPRESSION); // add all classes first Files.walkFileTree(binP, new SimpleFileVisitor() { @Override @@ -287,26 +392,68 @@ public class Make { } }); - // add sources - // TODO add effective BND, Eclipse project file, etc., in order to be able to - // repackage Path srcP = source.resolve("src"); + // Add all resources from 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); + if (file.getFileName().toString().endsWith(".java") + || file.getFileName().toString().endsWith(".class")) + return FileVisitResult.CONTINUE; + jarOut.putNextEntry(new JarEntry(srcP.relativize(file).toString())); + if (!Files.isDirectory(file)) + Files.copy(file, jarOut); return FileVisitResult.CONTINUE; } }); + + // add sources + // TODO add effective BND, Eclipse project file, etc., in order to be able to + // repackage + if (sourceBundles) { + Path a2srcJarDirectory = bundleParent != null ? a2srcOutput.resolve(bundleParent).resolve(category) + : a2srcOutput.resolve(category); + Files.createDirectories(a2srcJarDirectory); + Path srcJarP = a2srcJarDirectory + .resolve(compiled.getFileName() + "." + major + "." + minor + ".src.jar"); + Manifest srcManifest = new Manifest(); + srcManifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + srcManifest.getMainAttributes().putValue("Bundle-SymbolicName", bundleSymbolicName + ".src"); + srcManifest.getMainAttributes().putValue("Bundle-Version", + manifest.getMainAttributes().getValue("Bundle-Version").toString()); + srcManifest.getMainAttributes().putValue("Eclipse-SourceBundle", + bundleSymbolicName + ";version=\"" + manifest.getMainAttributes().getValue("Bundle-Version")); + + try (JarOutputStream srcJarOut = new JarOutputStream(Files.newOutputStream(srcJarP), srcManifest)) { + copySourcesToJar(srcP, srcJarOut, ""); + } + } else { + copySourcesToJar(srcP, jarOut, "OSGI-OPT/src/"); + } } } + /* + * UTILITIES + */ + /** Add sources to a jar file */ + void copySourcesToJar(Path srcP, JarOutputStream srcJarOut, String prefix) throws IOException { + Files.walkFileTree(srcP, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + srcJarOut.putNextEntry(new JarEntry(prefix + srcP.relativize(file).toString())); + if (!Files.isDirectory(file)) + Files.copy(file, srcJarOut); + return FileVisitResult.CONTINUE; + } + }); + } + /** * Recursively find the base source directory (which contains the * {@value #SDK_MK} file). */ - private Path findSdkMk(Path directory) { + Path findSdkMk(Path directory) { Path sdkMkP = directory.resolve(SDK_MK); if (Files.exists(sdkMkP)) { return sdkMkP.toAbsolutePath(); @@ -316,6 +463,28 @@ public class Make { return findSdkMk(directory.getParent()); } + /** + * Reads Makefile variable assignments of the form =, :=, or ?=, ignoring white + * spaces. To be used with very simple included Makefiles only. + */ + Map readeMakefileVariables(Path path) throws IOException { + Map context = new HashMap<>(); + List sdkMkLines = Files.readAllLines(path); + 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(); + if (st.hasMoreTokens()) // probably not a simple variable assignment + continue lines; + context.put(key, value); + } + return context; + } + /** Main entry point, interpreting actions and arguments. */ public static void main(String... args) { if (args.length == 0) @@ -350,17 +519,33 @@ public class Make { } long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime(); - logger.log(INFO, "Make action '" + action + "' succesfully completed after " + (jvmUptime / 1000) + "." + logger.log(INFO, "Make.java action '" + action + "' succesfully completed after " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + " s"); } catch (Exception e) { long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime(); - logger.log(ERROR, - "Make action '" + action + "' failed after " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + " s", - e); + logger.log(ERROR, "Make.java action '" + action + "' failed after " + (jvmUptime / 1000) + "." + + (jvmUptime % 1000) + " s", e); System.exit(1); } } + static class A2Jar { + final Path path; + final String name; + final int major; + final int minor; + + A2Jar(Path path) { + this.path = path; + String fileName = path.getFileName().toString(); + fileName = fileName.substring(0, fileName.lastIndexOf('.')); + minor = Integer.parseInt(fileName.substring(fileName.lastIndexOf('.') + 1)); + fileName = fileName.substring(0, fileName.lastIndexOf('.')); + major = Integer.parseInt(fileName.substring(fileName.lastIndexOf('.') + 1)); + name = fileName.substring(0, fileName.lastIndexOf('.')); + } + } + /** * An ECJ {@link CompilationProgress} printing a progress bar while compiling. */