diff --git a/build.sbt b/build.sbt index 5077a57..cf04758 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ import sbtrelease.ReleasePlugin.autoImport.ReleaseTransformations.* -ThisBuild / scalaVersion := "2.13.13" +ThisBuild / scalaVersion := "2.13.14" lazy val root = (project in file(".")) .settings( @@ -21,8 +21,11 @@ lazy val root = (project in file(".")) "org.scalatest" %% "scalatest" % "3.2.18" % Test, ), libraryDependencies ++= Seq( - "com.github.alexarchambault" %% "case-app" % "2.1.0-M26" + "com.github.alexarchambault" %% "case-app" % "2.1.0-M29" ), +// libraryDependencies ++= Seq( +// "org.graalvm.buildtools" % "graalvm-reachability-metadata" % "0.10.2" +// ), libraryDependencies ++= Seq( "io.circe" %% "circe-core", "io.circe" %% "circe-generic", @@ -63,6 +66,7 @@ lazy val root = (project in file(".")) graalVMNativeImageOptions ++= Seq( "--no-fallback", "-H:+ReportExceptionStackTraces", + "-H:+UnlockExperimentalVMOptions", "--report-unsupported-elements-at-runtime", "--enable-https", "--enable-http", diff --git a/nixtest.sh b/nixtest.sh new file mode 100755 index 0000000..d86c9cd --- /dev/null +++ b/nixtest.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +nix-shell --pure ./shell.nix --run ./test.sh diff --git a/project/build.properties b/project/build.properties index 58c9814..136f452 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.9.2 +sbt.version = 1.10.1 diff --git a/project/plugins.sbt b/project/plugins.sbt index e7d6a8a..676cdb3 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,3 @@ //addSbtPlugin("org.jetbrains.scala" % "sbt-ide-settings" % "1.1.1") -addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.0") +addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.4") addSbtPlugin("io.7mind.izumi.sbt" % "sbt-izumi" % "0.0.101") diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..c8dfa84 --- /dev/null +++ b/shell.nix @@ -0,0 +1,5 @@ +{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/24.05.tar.gz") { } }: + +pkgs.mkShell { + nativeBuildInputs = with pkgs.buildPackages; [ ncurses graalvm-ce sbt dotnet-sdk_7 ]; +} diff --git a/src/main/resources/META-INF/native-image/reflect-config.json b/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 0000000..93e46a8 --- /dev/null +++ b/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,42 @@ +[ +{ + "name":"[Lizumi.distage.model.plan.ExecutableOp;" +}, +{ + "name":"[Ljava.lang.String;" +}, +{ + "name":"com.intellij.rt.execution.application.AppMainV2$Agent", + "methods":[{"name":"premain","parameterTypes":["java.lang.String","java.lang.instrument.Instrumentation"] }] +}, +{ + "name":"izumi.distage.provisioning.strategies.dynamicproxy.DynamicProxyProvider$", + "fields":[{"name":"MODULE$"}] +}, +{ + "name":"java.lang.ClassValue" +}, +{ + "name":"java.lang.String" +}, +{ + "name":"java.lang.invoke.VarHandle", + "methods":[{"name":"releaseFence","parameterTypes":[] }] +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"scala.Symbol", + "methods":[{"name":"apply","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"sun.security.provider.SHA2$SHA256", + "methods":[{"name":"","parameterTypes":[] }] +} +] diff --git a/src/main/scala/io/septimalmind/baboon/Baboon.scala b/src/main/scala/io/septimalmind/baboon/Baboon.scala index 018e2fe..c622e04 100644 --- a/src/main/scala/io/septimalmind/baboon/Baboon.scala +++ b/src/main/scala/io/septimalmind/baboon/Baboon.scala @@ -33,6 +33,7 @@ case class Options( csExcludeGlobalUsings: Option[Boolean], omitMostRecentVersionSuffixFromPaths: Option[Boolean], omitMostRecentVersionSuffixFromNamespaces: Option[Boolean], + csUseCompactAdtForm: Option[Boolean], ) sealed trait RuntimeGenOpt @@ -71,7 +72,7 @@ object Baboon { !opts.csExcludeGlobalUsings.getOrElse(false), opts.omitMostRecentVersionSuffixFromPaths.getOrElse(true), opts.omitMostRecentVersionSuffixFromNamespaces.getOrElse(true), - csUseCompactAdtForm = true, + opts.csUseCompactAdtForm.getOrElse(true), ) Injector .NoCycles() diff --git a/src/main/scala/io/septimalmind/baboon/translator/csharp/CSNSJsonCodecGenerator.scala b/src/main/scala/io/septimalmind/baboon/translator/csharp/CSNSJsonCodecGenerator.scala index 6f9ce17..bcf5659 100644 --- a/src/main/scala/io/septimalmind/baboon/translator/csharp/CSNSJsonCodecGenerator.scala +++ b/src/main/scala/io/septimalmind/baboon/translator/csharp/CSNSJsonCodecGenerator.scala @@ -8,12 +8,15 @@ import izumi.fundamentals.platform.strings.TextTree.* class CSNSJsonCodecGenerator(trans: CSTypeTranslator, tools: CSDefnTools) extends CSCodecTranslator { + val csWrappedAdtBranchCodecs = true + override def translate(defn: DomainMember.User, csRef: CSValue.CSType, srcRef: CSValue.CSType, domain: Domain, evo: BaboonEvolution, ): TextTree[CSValue] = { + val version = domain.version val (enc, dec) = defn.defn match { case d: Typedef.Dto => @@ -135,21 +138,43 @@ class CSNSJsonCodecGenerator(trans: CSTypeTranslator, tools: CSDefnTools) ) } + private def wrapAdtBranchEncoder( + branchName: String, + tree: TextTree[CSValue] + ): TextTree[CSValue] = { + q"""new $nsJObject(new $nsJProperty("$branchName", $tree))""" + } + private def genAdtBodies( name: CSValue.CSType, a: Typedef.Adt - ): (TextTree[CSValue.CSType], TextTree[Nothing]) = { + ): (TextTree[CSValue], TextTree[Nothing]) = { + val branches = a.members.toList.map { m => val branchNs = q"${trans.adtNsName(a.id)}" val branchName = m.name.name val fqBranch = q"$branchNs.$branchName" + val routedBranchEncoder = + q"${fqBranch}_JsonCodec.Instance.Encode(($fqBranch)value)" + + val branchEncoder = if (csWrappedAdtBranchCodecs) { + routedBranchEncoder + } else { + wrapAdtBranchEncoder(branchName, routedBranchEncoder) + } + + val branchValue = if (csWrappedAdtBranchCodecs) { + q"wire" + } else { + q"head.Value" + } (q"""if (value is $fqBranch) |{ - | return new $nsJObject(new $nsJProperty("$branchName", ${fqBranch}_JsonCodec.Instance.Encode(($fqBranch)value))); + | return $branchEncoder; |}""".stripMargin, q"""if (head.Name == "$branchName") |{ - | return ${fqBranch}_JsonCodec.Instance.Decode(head.Value); + | return ${fqBranch}_JsonCodec.Instance.Decode($branchValue); |}""".stripMargin) } @@ -206,19 +231,37 @@ class CSNSJsonCodecGenerator(trans: CSTypeTranslator, tools: CSDefnTools) ) } - (q"""return new $nsJObject( - |${fields.map(_._1).join(",\n").shift(4)} - |);""".stripMargin, q"""var asObject = wire.Value(); - | - |if (asObject == null) - |{ - | throw new ArgumentException($$"Cannot decode {wire} to ${name.name}: object expected"); - |} - | - |return new $name( - |${fields.map(_._2).join(",\n").shift(4)} - |); - """.stripMargin) + val mainEnc = q"""new $nsJObject( + |${fields.map(_._1).join(",\n").shift(4)} + |)""".stripMargin + + val fullEnc = d.id.owner match { + case Owner.Adt(_) if csWrappedAdtBranchCodecs => + wrapAdtBranchEncoder(d.id.name.name, mainEnc) + case _ => mainEnc + } + + val encBody = q"""return $fullEnc;""" + + val fullDec = d.id.owner match { + case Owner.Adt(_) if csWrappedAdtBranchCodecs => + q"wire.Value().Properties().First().Value.Value()" + case _ => q"wire.Value()" + } + + val decBody = q"""var asObject = $fullDec; + | + |if (asObject == null) + |{ + | throw new ArgumentException($$"Cannot decode {wire} to ${name.name}: object expected"); + |} + | + |return new $name( + |${fields.map(_._2).join(",\n").shift(4)} + |); + """.stripMargin + + (encBody, decBody) } private def mkEncoder(tpe: TypeRef, diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..e3f0a26 --- /dev/null +++ b/test.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -x +set -e + +sbt GraalVMNativeImage/packageBin + +target/graalvm-native-image/baboon \ + --model-dir ./src/test/resources/baboon/ \ + --output ./test/cs-stub/ConversionsTest/Generated \ + --test-output ./test/cs-stub/ConversionsTest/Generated +pushd . + +cd ./test/cs-stub +dotnet build +dotnet test ConversionsTest/ConversionsTest.csproj + +popd \ No newline at end of file diff --git a/test/cs-stub/.gitignore b/test/cs-stub/.gitignore index b59f578..c948cb8 100644 --- a/test/cs-stub/.gitignore +++ b/test/cs-stub/.gitignore @@ -1,5 +1,7 @@ Generated/ +global.json + bin/ obj/ /packages/