[runtime module repository] implement a more efficient format for the repository (IJPL-189949)

A new implementation which generates and loads the runtime module repository from a module-descriptors.dat file in binary format is implemented. module-descriptors.jar in the old format is still generated for compatibility with other tools (e.g., IntelliJ Platform Gradle Plugin) and to provide a human-readable view. It's also used as a fallback variant if module-descriptors.dat is absent.

The new format speeds up loading by around 10 times.

GitOrigin-RevId: b17ba7b53f825e6dcf243ff0aa5b7aedaf7ab9e2
This commit is contained in:
Nikolay Chashnikov
2025-06-05 17:35:36 +02:00
committed by intellij-monorepo-bot
parent 1ad00d2a66
commit 791282fbe7
17 changed files with 305 additions and 52 deletions

View File

@@ -37,7 +37,11 @@
- getBasePath():java.nio.file.Path
- getMainPluginModuleId():java.lang.String
*f:com.intellij.platform.runtime.repository.serialization.RuntimeModuleRepositorySerialization
- s:loadBootstrapClasspath(java.nio.file.Path,java.lang.String):java.lang.String[]
- s:loadFromCompactFile(java.nio.file.Path):com.intellij.platform.runtime.repository.serialization.RawRuntimeModuleRepositoryData
- s:loadFromJar(java.nio.file.Path):com.intellij.platform.runtime.repository.serialization.RawRuntimeModuleRepositoryData
- s:loadFromRawData(java.nio.file.Path,com.intellij.platform.runtime.repository.serialization.RawRuntimeModuleRepositoryData):com.intellij.platform.runtime.repository.RuntimeModuleRepository
- s:saveToCompactFile(java.util.Collection,java.lang.String,java.nio.file.Path,I):V
- s:saveToCompactFile(java.util.Collection,java.lang.String,java.nio.file.Path,java.lang.String,I):V
- s:saveToJar(java.util.Collection,java.lang.String,java.nio.file.Path,I):V
- s:saveToJar(java.util.Collection,java.lang.String,java.nio.file.Path,java.lang.String,I):V

View File

