private @NotNull List<JpsModule> loadModules(@NotNull List<? extends Path> moduleFiles,
                                               @Nullable JpsSdkType<?> projectSdkType,
                                               @NotNull Map<String, String> pathVariables,
                                               @NotNull JpsPathMapper pathMapper,
                                               @Nullable Executor executor) {
    if (executor == null) {
      executor = DefaultExecutorHolder.threadPool;
    }

    List<JpsModule> modules = new ArrayList<>();
    List<CompletableFuture<Pair<Path, Element>>> futureModuleFilesContents = new ArrayList<>();
    Path externalModuleDir;
    if (myExternalConfigurationDirectory != null) {
      externalModuleDir = myExternalConfigurationDirectory.resolve("modules");
      LOG.info("External project config dir is used for modules: " + externalModuleDir);
    }
    else {
      externalModuleDir = null;
    }

    for (Path file : moduleFiles) {
      futureModuleFilesContents.add(CompletableFuture.supplyAsync(() -> {
        JpsMacroExpander expander = JpsProjectConfigurationLoading.createModuleMacroExpander(pathVariables, file);

        Element data = JpsComponentLoader.loadRootElement(file, expander);
        if (externalModuleDir != null) {
          String externalName = FileUtilRt.getNameWithoutExtension(file.getFileName().toString()) + ".xml";
          Element externalData = JpsComponentLoader.loadRootElement(externalModuleDir.resolve(externalName), expander);
          if (externalData != null) {
            if (data == null) {
              data = externalData;
            }
            else {
              JDOMUtil.deepMergeWithAttributes(data, externalData,
                                               List.of(
                                    new JDOMUtil.MergeAttribute("content", "url"),
                                    new JDOMUtil.MergeAttribute("component", "name")
                                  ));
            }
          }
        }

        if (data == null) {
          LOG.info("Module '" + getModuleName(file) + "' is skipped: " + file.toAbsolutePath() + " doesn't exist");
        }
        else {
          // Copy the content roots that are defined in a separate tag, to a general content root component
          List<Element> components = data.getChildren("component");
          Element rootManager = null;
          Element additionalElements = null;
          for (Element component : components) {
            String attributeValue = component.getAttributeValue("name");
            if (attributeValue.equals("NewModuleRootManager")) rootManager = component;
            if (attributeValue.equals("AdditionalModuleElements")) additionalElements = component;
            if (rootManager != null && additionalElements != null) break;
          }
          if (rootManager != null && additionalElements != null) {
            // Cleanup attributes that aren't needed
            additionalElements.removeAttribute("name");
            additionalElements.getChildren().forEach(o -> o.removeAttribute("dumb"));
            JDOMUtil.deepMerge(rootManager, additionalElements);
          }
        }

        return new Pair<>(file, data);
      }, executor));
    }

    try {
      List<String> classpathDirs = new ArrayList<>();
      for (CompletableFuture<Pair<Path, Element>> moduleFile : futureModuleFilesContents) {
        Element rootElement = moduleFile.join().getSecond();
        if (rootElement != null) {
          String classpathDir = rootElement.getAttributeValue(CLASSPATH_DIR_ATTRIBUTE);
          if (classpathDir != null) {
            classpathDirs.add(classpathDir);
          }
        }
      }

      List<CompletableFuture<JpsModule>> futures = new ArrayList<>();
      for (CompletableFuture<Pair<Path, Element>> futureModuleFile : futureModuleFilesContents) {
        Pair<Path, Element> moduleFile = futureModuleFile.join();
        if (moduleFile.getSecond() != null) {
          futures.add(CompletableFuture.supplyAsync(() -> {
            return loadModule(moduleFile.getFirst(), moduleFile.getSecond(), classpathDirs, projectSdkType, pathVariables, pathMapper);
          }, executor));
        }
      }
      for (CompletableFuture<JpsModule> future : futures) {
        JpsModule module = future.join();
        if (module != null) {
          modules.add(module);
        }
      }
      return modules;
    }
    catch (RuntimeException e) {
      throw e;
    }
    catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
private @NotNull List<JpsModule> loadModules(@NotNull List<? extends Path> moduleFiles,
                                               @Nullable JpsSdkType<?> projectSdkType,
                                               @NotNull Map<String, String> pathVariables,
                                               @NotNull JpsPathMapper pathMapper,
                                               @Nullable Executor executor)