All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.reactiverse.es4x.commands.Install Maven / Gradle / Ivy

package io.reactiverse.es4x.commands;

import io.reactiverse.es4x.cli.CmdLineParser;
import io.reactiverse.es4x.commands.proxies.JsonArrayProxy;
import io.reactiverse.es4x.commands.proxies.JsonObjectProxy;
import org.eclipse.aether.artifact.Artifact;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.jar.*;

import static io.reactiverse.es4x.cli.Helper.*;

public class Install implements Runnable {

  public static final String NAME = "install";
  public static final String SUMMARY = "Installs required jars from maven to 'node_modules'.";

  private static final Properties VERSIONS = new Properties();

  static {
    try (InputStream is = Install.class.getClassLoader().getResourceAsStream("META-INF/es4x-commands/VERSIONS.properties")) {
      if (is == null) {
        fatal("Cannot find 'META-INF/es4x-commands/VERSIONS.properties' on classpath");
      } else {
        VERSIONS.load(is);
      }
    } catch (IOException e) {
      fatal(e.getMessage());
    }
  }

  private boolean force;
  private boolean link;
  private List vendor;
  private File coreJar;

  private File cwd;

  public Install() {
  }

  public Install(String[] args) {
    CmdLineParser parser = new CmdLineParser();
    CmdLineParser.Option helpOption = parser.addBooleanOption('h', "help");
    CmdLineParser.Option forceOption = parser.addBooleanOption('f', "force");
    CmdLineParser.Option vendorOption = parser.addStringOption('v', "vendor");
    CmdLineParser.Option linkOption = parser.addBooleanOption('l', "link");

    try {
      parser.parse(args);
    } catch (CmdLineParser.OptionException e) {
      printUsage();
      System.exit(2);
      return;
    }

    Boolean isHelp = parser.getOptionValue(helpOption, Boolean.FALSE);

    if (isHelp != null && isHelp) {
      printUsage();
      System.exit(0);
      return;
    }

    Boolean force = parser.getOptionValue(forceOption, Boolean.FALSE);
    if (force != null && force) {
      setForce(true);
    }

    Boolean link = parser.getOptionValue(linkOption, Boolean.FALSE);
    if (link != null && link) {
      setLink(true);
    }

    setVendor(parser.getOptionValue(vendorOption));
    setCwd(new File("."));
  }

  private void printUsage() {
    System.err.println("Usage: es4x " + NAME + " [OPTIONS] [arg...]");
    System.err.println();
    System.err.println(SUMMARY);
    System.err.println();
    System.err.println("Options and Arguments:");
    System.err.println(" -f,--force\t\t\t\tWill always install a basic runtime in the current working dir.");
    System.err.println(" -v,--vendor \tComma separated list of vendor jars.");
    System.err.println(" -l,--link\t\t\t\tSymlink jars instead of copy.");
    System.err.println();
  }

  public Install setCwd(File cwd) {
    this.cwd = cwd;
    return this;
  }

  public void setForce(boolean force) {
    this.force = force;
  }

  public void setLink(boolean link) {
    this.link = link;
  }

  public void setVendor(String vendor) {
    if (vendor != null) {
      this.vendor = new ArrayList<>();
      for (String v : vendor.split(",")) {
        // the jar lives in node_modules/.bin (rebase to the project root)
        this.vendor.add(".." + File.separator + ".." + File.separator + v);
      }
    }
  }

  private static void processPackageJson(File json, Set dependencies) throws IOException {
    if (json.exists()) {
      Map npm = JSON.parse(json, Map.class);
      if (npm.containsKey("maven")) {
        final Map maven = (Map) npm.get("maven");
        // add this dependency
        dependencies.add(maven.get("groupId") + ":" + maven.get("artifactId") + ":" + maven.get("version"));
      }

      if (npm.containsKey("mvnDependencies")) {
        final List maven = (List) npm.get("mvnDependencies");
        for (Object el : maven) {
          // add this dependency
          dependencies.add((String) el);
        }
      }

      // only run if not production
      if (!isProduction()) {
        if (npm.containsKey("mvnDevDependencies")) {
          final List maven = (List) npm.get("mvnDevDependencies");
          for (Object el : maven) {
            // add this dependency
            dependencies.add((String) el);
          }
        }
      }
    }

  }

  private static void processModules(File dir, Set dependencies) throws IOException {
    File[] mods = dir.listFiles(File::isDirectory);
    if (mods != null) {
      for (File mod : mods) {
        String name = mod.getName();
        if (name.charAt(0) == '@') {
          processModules(mod, dependencies);
          continue;
        }

        File json = new File(mod, "package.json");

        // process
        processPackageJson(json, dependencies);

        File submod = new File(mod, "node_modules");
        if (submod.exists() && submod.isDirectory()) {
          processModules(submod, dependencies);
        }
      }
    }
  }

