implements PLN-0041 shipper preparation foundation
This commit is contained in:
parent
6649b11ec4
commit
70b9a183ba
@ -10,10 +10,12 @@ dependencies {
|
||||
implementation(project(":prometeu-packer:prometeu-packer-api"))
|
||||
implementation(project(":prometeu-compiler:prometeu-compiler-core"))
|
||||
implementation(project(":prometeu-compiler:prometeu-build-pipeline"))
|
||||
implementation(project(":prometeu-compiler:prometeu-frontend-api"))
|
||||
implementation(project(":prometeu-compiler:prometeu-frontend-registry"))
|
||||
implementation(libs.javafx.controls)
|
||||
implementation(libs.javafx.fxml)
|
||||
implementation(libs.richtextfx)
|
||||
testImplementation(project(":prometeu-packer:prometeu-packer-v1"))
|
||||
}
|
||||
|
||||
javafx {
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
package p.studio.shipper;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
public record StudioShipperLogEntry(
|
||||
StudioShipperLogSource source,
|
||||
StudioShipperLogLevel level,
|
||||
String message,
|
||||
Instant timestamp) {
|
||||
|
||||
public StudioShipperLogEntry {
|
||||
Objects.requireNonNull(source, "source");
|
||||
Objects.requireNonNull(level, "level");
|
||||
message = Objects.requireNonNull(message, "message").trim();
|
||||
timestamp = timestamp == null ? Instant.now() : timestamp;
|
||||
if (message.isBlank()) {
|
||||
throw new IllegalArgumentException("message must not be blank");
|
||||
}
|
||||
}
|
||||
|
||||
public static StudioShipperLogEntry info(
|
||||
final StudioShipperLogSource source,
|
||||
final String message) {
|
||||
return new StudioShipperLogEntry(source, StudioShipperLogLevel.INFO, message, Instant.now());
|
||||
}
|
||||
|
||||
public static StudioShipperLogEntry error(
|
||||
final StudioShipperLogSource source,
|
||||
final String message) {
|
||||
return new StudioShipperLogEntry(source, StudioShipperLogLevel.ERROR, message, Instant.now());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package p.studio.shipper;
|
||||
|
||||
public enum StudioShipperLogLevel {
|
||||
INFO,
|
||||
ERROR
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package p.studio.shipper;
|
||||
|
||||
public enum StudioShipperLogSource {
|
||||
BUILD,
|
||||
PACK_VALIDATION,
|
||||
PACK,
|
||||
MANIFEST
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package p.studio.shipper;
|
||||
|
||||
public enum StudioShipperPreparationStatus {
|
||||
SUCCESS,
|
||||
FAILED
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package p.studio.shipper;
|
||||
|
||||
import p.packer.messages.PackWorkspaceResult;
|
||||
import p.packer.messages.ValidatePackWorkspaceResult;
|
||||
import p.studio.compiler.models.BuildResult;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public record StudioShipperPrepareResult(
|
||||
StudioShipperPreparationStatus status,
|
||||
List<StudioShipperLogEntry> logs,
|
||||
BuildResult buildResult,
|
||||
ValidatePackWorkspaceResult validationResult,
|
||||
PackWorkspaceResult packResult,
|
||||
Path manifestPath) {
|
||||
|
||||
public StudioShipperPrepareResult {
|
||||
Objects.requireNonNull(status, "status");
|
||||
logs = List.copyOf(Objects.requireNonNull(logs, "logs"));
|
||||
manifestPath = manifestPath == null ? null : manifestPath.toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
public boolean success() {
|
||||
return status == StudioShipperPreparationStatus.SUCCESS;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,275 @@
|
||||
package p.studio.shipper;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import p.packer.PackerWorkspaceService;
|
||||
import p.packer.dtos.PackerDiagnosticDTO;
|
||||
import p.packer.dtos.PackerPackValidationAssetDTO;
|
||||
import p.packer.messages.PackWorkspaceRequest;
|
||||
import p.packer.messages.PackWorkspaceResult;
|
||||
import p.packer.messages.PackerOperationStatus;
|
||||
import p.packer.messages.ValidatePackWorkspaceRequest;
|
||||
import p.packer.messages.ValidatePackWorkspaceResult;
|
||||
import p.studio.Container;
|
||||
import p.studio.compiler.messages.BuilderPipelineConfig;
|
||||
import p.studio.compiler.models.BuildResult;
|
||||
import p.studio.compiler.models.BuilderPipelineContext;
|
||||
import p.studio.compiler.workspaces.BuilderPipelineService;
|
||||
import p.studio.projects.ProjectReference;
|
||||
import p.studio.utilities.logs.LogAggregator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class StudioShipperService {
|
||||
private static final String BUILD_DIR = "build";
|
||||
private static final String MANIFEST_FILE = "manifest.json";
|
||||
private static final String PROJECT_MANIFEST_FILE = "prometeu.json";
|
||||
private static final String ASSET_TABLE_FILE = "asset_table.json";
|
||||
private static final String PRELOAD_FILE = "preload.json";
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
private final PackerWorkspaceService packerWorkspaceService;
|
||||
|
||||
public StudioShipperService() {
|
||||
this(Container.packer().workspaceService());
|
||||
}
|
||||
|
||||
public StudioShipperService(final PackerWorkspaceService packerWorkspaceService) {
|
||||
this.packerWorkspaceService = Objects.requireNonNull(packerWorkspaceService, "packerWorkspaceService");
|
||||
}
|
||||
|
||||
public StudioShipperPrepareResult prepare(final ProjectReference projectReference) {
|
||||
return prepare(projectReference, ignored -> { });
|
||||
}
|
||||
|
||||
public StudioShipperPrepareResult prepare(
|
||||
final ProjectReference projectReference,
|
||||
final Consumer<StudioShipperLogEntry> logSink) {
|
||||
final ProjectReference safeProjectReference = Objects.requireNonNull(projectReference, "projectReference");
|
||||
final Consumer<StudioShipperLogEntry> safeLogSink = Objects.requireNonNull(logSink, "logSink");
|
||||
final ArrayList<StudioShipperLogEntry> logs = new ArrayList<>();
|
||||
|
||||
try {
|
||||
final BuildResult buildResult = build(safeProjectReference, logs, safeLogSink);
|
||||
final ValidatePackWorkspaceResult validationResult = validatePack(safeProjectReference, logs, safeLogSink);
|
||||
if (!validationResult.canPack()) {
|
||||
emit(logs, safeLogSink, StudioShipperLogEntry.error(
|
||||
StudioShipperLogSource.PACK_VALIDATION,
|
||||
validationResult.summary()));
|
||||
logBlockedAssets(validationResult.assets(), logs, safeLogSink);
|
||||
return new StudioShipperPrepareResult(
|
||||
StudioShipperPreparationStatus.FAILED,
|
||||
logs,
|
||||
buildResult,
|
||||
validationResult,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
final PackWorkspaceResult packResult = pack(safeProjectReference, logs, safeLogSink);
|
||||
if (packResult.status() != PackerOperationStatus.SUCCESS) {
|
||||
emit(logs, safeLogSink, logFor(packResult.status(), StudioShipperLogSource.PACK, packResult.summary()));
|
||||
return new StudioShipperPrepareResult(
|
||||
StudioShipperPreparationStatus.FAILED,
|
||||
logs,
|
||||
buildResult,
|
||||
validationResult,
|
||||
packResult,
|
||||
null);
|
||||
}
|
||||
|
||||
final Path manifestPath = writeManifest(safeProjectReference, buildResult, logs, safeLogSink);
|
||||
return new StudioShipperPrepareResult(
|
||||
StudioShipperPreparationStatus.SUCCESS,
|
||||
logs,
|
||||
buildResult,
|
||||
validationResult,
|
||||
packResult,
|
||||
manifestPath);
|
||||
} catch (RuntimeException runtimeException) {
|
||||
emit(logs, safeLogSink, StudioShipperLogEntry.error(
|
||||
StudioShipperLogSource.BUILD,
|
||||
runtimeException.getMessage() == null ? runtimeException.getClass().getSimpleName() : runtimeException.getMessage()));
|
||||
return new StudioShipperPrepareResult(
|
||||
StudioShipperPreparationStatus.FAILED,
|
||||
logs,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
private BuildResult build(
|
||||
final ProjectReference projectReference,
|
||||
final List<StudioShipperLogEntry> logs,
|
||||
final Consumer<StudioShipperLogEntry> logSink) {
|
||||
final var config = new BuilderPipelineConfig(false, projectReference.rootPath().toString());
|
||||
final var ctx = BuilderPipelineContext.fromConfig(config);
|
||||
final var logAggregator = LogAggregator.with(message -> emit(logs, logSink, StudioShipperLogEntry.info(
|
||||
StudioShipperLogSource.BUILD,
|
||||
normalizeMessage(message))));
|
||||
final BuildResult result = BuilderPipelineService.INSTANCE.build(ctx, logAggregator);
|
||||
if (result.bytecodeArtifactPath() != null) {
|
||||
emit(logs, logSink, StudioShipperLogEntry.info(
|
||||
StudioShipperLogSource.BUILD,
|
||||
"Prepared program bytecode at " + result.bytecodeArtifactPath().toAbsolutePath().normalize()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private ValidatePackWorkspaceResult validatePack(
|
||||
final ProjectReference projectReference,
|
||||
final List<StudioShipperLogEntry> logs,
|
||||
final Consumer<StudioShipperLogEntry> logSink) {
|
||||
final ValidatePackWorkspaceResult result = packerWorkspaceService
|
||||
.validatePackWorkspace(new ValidatePackWorkspaceRequest(projectReference.toPackerProjectContext()));
|
||||
// keep the validation source explicit even when the downstream packer reports partial status
|
||||
emit(logs, logSink, logFor(result.status(), StudioShipperLogSource.PACK_VALIDATION, result.summary()));
|
||||
if (!result.canPack()) {
|
||||
logBlockedAssets(result.assets(), logs, logSink);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private PackWorkspaceResult pack(
|
||||
final ProjectReference projectReference,
|
||||
final List<StudioShipperLogEntry> logs,
|
||||
final Consumer<StudioShipperLogEntry> logSink) {
|
||||
final PackWorkspaceResult result = packerWorkspaceService
|
||||
.packWorkspace(new PackWorkspaceRequest(projectReference.toPackerProjectContext()));
|
||||
emit(logs, logSink, logFor(result.status(), StudioShipperLogSource.PACK, result.summary()));
|
||||
result.result().emittedArtifacts().forEach(artifact -> emit(logs, logSink, StudioShipperLogEntry.info(
|
||||
StudioShipperLogSource.PACK,
|
||||
"Emitted " + artifact.label() + " at " + artifact.path())));
|
||||
return result;
|
||||
}
|
||||
|
||||
private Path writeManifest(
|
||||
final ProjectReference projectReference,
|
||||
final BuildResult buildResult,
|
||||
final List<StudioShipperLogEntry> logs,
|
||||
final Consumer<StudioShipperLogEntry> logSink) {
|
||||
final Path buildRoot = projectReference.rootPath().resolve(BUILD_DIR).toAbsolutePath().normalize();
|
||||
final Path manifestPath = buildRoot.resolve(MANIFEST_FILE);
|
||||
try {
|
||||
Files.createDirectories(buildRoot);
|
||||
final ObjectNode manifest = createManifest(projectReference, buildResult, buildRoot);
|
||||
MAPPER.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest);
|
||||
emit(logs, logSink, StudioShipperLogEntry.info(
|
||||
StudioShipperLogSource.MANIFEST,
|
||||
"Generated manifest at " + manifestPath));
|
||||
return manifestPath;
|
||||
} catch (IOException ioException) {
|
||||
emit(logs, logSink, StudioShipperLogEntry.error(
|
||||
StudioShipperLogSource.MANIFEST,
|
||||
"Failed to write manifest: " + manifestPath));
|
||||
throw new RuntimeException("failed to write build manifest: " + manifestPath, ioException);
|
||||
}
|
||||
}
|
||||
|
||||
private ObjectNode createManifest(
|
||||
final ProjectReference projectReference,
|
||||
final BuildResult buildResult,
|
||||
final Path buildRoot) throws IOException {
|
||||
final ObjectNode manifest = MAPPER.createObjectNode();
|
||||
final JsonNode projectManifest = MAPPER.readTree(projectReference.rootPath().resolve(PROJECT_MANIFEST_FILE).toFile());
|
||||
manifest.put("magic", "PMTU");
|
||||
manifest.put("cartridge_version", 1);
|
||||
manifest.put("app_id", stableAppId(projectReference));
|
||||
manifest.put("title", projectReference.name());
|
||||
manifest.put("app_version", projectReference.version());
|
||||
manifest.put("app_mode", "game");
|
||||
manifest.set("capabilities", capabilitiesNode(buildResult));
|
||||
manifest.set("asset_table", readArrayOrEmpty(buildRoot.resolve(ASSET_TABLE_FILE)));
|
||||
manifest.set("preload", readArrayOrEmpty(buildRoot.resolve(PRELOAD_FILE)));
|
||||
if (projectManifest != null) {
|
||||
putIfTextual(manifest, "project_name", projectManifest.get("name"));
|
||||
putIfTextual(manifest, "language", projectManifest.get("language"));
|
||||
}
|
||||
return manifest;
|
||||
}
|
||||
|
||||
private ArrayNode capabilitiesNode(final BuildResult buildResult) {
|
||||
final ArrayNode capabilities = MAPPER.createArrayNode();
|
||||
final var irBackend = buildResult.compileResult().analysisSnapshot().irBackend();
|
||||
final LinkedHashSet<String> distinct = new LinkedHashSet<>(irBackend.getReservedMetadata().requiredCapabilities().asList());
|
||||
distinct.forEach(capabilities::add);
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
private JsonNode readArrayOrEmpty(final Path path) throws IOException {
|
||||
if (!Files.isRegularFile(path)) {
|
||||
return MAPPER.createArrayNode();
|
||||
}
|
||||
final JsonNode loaded = MAPPER.readTree(path.toFile());
|
||||
return loaded != null && loaded.isArray() ? loaded : MAPPER.createArrayNode();
|
||||
}
|
||||
|
||||
private int stableAppId(final ProjectReference projectReference) {
|
||||
final int hash = Objects.hash(
|
||||
projectReference.name(),
|
||||
projectReference.version(),
|
||||
projectReference.languageId(),
|
||||
projectReference.stdlibVersion());
|
||||
return hash == Integer.MIN_VALUE ? 1 : Math.max(1, Math.abs(hash));
|
||||
}
|
||||
|
||||
private void putIfTextual(
|
||||
final ObjectNode node,
|
||||
final String fieldName,
|
||||
final JsonNode value) {
|
||||
if (value != null && value.isTextual() && !value.asText().isBlank()) {
|
||||
node.put(fieldName, value.asText());
|
||||
}
|
||||
}
|
||||
|
||||
private void logBlockedAssets(
|
||||
final List<PackerPackValidationAssetDTO> assets,
|
||||
final List<StudioShipperLogEntry> logs,
|
||||
final Consumer<StudioShipperLogEntry> logSink) {
|
||||
for (final PackerPackValidationAssetDTO asset : assets) {
|
||||
emit(logs, logSink, StudioShipperLogEntry.error(
|
||||
StudioShipperLogSource.PACK_VALIDATION,
|
||||
"Blocked asset #" + asset.assetId() + " (" + asset.assetName() + ")"));
|
||||
for (final PackerDiagnosticDTO diagnostic : asset.diagnostics()) {
|
||||
emit(logs, logSink, StudioShipperLogEntry.error(
|
||||
StudioShipperLogSource.PACK_VALIDATION,
|
||||
diagnostic.message()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private StudioShipperLogEntry logFor(
|
||||
final PackerOperationStatus status,
|
||||
final StudioShipperLogSource source,
|
||||
final String message) {
|
||||
return status == PackerOperationStatus.FAILED || status == PackerOperationStatus.PARTIAL
|
||||
? StudioShipperLogEntry.error(source, message)
|
||||
: StudioShipperLogEntry.info(source, message);
|
||||
}
|
||||
|
||||
private void emit(
|
||||
final List<StudioShipperLogEntry> logs,
|
||||
final Consumer<StudioShipperLogEntry> logSink,
|
||||
final StudioShipperLogEntry entry) {
|
||||
logs.add(entry);
|
||||
logSink.accept(entry);
|
||||
}
|
||||
|
||||
private String normalizeMessage(final String message) {
|
||||
if (message == null) {
|
||||
return "(empty build log entry)";
|
||||
}
|
||||
final String trimmed = message.strip();
|
||||
return trimmed.isEmpty() ? "(empty build log entry)" : trimmed;
|
||||
}
|
||||
}
|
||||
@ -6,12 +6,9 @@ import javafx.scene.control.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import p.studio.Container;
|
||||
import p.studio.compiler.messages.BuilderPipelineConfig;
|
||||
import p.studio.compiler.models.BuilderPipelineContext;
|
||||
import p.studio.compiler.workspaces.BuilderPipelineService;
|
||||
import p.studio.projects.ProjectReference;
|
||||
import p.studio.shipper.StudioShipperService;
|
||||
import p.studio.utilities.i18n.I18n;
|
||||
import p.studio.utilities.logs.LogAggregator;
|
||||
import p.studio.workspaces.Workspace;
|
||||
import p.studio.workspaces.WorkspaceId;
|
||||
|
||||
@ -20,6 +17,7 @@ public class ShipperWorkspace extends Workspace {
|
||||
private final TextArea logs = new TextArea();
|
||||
private final Button buildButton = new Button();
|
||||
private final Button clearButton = new Button();
|
||||
private final StudioShipperService shipperService = new StudioShipperService();
|
||||
|
||||
public ShipperWorkspace(ProjectReference projectReference) {
|
||||
super(projectReference);
|
||||
@ -66,10 +64,8 @@ public class ShipperWorkspace extends Workspace {
|
||||
buildButton.getStyleClass().addAll("studio-button", "studio-button-primary");
|
||||
buildButton.setOnAction(e -> {
|
||||
logs.clear();
|
||||
final var logAggregator = LogAggregator.with(logs::appendText);
|
||||
final var config = new BuilderPipelineConfig(false, projectReference.rootPath().toString());
|
||||
final var ctx = BuilderPipelineContext.fromConfig(config);
|
||||
BuilderPipelineService.INSTANCE.build(ctx, logAggregator);
|
||||
shipperService.prepare(projectReference, entry -> logs.appendText(
|
||||
"[%s] %s%s".formatted(entry.source().name(), entry.message(), System.lineSeparator())));
|
||||
});
|
||||
|
||||
clearButton.textProperty().bind(Container.i18n().bind(I18n.WORKSPACE_SHIPPER_BUTTON_CLEAR));
|
||||
|
||||
@ -0,0 +1,99 @@
|
||||
package p.studio.shipper;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import p.packer.Packer;
|
||||
import p.studio.projects.ProjectCatalogService;
|
||||
import p.studio.projects.ProjectReference;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class StudioShipperServiceTest {
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@Test
|
||||
void prepareBuildsPackAndManifestForMainFixture() throws Exception {
|
||||
final Path projectRoot = copyFixture(fixturePath("test-projects/main"), tempDir.resolve("main-project"));
|
||||
final ProjectReference projectReference = new ProjectCatalogService(tempDir).openProject(projectRoot);
|
||||
final ArrayList<StudioShipperLogEntry> streamedLogs = new ArrayList<>();
|
||||
|
||||
final StudioShipperPrepareResult result;
|
||||
try (final Packer packer = Packer.bootstrap(new ObjectMapper(), ignored -> { })) {
|
||||
final StudioShipperService service = new StudioShipperService(packer.workspaceService());
|
||||
result = service.prepare(projectReference, streamedLogs::add);
|
||||
}
|
||||
|
||||
assertTrue(result.success());
|
||||
assertEquals(StudioShipperPreparationStatus.SUCCESS, result.status());
|
||||
assertNotNull(result.buildResult());
|
||||
assertNotNull(result.validationResult());
|
||||
assertNotNull(result.packResult());
|
||||
assertNotNull(result.manifestPath());
|
||||
assertEquals(result.logs(), streamedLogs);
|
||||
assertTrue(Files.isRegularFile(projectRoot.resolve("build").resolve("program.pbx")));
|
||||
assertTrue(Files.isRegularFile(projectRoot.resolve("build").resolve("assets.pa")));
|
||||
assertTrue(Files.isRegularFile(projectRoot.resolve("build").resolve("manifest.json")));
|
||||
assertTrue(result.logs().stream().anyMatch(entry -> entry.source() == StudioShipperLogSource.BUILD));
|
||||
assertTrue(result.logs().stream().anyMatch(entry -> entry.source() == StudioShipperLogSource.PACK_VALIDATION));
|
||||
assertTrue(result.logs().stream().anyMatch(entry -> entry.source() == StudioShipperLogSource.PACK));
|
||||
assertTrue(result.logs().stream().anyMatch(entry -> entry.source() == StudioShipperLogSource.MANIFEST));
|
||||
final String manifestJson = Files.readString(result.manifestPath());
|
||||
assertTrue(manifestJson.contains("\"magic\""));
|
||||
assertTrue(manifestJson.contains("\"capabilities\""));
|
||||
assertTrue(manifestJson.contains("\"asset_table\""));
|
||||
assertTrue(manifestJson.contains("\"preload\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void prepareManifestPreservesCompilerCapabilities() throws Exception {
|
||||
final Path projectRoot = copyFixture(fixturePath("test-projects/main"), tempDir.resolve("main-capabilities"));
|
||||
final ProjectReference projectReference = new ProjectCatalogService(tempDir).openProject(projectRoot);
|
||||
final StudioShipperPrepareResult result;
|
||||
try (final Packer packer = Packer.bootstrap(new ObjectMapper(), ignored -> { })) {
|
||||
final StudioShipperService service = new StudioShipperService(packer.workspaceService());
|
||||
result = service.prepare(projectReference);
|
||||
}
|
||||
|
||||
assertTrue(result.success());
|
||||
final String manifestJson = Files.readString(result.manifestPath());
|
||||
assertTrue(manifestJson.contains("\"log\""));
|
||||
assertTrue(manifestJson.contains("\"gfx\""));
|
||||
assertTrue(manifestJson.contains("\"asset\""));
|
||||
}
|
||||
|
||||
private Path copyFixture(final Path sourceRoot, final Path targetRoot) throws IOException {
|
||||
Files.walk(sourceRoot).forEach(source -> {
|
||||
try {
|
||||
final Path target = targetRoot.resolve(sourceRoot.relativize(source).toString());
|
||||
if (Files.isDirectory(source)) {
|
||||
Files.createDirectories(target);
|
||||
} else {
|
||||
Files.createDirectories(target.getParent());
|
||||
Files.copy(source, target);
|
||||
}
|
||||
} catch (IOException ioException) {
|
||||
throw new RuntimeException(ioException);
|
||||
}
|
||||
});
|
||||
return targetRoot;
|
||||
}
|
||||
|
||||
private Path fixturePath(final String relativePath) {
|
||||
final Path direct = Path.of(relativePath).toAbsolutePath().normalize();
|
||||
if (Files.exists(direct)) {
|
||||
return direct;
|
||||
}
|
||||
final Path parent = Path.of("..").resolve(relativePath).toAbsolutePath().normalize();
|
||||
if (Files.exists(parent)) {
|
||||
return parent;
|
||||
}
|
||||
throw new IllegalArgumentException("fixture not found: " + relativePath);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user