@@ -17,10 +17,10 @@ import java.util.List;
@ApiStatus.NonExtendable
public interface RuntimeModuleRepository {
/**
* Creates a repository from a JAR file containing module descriptors.
* Creates a repository from a file containing module descriptors.
*/
static @NotNull RuntimeModuleRepository create(@NotNull Path moduleDescriptorsJarPath) throws MalformedRepositoryException {
return new RuntimeModuleRepositoryImpl(moduleDescriptorsJarPath);
static @NotNull RuntimeModuleRepository create(@NotNull Path moduleDescriptorsFilePath) throws MalformedRepositoryException {
return new RuntimeModuleRepositoryImpl(moduleDescriptorsFilePath);
}
/**

View File

@@ -8,7 +8,6 @@ import com.intellij.platform.runtime.repository.RuntimeModuleRepository;
import com.intellij.platform.runtime.repository.serialization.RawRuntimeModuleDescriptor;
import com.intellij.platform.runtime.repository.serialization.RawRuntimeModuleRepositoryData;
import com.intellij.platform.runtime.repository.serialization.RuntimeModuleRepositorySerialization;
import com.intellij.platform.runtime.repository.serialization.impl.JarFileSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -22,15 +21,15 @@ public class RuntimeModuleRepositoryImpl implements RuntimeModuleRepository {
private final Map<RuntimeModuleId, ResolveResult> myResolveResults;
private volatile RawRuntimeModuleRepositoryData myMainData;
private volatile List<RawRuntimeModuleRepositoryData> myAdditionalData;
private final Path myDescriptorsJarPath;
private final Path myDescriptorsFilePath;
private final Map<String, RuntimeModuleId> myInternedModuleIds;
public RuntimeModuleRepositoryImpl(@NotNull Path descriptorsJarPath) {
this(descriptorsJarPath, null);
public RuntimeModuleRepositoryImpl(@NotNull Path descriptorsFilePath) {
this(descriptorsFilePath, null);
}
public RuntimeModuleRepositoryImpl(@NotNull Path descriptorsJarPath, @Nullable RawRuntimeModuleRepositoryData preloadedMainData) {
myDescriptorsJarPath = descriptorsJarPath;
public RuntimeModuleRepositoryImpl(@NotNull Path descriptorsFilePath, @Nullable RawRuntimeModuleRepositoryData preloadedMainData) {
myDescriptorsFilePath = descriptorsFilePath;
myResolveResults = new ConcurrentHashMap<>();
myInternedModuleIds = new ConcurrentHashMap<>();
myMainData = preloadedMainData;
@@ -144,11 +143,11 @@ public class RuntimeModuleRepositoryImpl implements RuntimeModuleRepository {
public @NotNull List<@NotNull Path> getBootstrapClasspath(@NotNull String bootstrapModuleName) {
if (myMainData == null) {
try {
String[] bootstrapClasspath = JarFileSerializer.loadBootstrapClasspath(myDescriptorsJarPath, bootstrapModuleName);
String[] bootstrapClasspath = RuntimeModuleRepositorySerialization.loadBootstrapClasspath(myDescriptorsFilePath, bootstrapModuleName);
if (bootstrapClasspath != null) {
List<Path> result = new ArrayList<>(bootstrapClasspath.length);
for (String relativePath : bootstrapClasspath) {
result.add(myDescriptorsJarPath.getParent().resolve(relativePath));
result.add(myDescriptorsFilePath.getParent().resolve(relativePath));
}
return result;
}
@@ -174,12 +173,18 @@ public class RuntimeModuleRepositoryImpl implements RuntimeModuleRepository {
@Override
public String toString() {
return "RuntimeModuleRepository{descriptorsJarPath=" + myDescriptorsJarPath + '}';
return "RuntimeModuleRepository{descriptorsFilePath=" + myDescriptorsFilePath + '}';
}
private RawRuntimeModuleRepositoryData getMainData() {
if (myMainData == null) {
myMainData = RuntimeModuleRepositorySerialization.loadFromJar(myDescriptorsJarPath);
Path fallbackJarPath = RuntimeModuleRepositorySerialization.getFallbackJarPath(myDescriptorsFilePath);
if (fallbackJarPath != null) {
myMainData = RuntimeModuleRepositorySerialization.loadFromJar(fallbackJarPath);
}
else {
myMainData = RuntimeModuleRepositorySerialization.loadFromCompactFile(myDescriptorsFilePath);
}
}
return myMainData;
}

View File

@@ -4,18 +4,32 @@ package com.intellij.platform.runtime.repository.serialization;
import com.intellij.platform.runtime.repository.MalformedRepositoryException;
import com.intellij.platform.runtime.repository.RuntimeModuleRepository;
import com.intellij.platform.runtime.repository.impl.RuntimeModuleRepositoryImpl;
import com.intellij.platform.runtime.repository.serialization.impl.CompactFileReader;
import com.intellij.platform.runtime.repository.serialization.impl.CompactFileWriter;
import com.intellij.platform.runtime.repository.serialization.impl.JarFileSerializer;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.xml.stream.XMLStreamException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
public final class RuntimeModuleRepositorySerialization {
private RuntimeModuleRepositorySerialization() {}
public static void saveToCompactFile(@NotNull Collection<RawRuntimeModuleDescriptor> descriptors, @Nullable String bootstrapModuleName,
@NotNull Path filePath, int generatorVersion) throws IOException {
saveToCompactFile(descriptors, bootstrapModuleName, filePath, null, generatorVersion);
}
public static void saveToCompactFile(@NotNull Collection<RawRuntimeModuleDescriptor> descriptors, @Nullable String bootstrapModuleName,
@NotNull Path filePath, @Nullable String mainPluginModuleId, int generatorVersion) throws IOException {
CompactFileWriter.saveToFile(descriptors, bootstrapModuleName, mainPluginModuleId, generatorVersion, filePath);
}
public static void saveToJar(@NotNull Collection<RawRuntimeModuleDescriptor> descriptors, @Nullable String bootstrapModuleName,
@NotNull Path jarPath, int generatorVersion) throws IOException {
saveToJar(descriptors, bootstrapModuleName, jarPath, null, generatorVersion);
@@ -31,6 +45,15 @@ public final class RuntimeModuleRepositorySerialization {
throw new IOException(e);
}
}
public static @NotNull RawRuntimeModuleRepositoryData loadFromCompactFile(@NotNull Path filePath) throws MalformedRepositoryException {
try {
return CompactFileReader.loadFromFile(filePath);
}
catch (IOException e) {
throw new MalformedRepositoryException("Failed to load repository from " + filePath, e);
}
}
public static @NotNull RawRuntimeModuleRepositoryData loadFromJar(@NotNull Path jarPath) throws MalformedRepositoryException {
try {
@@ -45,4 +68,25 @@ public final class RuntimeModuleRepositorySerialization {
@NotNull RawRuntimeModuleRepositoryData rawRuntimeModuleRepositoryData) {
return new RuntimeModuleRepositoryImpl(descriptorsJarPath, rawRuntimeModuleRepositoryData);
}
@ApiStatus.Internal
public static @Nullable Path getFallbackJarPath(@NotNull Path descriptorsFilePath) {
if (descriptorsFilePath.getFileName().toString().endsWith(".jar")) {
return descriptorsFilePath;
}
Path jarPath = descriptorsFilePath.getParent().resolve("module-descriptors.jar");
if (!Files.exists(descriptorsFilePath) && Files.exists(jarPath)) {
return jarPath;
}
return null;
}
public static @NotNull String @Nullable [] loadBootstrapClasspath(@NotNull Path descriptorsFilePath, @NotNull String bootstrapModuleName)
throws IOException {
Path fallbackJarPath = getFallbackJarPath(descriptorsFilePath);
if (fallbackJarPath != null) {
return JarFileSerializer.loadBootstrapClasspath(fallbackJarPath, bootstrapModuleName);
}
return CompactFileReader.loadBootstrapClasspath(descriptorsFilePath, bootstrapModuleName);
}
}

View File

@@ -0,0 +1,91 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.runtime.repository.serialization.impl;
import com.intellij.platform.runtime.repository.MalformedRepositoryException;
import com.intellij.platform.runtime.repository.serialization.RawRuntimeModuleDescriptor;
import com.intellij.platform.runtime.repository.serialization.RawRuntimeModuleRepositoryData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public final class CompactFileReader {
public static final int FORMAT_VERSION = 1;
public static RawRuntimeModuleRepositoryData loadFromFile(@NotNull Path filePath) throws IOException {
try (DataInputStream in = new DataInputStream(new BufferedInputStream(Files.newInputStream(filePath)))) {
int formatVersion = in.readInt();
if (formatVersion != FORMAT_VERSION) {
throw new MalformedRepositoryException("'" + filePath + "' has unsupported format '" + formatVersion + "'");
}
in.readInt();//generator version
boolean hasBootstrapClasspath = in.readBoolean();
if (hasBootstrapClasspath) {
in.readUTF();
int size = in.readInt();
for (int i = 0; i < size; i++) {
in.readUTF();
}
}
boolean hasMainPluginModule = in.readBoolean();
String mainPluginModuleName = hasMainPluginModule? in.readUTF() : null;
Map<String, RawRuntimeModuleDescriptor> descriptors = new HashMap<>();
int descriptorsCount = in.readInt();
String[] descriptorIds = new String[descriptorsCount];
for (int i = 0; i < descriptorsCount; i++) {
descriptorIds[i] = in.readUTF();
}
for (int i = 0; i < descriptorsCount; i++) {
String descriptorId = descriptorIds[i];
int dependenciesCount = in.readInt();
List<String> dependencies = new ArrayList<>(dependenciesCount);
for (int j = 0; j < dependenciesCount; j++) {
int dependencyIndex = in.readInt();
if (dependencyIndex < 0 || dependencyIndex >= descriptorsCount) {
throw new MalformedRepositoryException("Invalid dependency index '" + dependencyIndex + "' in '" + descriptorId + "'");
}
dependencies.add(descriptorIds[dependencyIndex]);
}
int resourcePathsCount = in.readInt();
List<String> resourcePaths = new ArrayList<>(resourcePathsCount);
for (int j = 0; j < resourcePathsCount; j++) {
resourcePaths.add(in.readUTF());
}
descriptors.put(descriptorId, RawRuntimeModuleDescriptor.create(descriptorId, resourcePaths, dependencies));
}
return new RawRuntimeModuleRepositoryData(descriptors, filePath.getParent(), mainPluginModuleName);
}
}
public static @NotNull String @Nullable [] loadBootstrapClasspath(@NotNull Path binaryFile, @NotNull String bootstrapModuleName) throws IOException {
try (DataInputStream in = new DataInputStream(new BufferedInputStream(Files.newInputStream(binaryFile)))) {
int formatVersion = in.readInt();
if (formatVersion != FORMAT_VERSION) return null;
in.readInt();
boolean hasBootstrap = in.readBoolean();
if (!hasBootstrap) return null;
String actualBootstrapModuleName = in.readUTF();
if (!actualBootstrapModuleName.equals(bootstrapModuleName)) return null;
int size = in.readInt();
String[] classpath = new String[size];
for (int i = 0; i < size; i++) {
classpath[i] = in.readUTF();
}
return classpath;
}
}
}

View File

@@ -0,0 +1,69 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.runtime.repository.serialization.impl;
import com.intellij.platform.runtime.repository.MalformedRepositoryException;
import com.intellij.platform.runtime.repository.serialization.RawRuntimeModuleDescriptor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public final class CompactFileWriter {
public static void saveToFile(@NotNull Collection<RawRuntimeModuleDescriptor> originalDescriptors,
@Nullable String bootstrapModuleName, @Nullable String mainPluginModuleId,
int generatorVersion,
@NotNull Path outputFile) throws IOException {
try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(Files.newOutputStream(outputFile)))) {
out.writeInt(CompactFileReader.FORMAT_VERSION);
out.writeInt(generatorVersion);
boolean hasBootstrap = bootstrapModuleName != null;
out.writeBoolean(hasBootstrap);
if (hasBootstrap) {
out.writeUTF(bootstrapModuleName);
Collection<String> bootstrapClasspath = CachedClasspathComputation.computeClasspath(originalDescriptors, bootstrapModuleName);
out.writeInt(bootstrapClasspath.size());
for (String path : bootstrapClasspath) {
out.writeUTF(path);
}
}
boolean hasMainPluginModule = mainPluginModuleId != null;
out.writeBoolean(hasMainPluginModule);
if (hasMainPluginModule) {
out.writeUTF(mainPluginModuleId);
}
List<RawRuntimeModuleDescriptor> descriptors = new ArrayList<>(originalDescriptors);
Collections.sort(descriptors, Comparator.comparing(RawRuntimeModuleDescriptor::getId));
Map<String, Integer> indexes = new HashMap<>(descriptors.size());
for (int i = 0; i < descriptors.size(); i++) {
indexes.put(descriptors.get(i).getId(), i);
}
out.writeInt(descriptors.size());
for (RawRuntimeModuleDescriptor descriptor : descriptors) {
out.writeUTF(descriptor.getId());
}
for (RawRuntimeModuleDescriptor descriptor : descriptors) {
out.writeInt(descriptor.getDependencies().size());
for (String dependency : descriptor.getDependencies()) {
Integer index = indexes.get(dependency);
if (index == null) {
throw new MalformedRepositoryException("Unknown dependency '" + dependency + "' in '" + descriptor.getId() + "'");
}
out.writeInt(index);
}
out.writeInt(descriptor.getResourcePaths().size());
for (String path : descriptor.getResourcePaths()) {
out.writeUTF(path);
}
}
}
}
}

View File

@@ -16,6 +16,7 @@ jvm_library(
"//platform/util-ex",
"//platform/testFramework",
"//platform/testFramework:testFramework_test_lib",
"@lib//:junit5Pioneer",
]
)

View File

@@ -10,5 +10,6 @@
<orderEntry type="module" module-name="intellij.platform.runtime.repository" />
<orderEntry type="module" module-name="intellij.platform.util.ex" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
<orderEntry type="library" scope="TEST" name="JUnit5Pioneer" level="project" />
</component>
</module>

View File

@@ -11,8 +11,7 @@ import com.intellij.testFramework.rules.TempDirectoryExtension
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import org.junitpioneer.jupiter.cartesian.CartesianTest
import java.nio.file.Path
import kotlin.io.path.Path
@@ -153,18 +152,27 @@ class RepositoryTest {
assertEquals(listOf("bar.jar", "foo.jar", "baz.jar").map { tempDirectory.rootPath.resolve(it) }, classpath)
}
@ParameterizedTest(name = "stored bootstrap module = {0}")
@ValueSource(strings = ["", "ij.foo", "ij.bar"])
fun `bootstrap classpath`(storedBootstrapModule: String) {
@CartesianTest(name = "stored bootstrap module = {0}, loadFromCompact = {1}")
fun `bootstrap classpath`(
@CartesianTest.Values(strings = ["", "ij.foo", "ij.bar"]) storedBootstrapModule: String,
@CartesianTest.Values(booleans = [true, false]) loadFromCompact: Boolean
) {
val descriptors = arrayOf(
RawRuntimeModuleDescriptor.create("ij.foo", listOf("foo.jar"), emptyList()),
RawRuntimeModuleDescriptor.create("ij.bar", listOf("bar.jar"), listOf("ij.foo")),
)
val basePath = tempDirectory.rootPath
val moduleDescriptorsJarPath = basePath.resolve("module-descriptors.jar")
val bootstrapModuleName = storedBootstrapModule.takeIf { it.isNotEmpty() }
RuntimeModuleRepositorySerialization.saveToJar(descriptors.asList(), bootstrapModuleName, moduleDescriptorsJarPath, 0)
val repository = RuntimeModuleRepository.create(moduleDescriptorsJarPath)
val filePath: Path
if (loadFromCompact) {
filePath = basePath.resolve("module-descriptors.dat")
RuntimeModuleRepositorySerialization.saveToCompactFile(descriptors.asList(), bootstrapModuleName, filePath, 0)
}
else {
filePath = basePath.resolve("module-descriptors.jar")
RuntimeModuleRepositorySerialization.saveToJar(descriptors.asList(), bootstrapModuleName, filePath, 0)
}
val repository = RuntimeModuleRepository.create(filePath)
assertEquals(listOf(basePath.resolve("bar.jar"), basePath.resolve("foo.jar")), repository.getBootstrapClasspath("ij.bar"))
}

View File

@@ -16,8 +16,8 @@ fun DirectoryContentBuilder.xml(name: String, @Language("XML") content: String)
}
fun createRepository(basePath: Path, vararg descriptors: RawRuntimeModuleDescriptor): RuntimeModuleRepository {
val moduleDescriptorsJarPath = basePath.resolve("module-descriptors.jar")
return RuntimeModuleRepositoryImpl(moduleDescriptorsJarPath,
val moduleDescriptorsPath = basePath.resolve("module-descriptors.dat")
return RuntimeModuleRepositoryImpl(moduleDescriptorsPath,
RawRuntimeModuleRepositoryData(descriptors.associateBy { it.id }, basePath, null))
}

View File

@@ -5,12 +5,10 @@ import com.intellij.platform.runtime.repository.serialization.impl.JarFileSerial
import com.intellij.platform.runtime.repository.xml
import com.intellij.testFramework.UsefulTestCase
import com.intellij.testFramework.rules.TempDirectoryExtension
import com.intellij.util.io.DirectoryContentBuilder
import com.intellij.util.io.DirectoryContentSpec
import com.intellij.util.io.assertMatches
import com.intellij.util.io.jarFile
import com.intellij.util.io.*
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
import java.nio.file.Path
class JarRepositorySerializationTest {
@JvmField
@@ -111,19 +109,26 @@ class JarRepositorySerializationTest {
}
content()
}
checkSaving(descriptors, bootstrapModuleName, jarFile)
checkLoading(jarFile, descriptors)
val out = tempDirectory.rootPath
val jarFilePath = out.resolve("module-descriptors.jar")
RuntimeModuleRepositorySerialization.saveToJar(descriptors, bootstrapModuleName, jarFilePath, 0)
jarFilePath.assertMatches(jarFile)
val compactFilePath = out.resolve("module-descriptors.dat")
RuntimeModuleRepositorySerialization.saveToCompactFile(descriptors, bootstrapModuleName, compactFilePath, 0)
checkLoadingFromCompactFile(compactFilePath, descriptors)
checkLoadingFromJar(jarFile, descriptors)
}
private fun checkLoading(zipFileSpec: DirectoryContentSpec, expectedDescriptors: List<RawRuntimeModuleDescriptor>) {
val repositoryData = RuntimeModuleRepositorySerialization.loadFromJar(zipFileSpec.generateInTempDir())
private fun checkLoadingFromCompactFile(filePath: Path, expectedDescriptors: List<RawRuntimeModuleDescriptor>) {
val repositoryData = RuntimeModuleRepositorySerialization.loadFromCompactFile(filePath)
val actualDescriptors = repositoryData.allIds.map { repositoryData.findDescriptor(it)!! }
UsefulTestCase.assertSameElements(actualDescriptors, expectedDescriptors)
}
private fun checkSaving(descriptors: List<RawRuntimeModuleDescriptor>, bootstrapModuleName: String?, zipFileSpec: DirectoryContentSpec) {
val jarFile = tempDirectory.rootPath.resolve("descriptors.jar")
RuntimeModuleRepositorySerialization.saveToJar(descriptors, bootstrapModuleName, jarFile, 0)
jarFile.assertMatches(zipFileSpec)
private fun checkLoadingFromJar(zipFileSpec: DirectoryContentSpec, expectedDescriptors: List<RawRuntimeModuleDescriptor>) {
val repositoryData = RuntimeModuleRepositorySerialization.loadFromJar(zipFileSpec.generateInTempDir())
val actualDescriptors = repositoryData.allIds.map { repositoryData.findDescriptor(it)!! }
UsefulTestCase.assertSameElements(actualDescriptors, expectedDescriptors)
}
}

View File

@@ -1,6 +1,6 @@
build.target.intellij.runtime.module.descriptors=IntelliJ Runtime Module Descriptors
builder.name.intellij.runtime.module.descriptors=IntelliJ runtime module descriptors builder
error.message.duplicating.id.0.is.found=Duplicating ID ''{0}'' is found
error.message.failed.to.save.jar.file.0=Failed to save JAR file: {0}
error.message.failed.to.save.repository.0=Failed to save the repository: {0}
error.message.project.compiler.output.directory.is.not.specified=Project compiler output directory is not specified.
progress.message.generating.intellij.modules.repository=Generating IntelliJ Modules Repository\u2026

View File

@@ -3,6 +3,7 @@ package com.intellij.devkit.runtimeModuleRepository.jps.build
object RuntimeModuleRepositoryBuildConstants {
const val JAR_REPOSITORY_FILE_NAME: String = "module-descriptors.jar"
const val COMPACT_REPOSITORY_FILE_NAME: String = "module-descriptors.dat"
const val GENERATOR_VERSION: Int = 2
/**

View File

@@ -1,6 +1,7 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.devkit.runtimeModuleRepository.jps.build
import com.intellij.devkit.runtimeModuleRepository.jps.build.RuntimeModuleRepositoryBuildConstants.COMPACT_REPOSITORY_FILE_NAME
import com.intellij.devkit.runtimeModuleRepository.jps.build.RuntimeModuleRepositoryBuildConstants.GENERATOR_VERSION
import com.intellij.devkit.runtimeModuleRepository.jps.build.RuntimeModuleRepositoryBuildConstants.JAR_REPOSITORY_FILE_NAME
import com.intellij.devkit.runtimeModuleRepository.jps.impl.DevkitRuntimeModuleRepositoryJpsBundle
@@ -8,6 +9,7 @@ import com.intellij.openapi.diagnostic.logger
import com.intellij.platform.runtime.repository.RuntimeModuleId
import com.intellij.platform.runtime.repository.serialization.RawRuntimeModuleDescriptor
import com.intellij.platform.runtime.repository.serialization.RuntimeModuleRepositorySerialization
import com.intellij.platform.runtime.repository.serialization.impl.CompactFileWriter
import org.jetbrains.annotations.Nls
import org.jetbrains.jps.builders.BuildOutputConsumer
import org.jetbrains.jps.builders.BuildRootDescriptor
@@ -74,19 +76,27 @@ internal class RuntimeModuleRepositoryBuilder
context.reportError(DevkitRuntimeModuleRepositoryJpsBundle.message("error.message.project.compiler.output.directory.is.not.specified"))
return
}
val outputPath = Path.of(JpsPathUtil.urlToOsPath (outputUrl), JAR_REPOSITORY_FILE_NAME)
val timeToSaveDescriptors = measureTimeMillis {
try {
RuntimeModuleRepositorySerialization.saveToJar(descriptors, null, outputPath, GENERATOR_VERSION)
}
catch (e: IOException) {
LOG.info(e)
context.reportError(DevkitRuntimeModuleRepositoryJpsBundle.message("error.message.failed.to.save.jar.file.0", e.message ?: ""))
}
}
val outputDir = Path.of(JpsPathUtil.urlToOsPath(outputUrl))
val modulesXml = RuntimeModuleRepositoryTarget.getModulesXmlFile(project) ?: error("Project was not loaded from .idea")
outputConsumer.registerOutputFile(outputPath.toFile(), listOf(modulesXml.absolutePath))
LOG.info("${descriptors.size} descriptors are saved in ${timeToSaveDescriptors}ms")
try {
val jarRepositoryPath = outputDir.resolve(JAR_REPOSITORY_FILE_NAME)
val timeToSaveDescriptorsToJar = measureTimeMillis {
RuntimeModuleRepositorySerialization.saveToJar(descriptors, null, jarRepositoryPath, null, GENERATOR_VERSION)
}
outputConsumer.registerOutputFile(jarRepositoryPath.toFile(), listOf(modulesXml.absolutePath))
LOG.info("${descriptors.size} descriptors are saved to JAR in ${timeToSaveDescriptorsToJar}ms")
val compactRepositoryPath = outputDir.resolve(COMPACT_REPOSITORY_FILE_NAME)
val timeToSaveDescriptorsToCompactFile = measureTimeMillis {
CompactFileWriter.saveToFile(descriptors, null, null, GENERATOR_VERSION, compactRepositoryPath)
}
LOG.info("${descriptors.size} descriptors are saved in compact format in ${timeToSaveDescriptorsToCompactFile}ms")
outputConsumer.registerOutputFile(compactRepositoryPath.toFile(), listOf(modulesXml.absolutePath))
}
catch (e: IOException) {
LOG.info(e)
context.reportError(DevkitRuntimeModuleRepositoryJpsBundle.message("error.message.failed.to.save.repository.0", e.message ?: ""))
}
}
private fun CompileContext.reportError(message: @Nls String) {

View File

@@ -4,9 +4,11 @@
package com.intellij.devkit.runtimeModuleRepository.jps.build
import com.dynatrace.hash4j.hashing.HashSink
import com.intellij.devkit.runtimeModuleRepository.jps.build.RuntimeModuleRepositoryBuildConstants.COMPACT_REPOSITORY_FILE_NAME
import com.intellij.devkit.runtimeModuleRepository.jps.build.RuntimeModuleRepositoryBuildConstants.JAR_REPOSITORY_FILE_NAME
import com.intellij.devkit.runtimeModuleRepository.jps.impl.DevkitRuntimeModuleRepositoryJpsBundle
import com.intellij.openapi.diagnostic.logger
import com.intellij.platform.runtime.repository.serialization.impl.CompactFileReader
import com.intellij.platform.runtime.repository.serialization.impl.JarFileSerializer
import org.jetbrains.jps.builders.*
import org.jetbrains.jps.builders.impl.BuildRootDescriptorImpl
@@ -69,11 +71,14 @@ internal class RuntimeModuleRepositoryTarget(
override fun getOutputRoots(context: CompileContext): Collection<File> {
val project = context.projectDescriptor.project
val outputUrl = JpsJavaExtensionService.getInstance().getProjectExtension(project)?.outputUrl ?: return emptyList()
return java.util.List.of(File(JpsPathUtil.urlToFile(outputUrl), JAR_REPOSITORY_FILE_NAME))
val outputDir = JpsPathUtil.urlToFile(outputUrl)
return java.util.List.of(File(outputDir, JAR_REPOSITORY_FILE_NAME),
File(outputDir, COMPACT_REPOSITORY_FILE_NAME))
}
override fun computeConfigurationDigest(projectDescriptor: ProjectDescriptor, hash: HashSink) {
hash.putString(JarFileSerializer.SPECIFICATION_VERSION)
hash.putInt(CompactFileReader.FORMAT_VERSION)
hash.putInt(RuntimeModuleRepositoryBuildConstants.GENERATOR_VERSION)
val time = measureTimeMillis {

View File

@@ -3,14 +3,23 @@ package com.intellij.devkit.runtimeModuleRepository.jps.build
import com.intellij.platform.runtime.repository.RuntimeModuleId
import com.intellij.platform.runtime.repository.serialization.RawRuntimeModuleDescriptor
import com.intellij.platform.runtime.repository.serialization.RawRuntimeModuleRepositoryData
import com.intellij.platform.runtime.repository.serialization.RuntimeModuleRepositorySerialization
import org.jetbrains.jps.builders.JpsBuildTestCase
import java.nio.file.Path
fun checkRuntimeModuleRepository(outputDir: Path,
expected: RawDescriptorListBuilder.() -> Unit) {
val zipPath = outputDir.resolve(RuntimeModuleRepositoryBuildConstants.JAR_REPOSITORY_FILE_NAME)
val buildRepositoryData = RuntimeModuleRepositorySerialization.loadFromJar(zipPath)
val jarPath = outputDir.resolve(RuntimeModuleRepositoryBuildConstants.JAR_REPOSITORY_FILE_NAME)
checkRuntimeModuleRepository(RuntimeModuleRepositorySerialization.loadFromJar(jarPath), expected)
val compactPath = outputDir.resolve(RuntimeModuleRepositoryBuildConstants.COMPACT_REPOSITORY_FILE_NAME)
checkRuntimeModuleRepository(RuntimeModuleRepositorySerialization.loadFromCompactFile(compactPath), expected)
}
private fun checkRuntimeModuleRepository(
buildRepositoryData: RawRuntimeModuleRepositoryData,
expected: RawDescriptorListBuilder.() -> Unit,
) {
val actualIds = buildRepositoryData.allIds.filter { it != RUNTIME_REPOSITORY_MARKER_MODULE && it != "${RUNTIME_REPOSITORY_MARKER_MODULE}${RuntimeModuleId.TESTS_NAME_SUFFIX}" }
val builder = RawDescriptorListBuilder()
builder.expected()

View File

@@ -83,7 +83,7 @@ class RuntimeModuleRepositoryIncrementalBuildTest : RuntimeModuleRepositoryTestC
descriptor("a")
}
deleteFile("out/${RuntimeModuleRepositoryBuildConstants.JAR_REPOSITORY_FILE_NAME}")
deleteFile("out/${RuntimeModuleRepositoryBuildConstants.COMPACT_REPOSITORY_FILE_NAME}")
buildAndCheck {
descriptor("a")
}