  @Override
  public void run() {
    final Set artifacts = new HashSet<>();
    installNodeModules(artifacts);

    final File base = new File(cwd, "node_modules");
    final File libs = new File(base, ".lib");

    if (force || libs.exists()) {
      final double version = Double.parseDouble(System.getProperty("java.specification.version"));
      final boolean isGraalVM =
        System.getProperty("java.vm.name", "").toLowerCase().contains("graalvm") ||
        // from graal 20.0.0 the vm name doesn't contain graalvm in the name
        // but it is now part of the vendor version
        System.getProperty("java.vendor.version", "").toLowerCase().contains("graalvm");

      if (!isGraalVM) {

        // not on graal, install graaljs and dependencies
        warn("Installing GraalJS...");
        // graaljs + dependencies
        installGraalJS(artifacts);

        if (version >= 11) {
          // verify if the current JDK contains the jdk.internal.vm.ci module
          try {
            String modules = exec(javaHomePrefix() + "java", "--list-modules");
            if (modules.contains("jdk.internal.vm.ci")) {
              warn("Installing JVMCI Compiler...");
              // jvmci compiler + dependencies
              installGraalJMVCICompiler();
            }
          } catch (IOException | InterruptedException e) {
            err(e.getMessage());
          }
        } else {
          warn("Current JDK only supports GraalJS in Interpreted mode!");
        }
      }
    }

    // always create a launcher even if no dependencies are needed
    createLauncher(artifacts);
    // always install the es4x type definitions
    installTypeDefinitions();
  }

  private void installGraalJS(Set artifacts) {
    final File base = new File(cwd,"node_modules");

    File libs = new File(base, ".lib");
    if (!libs.exists()) {
      if (!libs.mkdirs()) {
        fatal("Failed to mkdirs 'node_modules/.lib'.");
      }
    }

    try {
      Resolver resolver = new Resolver();

      for (Artifact a : resolver.resolve("org.graalvm.js:js:" + VERSIONS.getProperty("graalvm"), Arrays.asList("org.graalvm.tools:profiler:" + VERSIONS.getProperty("graalvm"), "org.graalvm.tools:chromeinspector:" + VERSIONS.getProperty("graalvm")))) {
        artifacts.add(".." + File.separator + ".lib" + File.separator +  a.getFile().getName());
        File destination = new File(libs, a.getFile().getName());
        if (!destination.exists()) {
          if (link) {
            Files.createSymbolicLink(destination.toPath(), a.getFile().toPath());
          } else {
            Files.copy(a.getFile().toPath(), destination.toPath());
          }
        }
      }
    } catch (IOException e) {
      fatal(e.getMessage());
    }
  }

  private void installGraalJMVCICompiler() {
    final File base = new File(cwd, "node_modules");

    File libs = new File(base, ".jvmci");
    if (!libs.exists()) {
      if (!libs.mkdirs()) {
        fatal("Failed to mkdirs 'node_modules/.jvmci'.");
      }
    }

    try {
      Resolver resolver = new Resolver();

      for (Artifact a : resolver.resolve("org.graalvm.compiler:compiler:" + VERSIONS.getProperty("graalvm"), Collections.emptyList())) {
        File destination = new File(libs, a.getArtifactId() + "." + a.getExtension());
        if (!destination.exists()) {
          if (link) {
            Files.createSymbolicLink(destination.toPath(), a.getFile().toPath());
          } else {
            Files.copy(a.getFile().toPath(), destination.toPath());
          }
        }
      }
    } catch (IOException e) {
      fatal(e.getMessage());
    }
  }

  private void installNodeModules(Set artifacts) {
    final File base = new File(cwd, "node_modules");
    final Set dependencies = new HashSet<>();

    // process mvnDependencies from CWD package.json
    try {
      processPackageJson(new File(cwd, "package.json"), dependencies);
    } catch (IOException e) {
      fatal(e.getMessage());
    }

    // crawl node modules
    if (base.exists() && base.isDirectory()) {
      try {
        processModules(base, dependencies);
      } catch (IOException e) {
        fatal(e.getMessage());
      }
    }

    if (force || dependencies.size() > 0) {
      File libs = new File(base, ".lib");

      try {
        Resolver resolver = new Resolver();

        // lookup root
        String root = "io.reactiverse:es4x:" + VERSIONS.getProperty("es4x");

        for (String el : dependencies) {
          // ensure we respect the wish of the user
          if (el.startsWith("io.reactiverse:es4x:")) {
            root = el;
            break;
          }
        }

        for (Artifact a : resolver.resolve(root, dependencies)) {

          if (!libs.exists()) {
            if (!libs.mkdirs()) {
              fatal("Failed to mkdirs 'node_modules/.lib'.");
            }
          }
          artifacts.add(".." + File.separator + ".lib" + File.separator + a.getFile().getName());
          File destination = new File(libs, a.getFile().getName());

          // locate core jar
          if ("io.vertx".equals(a.getGroupId()) && "vertx-core".equals(a.getArtifactId())) {
            coreJar = a.getFile();
          }

          if (!destination.exists()) {
            if (link) {
              Files.createSymbolicLink(destination.toPath(), a.getFile().toPath());
            } else {
              Files.copy(a.getFile().toPath(), destination.toPath());
            }
          }
        }
      } catch (IOException e) {
        fatal(e.getMessage());
      }
    }
  }

