diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 6d86f3bbe..fe36dce62 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -50,7 +50,7 @@ val jsOutputPackageDir = jsOutputDir.map { it.dir("org/teavm/backend/wasm") } val jsInputDir = layout.projectDirectory.dir("src/main/js/wasm-gc-runtime") val jsInput = jsInputDir.file("runtime.js") -fun registerRuntimeTasks(taskName: String, wrapperType: String, outputName: String) { +fun registerRuntimeTasks(taskName: String, wrapperType: String, outputName: String, module: Boolean) { val generateTask by tasks.register("generate${taskName}Runtime") { dependsOn(tasks.npmInstall) val wrapperFile = jsInputDir.file(wrapperType) @@ -80,7 +80,9 @@ fun registerRuntimeTasks(taskName: String, wrapperType: String, outputName: Stri args.addAll(provider { listOf( "--", - "-m", "--module", "--toplevel", + "-m", "--toplevel", + *(if (module) arrayOf("--module") else emptyArray()), + "--mangle", "reserved=['TeaVM']", inputFiles.singleFile.absolutePath, "-o", outputFile.get().asFile.absolutePath ) @@ -93,8 +95,8 @@ fun registerRuntimeTasks(taskName: String, wrapperType: String, outputName: Stri } } -registerRuntimeTasks("Simple", "simple-wrapper.js", "wasm-gc-runtime") -registerRuntimeTasks("Module", "module-wrapper.js", "wasm-gc-module-runtime") +registerRuntimeTasks("Simple", "simple-wrapper.js", "wasm-gc-runtime", module = false) +registerRuntimeTasks("Module", "module-wrapper.js", "wasm-gc-module-runtime", module = true) teavmPublish { artifactId = "teavm-core" diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java index 08d2282e3..54b30b729 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -371,7 +371,7 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { null, null, debugLines, null, WasmBinaryStatsCollector.EMPTY); optimizeIndexes(module); module.prepareForRendering(); - if (debugLocation == WasmDebugInfoLocation.EMBEDDED) { + if (debugLocation == WasmDebugInfoLocation.EMBEDDED && debugInfo) { binaryRenderer.render(module, debugInfoBuilder::build); } else { binaryRenderer.render(module); @@ -380,7 +380,7 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { try (var output = buildTarget.createResource(outputName)) { output.write(data); } - if (debugLocation == WasmDebugInfoLocation.EXTERNAL) { + if (debugLocation == WasmDebugInfoLocation.EXTERNAL && debugInfo) { var debugInfoData = ExternalDebugFile.write(debugInfoBuilder.build()); if (debugInfoData != null) { try (var output = buildTarget.createResource(outputName + ".teadbg")) { diff --git a/samples/benchmark/build.gradle.kts b/samples/benchmark/build.gradle.kts index 6144fa866..f03677dbf 100644 --- a/samples/benchmark/build.gradle.kts +++ b/samples/benchmark/build.gradle.kts @@ -54,7 +54,7 @@ teavm { wasmGC { addedToWebApp = true mainClass = "org.teavm.samples.benchmark.teavm.BenchmarkStarter" - sourceMap = true + debugInformation = true } wasm { addedToWebApp = true diff --git a/samples/benchmark/src/main/webapp/teavm-wasm-gc.html b/samples/benchmark/src/main/webapp/teavm-wasm-gc.html index 5676d49fe..e14770eaa 100644 --- a/samples/benchmark/src/main/webapp/teavm-wasm-gc.html +++ b/samples/benchmark/src/main/webapp/teavm-wasm-gc.html @@ -22,7 +22,11 @@ diff --git a/tools/deobfuscator-wasm-gc/build.gradle.kts b/tools/deobfuscator-wasm-gc/build.gradle.kts index aae25913c..b553cd05c 100644 --- a/tools/deobfuscator-wasm-gc/build.gradle.kts +++ b/tools/deobfuscator-wasm-gc/build.gradle.kts @@ -16,9 +16,10 @@ plugins { `java-library` + `teavm-publish` } -description = "JavaScript deobfuscator" +description = "WebAssembly deobfuscator" configurations { val teavmCompile = create("teavmCompile") @@ -42,21 +43,18 @@ val generateWasm by tasks.register("generateWasm") { mainClass = "org.teavm.tooling.deobfuscate.wasmgc.Compiler" args( "org.teavm.tooling.deobfuscate.wasmgc.DeobfuscatorFactory", - layout.buildDirectory.dir("teavm").get().asFile.absolutePath, + layout.buildDirectory.dir("teavm/org/teavm/backend/wasm/").get().asFile.absolutePath, "deobfuscator.wasm" ) } -val zipWithWasm by tasks.register("zipWithWasm") { +tasks.withType { dependsOn(generateWasm) - archiveClassifier = "wasm" - from(layout.buildDirectory.dir("teavm"), layout.buildDirectory.dir("teavm-lib")) - include("*.wasm") - entryCompression = ZipEntryCompression.DEFLATED + from(layout.buildDirectory.dir("teavm")) + include("**/*.wasm") + includeEmptyDirs = false } -tasks.assemble.configure { - dependsOn(zipWithWasm) -} - -artifacts.add("wasm", zipWithWasm) \ No newline at end of file +teavmPublish { + artifactId = "teavm-wasm-gc-deobfuscator" +} \ No newline at end of file diff --git a/tools/gradle/build.gradle.kts b/tools/gradle/build.gradle.kts index 19388ff54..6ecc3b478 100644 --- a/tools/gradle/build.gradle.kts +++ b/tools/gradle/build.gradle.kts @@ -26,6 +26,7 @@ description = "TeaVM Gradle plugin" dependencies { implementation(project(":core")) implementation(project(":tools:core")) + implementation(project(":tools:deobfuscator-wasm-gc")) } gradlePlugin { diff --git a/tools/gradle/src/main/java/org/teavm/gradle/TeaVMExtensionImpl.java b/tools/gradle/src/main/java/org/teavm/gradle/TeaVMExtensionImpl.java index e494c7b3e..d2cf06765 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/TeaVMExtensionImpl.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/TeaVMExtensionImpl.java @@ -80,7 +80,7 @@ class TeaVMExtensionImpl extends TeaVMBaseExtensionImpl implements TeaVMExtensio .orElse(OptimizationLevel.BALANCED)); js.getSourceFilePolicy().convention(property("js.sourceFilePolicy") .map(SourceFilePolicy::valueOf) - .orElse(SourceFilePolicy.DO_NOTHING)); + .orElse(SourceFilePolicy.LINK_LOCAL_FILES)); js.getDevServer().getStackDeobfuscated().convention(property("js.devServer.stackDeobfuscated") .map(Boolean::parseBoolean)); js.getDevServer().getIndicator().convention(property("js.devServer.indicator").map(Boolean::parseBoolean)); @@ -120,6 +120,11 @@ class TeaVMExtensionImpl extends TeaVMBaseExtensionImpl implements TeaVMExtensio wasmGC.getDebugInfoLevel().convention(property("wasm-gc.debugInformation.level") .map(v -> WasmDebugInfoLevel.valueOf(v.toUpperCase())).orElse(WasmDebugInfoLevel.DEOBFUSCATION)); wasmGC.getSourceMap().convention(property("wasm-gc.sourceMap").map(Boolean::parseBoolean).orElse(false)); + wasmGC.getSourceFilePolicy().convention(property("wasm-gc.sourceFilePolicy") + .map(SourceFilePolicy::valueOf) + .orElse(SourceFilePolicy.LINK_LOCAL_FILES)); + wasmGC.getModularRuntime().convention(property("wasm-gc.modularRuntime") + .map(Boolean::parseBoolean).orElse(false)); } private void setupWasiDefaults() { diff --git a/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java b/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java index 263f93242..c4f083fcc 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java @@ -57,6 +57,7 @@ public class TeaVMPlugin implements Plugin { public static final String WASM_TASK_NAME = "generateWasm"; public static final String WASI_TASK_NAME = "generateWasi"; public static final String WASM_GC_TASK_NAME = "generateWasmGC"; + public static final String BUILD_WASM_GC_TASK_NAME = "buildWasmGC"; public static final String WASM_GC_COPY_RUNTIME_TASK_NAME = "copyWasmGCRuntime"; public static final String WASM_GC_DISASSEMBLY_TASK_NAME = "disasmWasmGC"; public static final String C_TASK_NAME = "generateC"; @@ -219,6 +220,9 @@ public class TeaVMPlugin implements Plugin { private void registerWasmGCTask(Project project, Configuration configuration) { var extension = project.getExtensions().getByType(TeaVMExtension.class); + var buildTask = project.getTasks().create(BUILD_WASM_GC_TASK_NAME, task -> { + task.setGroup(TASK_GROUP); + }); project.getTasks().create(WASM_GC_TASK_NAME, GenerateWasmGCTask.class, task -> { var wasmGC = extension.getWasmGC(); applyToTask(wasmGC, task, configuration); @@ -226,6 +230,9 @@ public class TeaVMPlugin implements Plugin { task.getObfuscated().convention(wasmGC.getObfuscated()); task.getStrict().convention(wasmGC.getStrict()); task.getSourceMap().convention(wasmGC.getSourceMap()); + task.getSourceFilePolicy().convention(wasmGC.getSourceFilePolicy()); + setupSources(task.getSourceFiles(), project); + buildTask.dependsOn(task); }); project.getTasks().create(WASM_GC_COPY_RUNTIME_TASK_NAME, CopyWasmGCRuntimeTask.class, task -> { task.setGroup(TASK_GROUP); @@ -234,6 +241,15 @@ public class TeaVMPlugin implements Plugin { task.getOutputFile().convention(extension.getWasmGC().getOutputDir() .flatMap(d -> d.dir(extension.getWasmGC().getRelativePathInOutputDir())) .flatMap(d -> d.file(fileName))); + task.getDeobfuscator().convention(extension.getWasmGC().getDebugInformation()); + var deobfuscatorFileName = extension.getWasmGC().getTargetFileName() + .map(x -> x + "-deobfuscator.wasm"); + task.getDeobfuscatorOutputFile().convention(extension.getWasmGC().getOutputDir() + .flatMap(d -> d.dir(extension.getWasmGC().getRelativePathInOutputDir())) + .flatMap(d -> d.file(deobfuscatorFileName))); + task.getModular().convention(extension.getWasmGC().getModularRuntime()); + task.getObfuscated().convention(extension.getWasmGC().getObfuscated()); + buildTask.dependsOn(task); }); project.getTasks().create(WASM_GC_DISASSEMBLY_TASK_NAME, DisasmWebAssemblyTask.class, task -> { task.setGroup(TASK_GROUP); @@ -251,7 +267,9 @@ public class TeaVMPlugin implements Plugin { }); task.getOutputFile().convention(project.getLayout().dir(genTask.getOutputDir()) .flatMap(d -> d.file(fileName))); + buildTask.dependsOn(task); }); + } private void registerCTask(Project project, Configuration configuration) { @@ -301,9 +319,7 @@ public class TeaVMPlugin implements Plugin { })); } if (wasmGCAddedToWebApp) { - task.dependsOn(project.getTasks().named(WASM_GC_TASK_NAME)); - task.dependsOn(project.getTasks().named(WASM_GC_COPY_RUNTIME_TASK_NAME)); - task.dependsOn(project.getTasks().named(WASM_GC_DISASSEMBLY_TASK_NAME)); + task.dependsOn(project.getTasks().named(BUILD_WASM_GC_TASK_NAME)); var outDir = extension.getWasmGC().getOutputDir(); var relPath = extension.getWasmGC().getRelativePathInOutputDir(); task.with(project.copySpec(spec -> { diff --git a/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMWasmGCConfiguration.java b/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMWasmGCConfiguration.java index f4e73bdba..328d85643 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMWasmGCConfiguration.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMWasmGCConfiguration.java @@ -33,4 +33,8 @@ public interface TeaVMWasmGCConfiguration extends TeaVMCommonConfiguration, TeaV Property getDebugInfoLevel(); Property getSourceMap(); + + Property getSourceFilePolicy(); + + Property getModularRuntime(); } diff --git a/tools/gradle/src/main/java/org/teavm/gradle/tasks/CopyWasmGCRuntimeTask.java b/tools/gradle/src/main/java/org/teavm/gradle/tasks/CopyWasmGCRuntimeTask.java index 70adf9946..0e9eb11c4 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/tasks/CopyWasmGCRuntimeTask.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/tasks/CopyWasmGCRuntimeTask.java @@ -20,20 +20,56 @@ import java.nio.file.Files; import java.nio.file.StandardCopyOption; import org.gradle.api.DefaultTask; import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; public abstract class CopyWasmGCRuntimeTask extends DefaultTask { + public CopyWasmGCRuntimeTask() { + getModular().convention(false); + getObfuscated().convention(true); + getDeobfuscator().convention(false); + } + + @Input + public abstract Property getModular(); + + @Input + public abstract Property getObfuscated(); + @OutputFile public abstract RegularFileProperty getOutputFile(); + @Input + public abstract Property getDeobfuscator(); + + @OutputFile + public abstract RegularFileProperty getDeobfuscatorOutputFile(); + @TaskAction public void copyRuntime() throws IOException { - var resourceName = "org/teavm/backend/wasm/wasm-gc-runtime.min.js"; + var name = new StringBuilder("wasm-gc"); + if (getModular().get()) { + name.append("-modular"); + } + name.append("-runtime"); + if (getObfuscated().get()) { + name.append(".min"); + } + var resourceName = "org/teavm/backend/wasm/" + name + ".js"; var classLoader = CopyWasmGCRuntimeTask.class.getClassLoader(); var output = getOutputFile().get().getAsFile(); try (var input = classLoader.getResourceAsStream(resourceName)) { Files.copy(input, output.toPath(), StandardCopyOption.REPLACE_EXISTING); } + + if (getDeobfuscator().get()) { + resourceName = "org/teavm/backend/wasm/deobfuscator.wasm"; + output = getDeobfuscatorOutputFile().get().getAsFile(); + try (var input = classLoader.getResourceAsStream(resourceName)) { + Files.copy(input, output.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + } } } diff --git a/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateJavaScriptTask.java b/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateJavaScriptTask.java index fe74365d3..3922a0b87 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateJavaScriptTask.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateJavaScriptTask.java @@ -22,7 +22,6 @@ import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Optional; import org.teavm.gradle.api.JSModuleType; import org.teavm.gradle.api.SourceFilePolicy; -import org.teavm.tooling.TeaVMSourceFilePolicy; import org.teavm.tooling.TeaVMTargetType; import org.teavm.tooling.builder.BuildStrategy; @@ -32,7 +31,7 @@ public abstract class GenerateJavaScriptTask extends TeaVMTask { getStrict().convention(false); getModuleType().convention(JSModuleType.UMD); getSourceMap().convention(false); - getSourceFilePolicy().convention(SourceFilePolicy.DO_NOTHING); + getSourceFilePolicy().convention(SourceFilePolicy.LINK_LOCAL_FILES); getEntryPointName().convention("main"); } @@ -91,25 +90,7 @@ public abstract class GenerateJavaScriptTask extends TeaVMTask { } builder.setSourceMapsFileGenerated(getSourceMap().get()); builder.setEntryPointName(getEntryPointName().get()); - for (var file : getSourceFiles()) { - if (file.isFile()) { - if (file.getName().endsWith(".jar") || file.getName().endsWith(".zip")) { - builder.addSourcesJar(file.getAbsolutePath()); - } - } else if (file.isDirectory()) { - builder.addSourcesDirectory(file.getAbsolutePath()); - } - } - switch (getSourceFilePolicy().get()) { - case DO_NOTHING: - builder.setSourceFilePolicy(TeaVMSourceFilePolicy.DO_NOTHING); - break; - case COPY: - builder.setSourceFilePolicy(TeaVMSourceFilePolicy.COPY); - break; - case LINK_LOCAL_FILES: - builder.setSourceFilePolicy(TeaVMSourceFilePolicy.LINK_LOCAL_FILES); - break; - } + TaskUtils.applySourceFiles(getSourceFiles(), builder); + TaskUtils.applySourceFilePolicy(getSourceFilePolicy(), builder); } } diff --git a/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasmGCTask.java b/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasmGCTask.java index e524e3d75..d91c3189b 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasmGCTask.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasmGCTask.java @@ -15,8 +15,12 @@ */ package org.teavm.gradle.tasks; +import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Optional; +import org.teavm.gradle.api.SourceFilePolicy; import org.teavm.gradle.api.WasmDebugInfoLevel; import org.teavm.gradle.api.WasmDebugInfoLocation; import org.teavm.tooling.TeaVMTargetType; @@ -26,10 +30,10 @@ public abstract class GenerateWasmGCTask extends TeaVMTask { public GenerateWasmGCTask() { getStrict().convention(true); getObfuscated().convention(true); - getDebugInfo().convention(true); getDebugInfoLevel().convention(WasmDebugInfoLevel.DEOBFUSCATION); getDebugInfoLocation().convention(WasmDebugInfoLocation.EXTERNAL); getSourceMap().convention(false); + getSourceFilePolicy().convention(SourceFilePolicy.LINK_LOCAL_FILES); } @Input @@ -38,9 +42,6 @@ public abstract class GenerateWasmGCTask extends TeaVMTask { @Input public abstract Property getObfuscated(); - @Input - public abstract Property getDebugInfo(); - @Input public abstract Property getDebugInfoLevel(); @@ -50,11 +51,18 @@ public abstract class GenerateWasmGCTask extends TeaVMTask { @Input public abstract Property getSourceMap(); + @InputFiles + public abstract ConfigurableFileCollection getSourceFiles(); + + @Input + @Optional + public abstract Property getSourceFilePolicy(); + @Override protected void setupBuilder(BuildStrategy builder) { builder.setStrict(getStrict().get()); builder.setObfuscated(getObfuscated().get()); - builder.setDebugInformationGenerated(getDebugInfo().get()); + builder.setDebugInformationGenerated(getDebugInformation().get()); builder.setSourceMapsFileGenerated(getSourceMap().get()); switch (getDebugInfoLevel().get()) { case FULL: @@ -73,5 +81,7 @@ public abstract class GenerateWasmGCTask extends TeaVMTask { break; } builder.setTargetType(TeaVMTargetType.WEBASSEMBLY_GC); + TaskUtils.applySourceFiles(getSourceFiles(), builder); + TaskUtils.applySourceFilePolicy(getSourceFilePolicy(), builder); } } diff --git a/tools/gradle/src/main/java/org/teavm/gradle/tasks/TaskUtils.java b/tools/gradle/src/main/java/org/teavm/gradle/tasks/TaskUtils.java new file mode 100644 index 000000000..507968732 --- /dev/null +++ b/tools/gradle/src/main/java/org/teavm/gradle/tasks/TaskUtils.java @@ -0,0 +1,53 @@ +/* + * Copyright 2024 konsoletyper. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.gradle.tasks; + +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.provider.Property; +import org.teavm.gradle.api.SourceFilePolicy; +import org.teavm.tooling.TeaVMSourceFilePolicy; +import org.teavm.tooling.builder.BuildStrategy; + +final class TaskUtils { + private TaskUtils() { + } + + static void applySourceFiles(ConfigurableFileCollection sourceFiles, BuildStrategy builder) { + for (var file : sourceFiles) { + if (file.isFile()) { + if (file.getName().endsWith(".jar") || file.getName().endsWith(".zip")) { + builder.addSourcesJar(file.getAbsolutePath()); + } + } else if (file.isDirectory()) { + builder.addSourcesDirectory(file.getAbsolutePath()); + } + } + } + + static void applySourceFilePolicy(Property policy, BuildStrategy builder) { + switch (policy.get()) { + case DO_NOTHING: + builder.setSourceFilePolicy(TeaVMSourceFilePolicy.DO_NOTHING); + break; + case COPY: + builder.setSourceFilePolicy(TeaVMSourceFilePolicy.COPY); + break; + case LINK_LOCAL_FILES: + builder.setSourceFilePolicy(TeaVMSourceFilePolicy.LINK_LOCAL_FILES); + break; + } + } +} diff --git a/tools/junit/build.gradle.kts b/tools/junit/build.gradle.kts index 2d0074643..ecb99c6df 100644 --- a/tools/junit/build.gradle.kts +++ b/tools/junit/build.gradle.kts @@ -31,7 +31,7 @@ dependencies { implementation(project(":core")) implementation(project(":tools:core")) implementation(project(":tools:browser-runner")) - runtimeOnly(project(":tools:deobfuscator-wasm-gc", "wasm")) + runtimeOnly(project(":tools:deobfuscator-wasm-gc")) } teavmPublish { diff --git a/tools/junit/src/main/java/org/teavm/junit/WebAssemblyGCPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/WebAssemblyGCPlatformSupport.java index a83e29a11..6901b0705 100644 --- a/tools/junit/src/main/java/org/teavm/junit/WebAssemblyGCPlatformSupport.java +++ b/tools/junit/src/main/java/org/teavm/junit/WebAssemblyGCPlatformSupport.java @@ -169,7 +169,7 @@ class WebAssemblyGCPlatformSupport extends TestPlatformSupport { getExtension() + "-deobfuscator.wasm"); try { TestUtil.resourceToFile("org/teavm/backend/wasm/wasm-gc-runtime.js", testPath, Map.of()); - TestUtil.resourceToFile("deobfuscator.wasm", testDeobfuscatorPath, Map.of()); + TestUtil.resourceToFile("org/teavm/backend/wasm/deobfuscator.wasm", testDeobfuscatorPath, Map.of()); } catch (IOException e) { throw new RuntimeException(e); }