Skip *.js source directories
[cc0/argeo-build.git] / src / org / argeo / build / Make.java
index dde90a490bf43f04c371a3ac18c074b124947b9b..8e872dcb61af818ea71946b4369197eaf6e7ddc4 100644 (file)
@@ -20,6 +20,7 @@ import java.nio.file.Path;
 import java.nio.file.PathMatcher;
 import java.nio.file.Paths;
 import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -77,6 +78,18 @@ public class Make {
         */
        private final static String ENV_ARGEO_BUILD_CONFIG = "ARGEO_BUILD_CONFIG";
 
+       /** Make file variable (in {@link #SDK_MK}) with a path to the sources base. */
+       private final static String VAR_SDK_SRC_BASE = "SDK_SRC_BASE";
+
+       /**
+        * Make file variable (in {@link #SDK_MK}) with a path to the build output base.
+        */
+       private final static String VAR_SDK_BUILD_BASE = "SDK_BUILD_BASE";
+       /**
+        * Make file variable (in {@link #BRANCH_MK}) with the branch.
+        */
+       private final static String VAR_BRANCH = "BRANCH";
+
        /** Name of the local-specific Makefile (sdk.mk). */
        final static String SDK_MK = "sdk.mk";
        /** Name of the branch definition Makefile (branch.mk). */
@@ -97,7 +110,7 @@ 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. */
+       /** The base of the a2 sources when packaged separately. */
        final Path a2srcOutput;
 
        /** Whether sources should be packaged separately. */
@@ -118,8 +131,8 @@ public class Make {
                Path sdkMkP = findSdkMk(execDirectory);
                Objects.requireNonNull(sdkMkP, "No " + SDK_MK + " found under " + execDirectory);
 
-               Map<String, String> context = readeMakefileVariables(sdkMkP);
-               sdkSrcBase = Paths.get(context.computeIfAbsent("SDK_SRC_BASE", (key) -> {
+               Map<String, String> context = readMakefileVariables(sdkMkP);
+               sdkSrcBase = Paths.get(context.computeIfAbsent(VAR_SDK_SRC_BASE, (key) -> {
                        throw new IllegalStateException(key + " not found");
                })).toAbsolutePath();
 
@@ -136,7 +149,7 @@ public class Make {
                }
                argeoBuildBase = argeoBuildBaseT;
 
-               sdkBuildBase = Paths.get(context.computeIfAbsent("SDK_BUILD_BASE", (key) -> {
+               sdkBuildBase = Paths.get(context.computeIfAbsent(VAR_SDK_BUILD_BASE, (key) -> {
                        throw new IllegalStateException(key + " not found");
                })).toAbsolutePath();
                buildBase = sdkBuildBase.resolve(sdkSrcBase.getFileName());
@@ -258,7 +271,7 @@ public class Make {
                        return;
 
                List<String> categories = options.get("--category");
-               Objects.requireNonNull(bundles, "--category argument must be set");
+               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);
@@ -266,8 +279,8 @@ public class Make {
                final String branch;
                Path branchMk = sdkSrcBase.resolve(BRANCH_MK);
                if (Files.exists(branchMk)) {
-                       Map<String, String> branchVariables = readeMakefileVariables(branchMk);
-                       branch = branchVariables.get("BRANCH");
+                       Map<String, String> branchVariables = readMakefileVariables(branchMk);
+                       branch = branchVariables.get(VAR_BRANCH);
                } else {
                        branch = null;
                }
@@ -289,6 +302,88 @@ public class Make {
                logger.log(INFO, "Packaging took " + duration + " ms");
        }
 
+       /** Install the bundles. */
+       void install(Map<String, List<String>> options, boolean uninstall) throws IOException {
+               // check arguments
+               List<String> bundles = options.get("--bundles");
+               Objects.requireNonNull(bundles, "--bundles argument must be set");
+               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);
+
+               final String branch;
+               Path branchMk = sdkSrcBase.resolve(BRANCH_MK);
+               if (Files.exists(branchMk)) {
+                       Map<String, String> branchVariables = readMakefileVariables(branchMk);
+                       branch = branchVariables.get(VAR_BRANCH);
+               } else {
+                       throw new IllegalArgumentException(VAR_BRANCH + " variable must be set.");
+               }
+
+               Properties properties = new Properties();
+               Path branchBnd = sdkSrcBase.resolve("sdk/branches/" + branch + ".bnd");
+               if (Files.exists(branchBnd))
+                       try (InputStream in = Files.newInputStream(branchBnd)) {
+                               properties.load(in);
+                       }
+               String major = properties.getProperty("major");
+               Objects.requireNonNull(major, "'major' must be set");
+               String minor = properties.getProperty("minor");
+               Objects.requireNonNull(minor, "'minor' must be set");
+
+               int count = 0;
+               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));
+                       if (uninstall) { // uninstall
+                               if (Files.exists(targetJarP)) {
+                                       Files.delete(targetJarP);
+                                       logger.log(DEBUG, "Removed " + targetJarP);
+                                       count++;
+                               }
+                               Path targetParent = targetJarP.getParent();
+                               deleteEmptyParents(targetA2, targetParent);
+                       } else { // install
+                               Files.createDirectories(targetJarP.getParent());
+                               boolean update = Files.exists(targetJarP);
+                               Files.copy(jarP, targetJarP, StandardCopyOption.REPLACE_EXISTING);
+                               logger.log(DEBUG, (update ? "Updated " : "Installed ") + targetJarP);
+                               count++;
+                       }
+               }
+               logger.log(INFO, uninstall ? count + " bundles removed" : count + " bundles installed or updated");
+       }
+
+       /** Delete empty parent directory up to the A2 target (included). */
+       void deleteEmptyParents(Path targetA2, Path targetParent) throws IOException {
+               if (!Files.isDirectory(targetParent))
+                       throw new IllegalArgumentException(targetParent + " must be a directory");
+               boolean isA2target = Files.isSameFile(targetA2, targetParent);
+               if (!Files.list(targetParent).iterator().hasNext()) {
+                       Files.delete(targetParent);
+                       if (isA2target)
+                               return;// stop after deleting A2 base
+                       deleteEmptyParents(targetA2, targetParent.getParent());
+               }
+       }
+
        /** Package a single bundle. */
        void createBundle(String branch, String bundle, String category) throws IOException {
                final Path source;
@@ -385,6 +480,11 @@ public class Make {
                        Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
                                @Override
                                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+                                       // skip output directory if it happens to be within the sources
+                                       if (Files.isSameFile(sdkBuildBase, dir))
+                                               return FileVisitResult.SKIP_SUBTREE;
+
+                                       // skip excluded patterns
                                        Path relativeP = source.relativize(dir);
                                        for (PathMatcher exclude : excludes)
                                                if (exclude.matches(relativeP))
@@ -418,6 +518,16 @@ public class Make {
                                                Files.copy(file, jarOut);
                                        return FileVisitResult.CONTINUE;
                                }
+
+                               @Override
+                               public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+                                       // skip directories ending with .js
+                                       // TODO find something more robust?
+                                       if (dir.getFileName().endsWith(".js"))
+                                               return FileVisitResult.SKIP_SUBTREE;
+                                       return super.preVisitDirectory(dir, attrs);
+                               }
+
                        });
 
                        // add legal notices and licenses
@@ -523,7 +633,7 @@ public class Make {
         * Reads Makefile variable assignments of the form =, :=, or ?=, ignoring white
         * spaces. To be used with very simple included Makefiles only.
         */
-       Map<String, String> readeMakefileVariables(Path path) throws IOException {
+       Map<String, String> readMakefileVariables(Path path) throws IOException {
                Map<String, String> context = new HashMap<>();
                List<String> sdkMkLines = Files.readAllLines(path);
                lines: for (String line : sdkMkLines) {
@@ -570,6 +680,8 @@ public class Make {
                        case "compile" -> argeoMake.compile(options);
                        case "bundle" -> argeoMake.bundle(options);
                        case "all" -> argeoMake.all(options);
+                       case "install" -> argeoMake.install(options, false);
+                       case "uninstall" -> argeoMake.install(options, true);
 
                        default -> throw new IllegalArgumentException("Unkown action: " + action);
                        }