  private void createLauncher(Set artifacts) {

    File json = new File(cwd, "package.json");

    if (json.exists()) {
      try {
        Map npm = JSON.parse(json, Map.class);
        // default main script
        String main = ".";
        String verticleFactory = "js";

        // if package json declares a different main, then it shall be used
        if (npm.containsKey("main")) {
          main = (String) npm.get("main");
          // allow main to be a mjs
          if (main != null && main.endsWith(".mjs")) {
            verticleFactory = "mjs";
          }
        }

        // if package json declares a different main, then it shall be used
        if (npm.containsKey("module")) {
          main = (String) npm.get("module");
          verticleFactory = "mjs";
        }

        final File base = new File(cwd, "node_modules");
        File bin = new File(base, ".bin");
        if (!bin.exists()) {
          if (!bin.mkdirs()) {
            fatal("Failed to mkdirs 'node_modules/.bin'.");
          }
        }

        final Manifest manifest = new Manifest();
        final Attributes attributes = manifest.getMainAttributes();

        attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
        attributes.put(new Attributes.Name("Created-By"), "ES4X " + VERSIONS.getProperty("es4x"));
        attributes.put(new Attributes.Name("Built-By"), System.getProperty("user.name"));
        attributes.put(new Attributes.Name("Build-Jdk"), System.getProperty("java.version"));
        attributes.put(Attributes.Name.MAIN_CLASS, "io.reactiverse.es4x.ES4X");
        String classpath = String.join(" ", artifacts);
        if (vendor != null && vendor.size() > 0) {
          classpath += " " + String.join(" ", vendor);
        }
        attributes.put(Attributes.Name.CLASS_PATH, classpath);
        attributes.put(new Attributes.Name("Main-Verticle"), main);
        attributes.put(new Attributes.Name("Main-Command"), "run");
        attributes.put(new Attributes.Name("Default-Verticle-Factory"), verticleFactory);

        try (JarOutputStream target = new JarOutputStream(new FileOutputStream(new File(bin, "es4x-launcher.jar")), manifest)) {
          if (coreJar != null) {
            try (InputStream in = new FileInputStream(coreJar)) {
              try (JarInputStream jar = new JarInputStream(in)) {
                JarEntry je;
                while ((je = jar.getNextJarEntry()) != null) {
                  if ("io/vertx/core/json/JsonObject.class".equals(je.getName())) {
                    target.putNextEntry(je);
                    target.write(new JsonObjectProxy().rewrite(jar));
                    target.closeEntry();
                  }
                  if ("io/vertx/core/json/JsonArray.class".equals(je.getName())) {
                    target.putNextEntry(je);
                    target.write(new JsonArrayProxy().rewrite(jar));
                    target.closeEntry();
                  }
                }
              } catch (RuntimeException e) {
                warn(e.getMessage());
              }
            } catch (RuntimeException e) {
              warn(e.getMessage());
            }
          }
        }

        // create the launcher scripts
        if (isUnix()) {
          createUNIXScript(bin);
        }
        if (isWindows()) {
          createDOSScript(bin);
        }

      } catch (IOException e) {
        fatal(e.getMessage());
      }
    }
  }

