Properly deploy OS-specific jars
[cc0/argeo-build.git] / src / org / argeo / build / Make.java
index 8c9dadd64105823acc96f2c0bb8be61439c0134e..71976c00188d70cdff9125efb6912865fcf610f2 100644 (file)
@@ -3,7 +3,6 @@ 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.TRACE;
 import static java.lang.System.Logger.Level.WARNING;
 
 import java.io.File;
@@ -310,29 +309,24 @@ public class Make {
                }
                CompletableFuture.allOf(toDos.toArray(new CompletableFuture[toDos.size()])).join();
                long duration = System.currentTimeMillis() - begin;
-               logger.log(INFO, "Packaging took " + duration + " ms");
+               logger.log(DEBUG, "Packaging took " + duration + " ms");
        }
 
-       /** Install the bundles. */
+       /** Install or uninstall bundles and native output. */
        void install(Map<String, List<String>> options, boolean uninstall) throws IOException {
+               final String LIB_ = "lib/";
+               final String NATIVE_ = "native/";
+
                // check arguments
-               List<String> bundles = options.get("--bundles");
-               Objects.requireNonNull(bundles, "--bundles argument must be set");
+               List<String> bundles = multiArg(options, "--bundles", true);
                if (bundles.isEmpty())
                        return;
-
-               List<String> categories = options.get("--category");
-               Objects.requireNonNull(categories, "--category argument must be set");
-               if (categories.size() != 1)
-                       throw new IllegalArgumentException("One and only one --category must be specified");
-               String category = categories.get(0);
-
-               List<String> targetDirs = options.get("--target");
-               Objects.requireNonNull(targetDirs, "--target argument must be set");
-               if (targetDirs.size() != 1)
-                       throw new IllegalArgumentException("One and only one --target must be specified");
-               Path targetA2 = Paths.get(targetDirs.get(0));
-               logger.log(INFO, (uninstall ? "Uninstalling from " : "Installing to ") + targetA2);
+               String category = singleArg(options, "--category", true);
+               Path targetA2 = Paths.get(singleArg(options, "--target", true));
+               String nativeTargetArg = singleArg(options, "--target-native", false);
+               Path nativeTargetA2 = nativeTargetArg != null ? Paths.get(nativeTargetArg) : null;
+               String targetOs = singleArg(options, "--os", nativeTargetArg != null);
+               logger.log(INFO, (uninstall ? "Uninstalling bundles from " : "Installing bundles to ") + targetA2);
 
                final String branch;
                Path branchMk = sdkSrcBase.resolve(BRANCH_MK);
@@ -355,14 +349,28 @@ public class Make {
                Objects.requireNonNull(minor, "'minor' must be set");
 
                int count = 0;
-               for (String bundle : bundles) {
+               bundles: for (String bundle : bundles) {
                        Path bundlePath = Paths.get(bundle);
                        Path bundleParent = bundlePath.getParent();
                        Path a2JarDirectory = bundleParent != null ? a2Output.resolve(bundleParent).resolve(category)
                                        : a2Output.resolve(category);
                        Path jarP = a2JarDirectory.resolve(bundlePath.getFileName() + "." + major + "." + minor + ".jar");
 
-                       Path targetJarP = targetA2.resolve(a2Output.relativize(jarP));
+                       Path targetJarP;
+                       if (bundle.startsWith(LIB_)) {// OS-specific
+                               Objects.requireNonNull(nativeTargetA2);
+                               if (bundle.startsWith(LIB_ + NATIVE_) // portable native
+                                               || bundle.startsWith(LIB_ + targetOs + "/" + NATIVE_)) {// OS-specific native
+                                       targetJarP = nativeTargetA2.resolve(category).resolve(jarP.getFileName());
+                               } else if (bundle.startsWith(LIB_ + targetOs)) {// OS-specific portable
+                                       targetJarP = targetA2.resolve(category).resolve(jarP.getFileName());
+                               } else { // ignore other OS
+                                       continue bundles;
+                               }
+                       } else {
+                               targetJarP = targetA2.resolve(a2Output.relativize(jarP));
+                       }
+
                        if (uninstall) { // uninstall
                                if (Files.exists(targetJarP)) {
                                        Files.delete(targetJarP);
@@ -382,6 +390,27 @@ public class Make {
                logger.log(INFO, uninstall ? count + " bundles removed" : count + " bundles installed or updated");
        }
 
+       /** Extracts an argument which must be unique. */
+       String singleArg(Map<String, List<String>> options, String arg, boolean mandatory) {
+               List<String> values = options.get(arg);
+               if (values == null || values.size() == 0)
+                       if (mandatory)
+                               throw new IllegalArgumentException(arg + " argument must be set");
+                       else
+                               return null;
+               if (values.size() != 1)
+                       throw new IllegalArgumentException("One and only one " + arg + " arguments must be specified");
+               return values.get(0);
+       }
+
+       /** Extracts an argument which can have multiple values. */
+       List<String> multiArg(Map<String, List<String>> options, String arg, boolean mandatory) {
+               List<String> values = options.get(arg);
+               if (mandatory && values == null)
+                       throw new IllegalArgumentException(arg + " argument must be set");
+               return values != null ? values : new ArrayList<>();
+       }
+
        /** Delete empty parent directory up to the A2 target (included). */
        void deleteEmptyParents(Path targetA2, Path targetParent) throws IOException {
                if (!Files.isDirectory(targetParent))
@@ -397,18 +426,18 @@ public class Make {
 
        /** Package a single bundle. */
        void createBundle(String branch, String bundle, String category) throws IOException {
-               final Path source;
+               final Path bundleSourceBase;
                if (!Files.exists(execDirectory.resolve(bundle))) {
                        logger.log(WARNING,
                                        "Bundle " + bundle + " not found in " + execDirectory + ", assuming this is this directory.");
-                       source = execDirectory;
+                       bundleSourceBase = execDirectory;
                } else {
-                       source = execDirectory.resolve(bundle);
+                       bundleSourceBase = execDirectory.resolve(bundle);
                }
-               Path srcP = source.resolve("src");
+               Path srcP = bundleSourceBase.resolve("src");
 
                Path compiled = buildBase.resolve(bundle);
-               String bundleSymbolicName = source.getFileName().toString();
+               String bundleSymbolicName = bundleSourceBase.getFileName().toString();
 
                // Metadata
                Properties properties = new Properties();
@@ -425,7 +454,7 @@ public class Make {
                                }
                }
 
-               Path bndBnd = source.resolve("bnd.bnd");
+               Path bndBnd = bundleSourceBase.resolve("bnd.bnd");
                if (Files.exists(bndBnd))
                        try (InputStream in = Files.newInputStream(bndBnd)) {
                                properties.load(in);
@@ -488,7 +517,7 @@ public class Make {
                        });
 
                        // add resources
-                       Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
+                       Files.walkFileTree(bundleSourceBase, new SimpleFileVisitor<Path>() {
                                @Override
                                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                                        // skip output directory if it happens to be within the sources
@@ -496,7 +525,7 @@ public class Make {
                                                return FileVisitResult.SKIP_SUBTREE;
 
                                        // skip excluded patterns
-                                       Path relativeP = source.relativize(dir);
+                                       Path relativeP = bundleSourceBase.relativize(dir);
                                        for (PathMatcher exclude : excludes)
                                                if (exclude.matches(relativeP))
                                                        return FileVisitResult.SKIP_SUBTREE;
@@ -506,12 +535,12 @@ public class Make {
 
                                @Override
                                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                                       Path relativeP = source.relativize(file);
+                                       Path relativeP = bundleSourceBase.relativize(file);
                                        for (PathMatcher exclude : excludes)
                                                if (exclude.matches(relativeP))
                                                        return FileVisitResult.CONTINUE;
                                        // skip JavaScript source maps
-                                       if (relativeP.getFileName().toString().endsWith(".map"))
+                                       if (sourceBundles && file.getFileName().toString().endsWith(".map"))
                                                return FileVisitResult.CONTINUE;
 
                                        JarEntry entry = new JarEntry(relativeP.toString());
@@ -554,7 +583,7 @@ public class Make {
                        }
 
                        // add legal notices and licenses
-                       for (Path p : listLegalFilesToInclude(source).values()) {
+                       for (Path p : listLegalFilesToInclude(bundleSourceBase).values()) {
                                jarOut.putNextEntry(new JarEntry(p.getFileName().toString()));
                                Files.copy(p, jarOut);
                        }
@@ -565,22 +594,49 @@ public class Make {
                                        : 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());
+                       createSourceBundle(bundleSymbolicName, manifest, bundleSourceBase, srcP, srcJarP);
+               }
+       }
+
+       /** Create a separate bundle containing the sources. */
+       void createSourceBundle(String bundleSymbolicName, Manifest manifest, Path bundleSourceBase, Path srcP,
+                       Path srcJarP) throws IOException {
+               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());
+
+               boolean isJsBundle = bundleSymbolicName.endsWith(".js");
+               if (!isJsBundle) {
                        srcManifest.getMainAttributes().putValue("Eclipse-SourceBundle",
                                        bundleSymbolicName + ";version=\"" + manifest.getMainAttributes().getValue("Bundle-Version"));
 
                        try (JarOutputStream srcJarOut = new JarOutputStream(Files.newOutputStream(srcJarP), srcManifest)) {
                                copySourcesToJar(srcP, srcJarOut, "");
                                // add legal notices and licenses
-                               for (Path p : listLegalFilesToInclude(source).values()) {
+                               for (Path p : listLegalFilesToInclude(bundleSourceBase).values()) {
                                        srcJarOut.putNextEntry(new JarEntry(p.getFileName().toString()));
                                        Files.copy(p, srcJarOut);
                                }
                        }
+               } else {// JavaScript source maps
+                       srcManifest.getMainAttributes().putValue("Fragment-Host", bundleSymbolicName + ";bundle-version=\""
+                                       + manifest.getMainAttributes().getValue("Bundle-Version"));
+                       try (JarOutputStream srcJarOut = new JarOutputStream(Files.newOutputStream(srcJarP), srcManifest)) {
+                               Files.walkFileTree(bundleSourceBase, new SimpleFileVisitor<Path>() {
+                                       @Override
+                                       public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                                               Path relativeP = bundleSourceBase.relativize(file);
+                                               if (!file.getFileName().toString().endsWith(".map"))
+                                                       return FileVisitResult.CONTINUE;
+                                               JarEntry entry = new JarEntry(relativeP.toString());
+                                               srcJarOut.putNextEntry(entry);
+                                               Files.copy(file, srcJarOut);
+                                               return FileVisitResult.CONTINUE;
+                                       }
+                               });
+                       }
                }
        }
 
@@ -703,8 +759,8 @@ public class Make {
                        }
 
                        long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
-                       logger.log(INFO, "Make.java action '" + action + "' succesfully completed after " + (jvmUptime / 1000) + "."
-                                       + (jvmUptime % 1000) + " s");
+                       logger.log(INFO, "Make.java action '" + action + "' successfully completed after " + (jvmUptime / 1000)
+                                       + "." + (jvmUptime % 1000) + " s");
                } catch (Exception e) {
                        long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
                        logger.log(ERROR, "Make.java action '" + action + "' failed after " + (jvmUptime / 1000) + "."