List<JpsModule> (@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 = resolveExternalProjectConfig("modules"); if (externalModuleDir != null) { LOG.info("External project config dir is used for modules: " + externalModuleDir); } for (Path file : moduleFiles) { futureModuleFilesContents.add(CompletableFuture.supplyAsync(() -> { JpsMacroExpander expander = createModuleMacroExpander(pathVariables, file); Element data = loadRootElement(file, expander); if (externalModuleDir != null) { String externalName = FileUtilRt.getNameWithoutExtension(file.getFileName().toString()) + ".xml"; Element externalData = 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); } }