  private void createUNIXScript(File bin) throws IOException {

    String script =
      "#!/usr/bin/env bash\n" +
        "(set -o igncr) 2>/dev/null && set -o igncr; # cygwin encoding fix\n" +
        "\n" +
        "# fight simlinks and avoid readlink -f which doesn't exist on Darwin and Solaris\n" +
        "pushd . > /dev/null\n" +
        "basedir=\"${BASH_SOURCE[0]}\";\n" +
        "while([ -h \"${basedir}\" ]); do\n" +
        "    cd \"`dirname \"${basedir}\"`\"\n" +
        "    basedir=\"$(readlink \"`basename \"${basedir}\"`\")\";\n" +
        "done\n" +
        "cd \"`dirname \"${basedir}\"`\" > /dev/null\n" +
        "basedir=\"`pwd`\";\n" +
        "popd  > /dev/null\n" +
        "\n" +
        "case `uname` in\n" +
        "    *CYGWIN*) basedir=`cygpath -w \"$basedir\"`;;\n" +
        "esac\n" +
        "\n" +
        "JAVA_EXE=\"$JAVA_HOME/bin/java\"\n" +
        "if ! [[ -x \"$JAVA_EXE\" ]]; then\n" +
        "  JAVA_EXE=java\n" +
        "fi\n" +
        "\n" +
        "if [[ -d \"$basedir/../.jvmci\" ]]; then\n" +
        "  JVMCI=\"--module-path=$basedir/../.jvmci -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI --upgrade-module-path=$basedir/../.jvmci/compiler.jar\"\n" +
        "fi\n" +
        "\n" +
        "if [[ -f \"$basedir/../../security.policy\" ]]; then\n" +
        "  SECURITY_MANAGER=\"-Djava.security.manager -Djava.security.policy=$basedir/../../security.policy\"\n" +
        "fi\n" +
        "\n" +
        "if [[ -f \"$basedir/../../logging.properties\" ]]; then\n" +
        "  LOGGING_PROPERTIES=\"-Djava.util.logging.config.file=$basedir/../../logging.properties\"\n" +
        "fi\n" +
        "\n" +
        "exec \"$JAVA_EXE\" -XX:+IgnoreUnrecognizedVMOptions $JVMCI $SECURITY_MANAGER $LOGGING_PROPERTIES $JAVA_OPTS -jar \"$basedir/es4x-launcher.jar\" \"$@\"\n";

    final File exe = new File(bin, "es4x-launcher");
    try (FileOutputStream out = new FileOutputStream(exe)) {
      out.write(script.getBytes(StandardCharsets.UTF_8));
    }

    // this is a best effort
    if (!exe.setExecutable(true, false)) {
      fatal("Cannot set script 'node_modules/.bin/es4x-launcher' executable!");
    }
  }

  private void createDOSScript(File bin) throws IOException {

    String script =
      "@ECHO OFF\n" +
        "\n" +
        "SETLOCAL\n" +
        "\n" +
        "SET \"JAVA_EXE=%JAVA_HOME%\\bin\\java.exe\"\n" +
        "IF NOT EXIST \"%JAVA_EXE%\" (\n" +
        "  SET \"JAVA_EXE=java\"\n" +
        ")\n" +
        "\n" +
        "IF EXIST \"%~dp0\\..\\.jvmci\" (\n" +
        "  SET \"JVMCI=--module-path=\"\"%~dp0\\..\\.jvmci\"\" -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI --upgrade-module-path=\"\"%~dp0\\..\\.jvmci\\compiler.jar\"\"\"\n" +
        ")\n" +
        "\n" +
        "IF EXIST \"%~dp0\\..\\..\\security.policy\" (\n" +
        "  SET \"SECURITY_MANAGER=-Djava.security.manager -Djava.security.policy=\"\"%~dp0\\..\\..\\security.policy\"\"\"\n" +
        ")\n" +
        "\n" +
        "IF EXIST \"%~dp0\\..\\..\\logging.properties\" (\n" +
        "  SET \"LOGGING_PROPERTIES=-Djava.util.logging.config.file=\"\"%~dp0\\..\\..\\logging.properties\"\"\"\n" +
        ")\n" +
        "\n" +
        "\"%JAVA_EXE%\" -XX:+IgnoreUnrecognizedVMOptions %JVMCI% %SECURITY_MANAGER% %LOGGING_PROPERTIES% %JAVA_OPTS% -jar \"%~dp0\\es4x-launcher.jar\" %*\n";

    final File exe = new File(bin, "es4x-launcher.cmd");
    try (FileOutputStream out = new FileOutputStream(exe)) {
      out.write(script.getBytes(StandardCharsets.UTF_8));
    }
  }

  private void installTypeDefinitions() {
    final File base = new File(cwd, "node_modules");

    File atTypes = new File(base, "@types");
    if (!atTypes.exists()) {
      if (!atTypes.mkdirs()) {
        fatal("Failed to mkdirs 'node_modules/@types'.");
      }
    }

    final File file = new File(atTypes, "es4x.d.ts");

    if (force || !file.exists()) {
      // Load the file from the class path
      try (InputStream in = Install.class.getClassLoader().getResourceAsStream("META-INF/es4x-commands/es4x.d.ts")) {
        if (in == null) {
          fatal("Cannot load es4x.d.ts.");
        } else {
          Files.copy(in, file.toPath());
        }
      } catch (IOException e) {
        fatal(e.getMessage());
      }
    }
  }

  private static boolean isProduction() {
    // NODE_ENV set to production
    if ("production".equalsIgnoreCase(System.getenv("NODE_ENV"))) {
      return true;
    }

    return "production".equalsIgnoreCase(System.getenv("ES4X_ENV"));
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy