Skip to content

Commit

Permalink
language-support/scala: add support for specifying dependency roots e…
Browse files Browse the repository at this point in the history
…xplicitly (#1210)

* language-support/scala: add support for specifying roots explicitly

By default, code-gen will use all templates in the given package as roots, and
generate code for all types used. This patch adds the ability to specify roots
explicitly via a set of regex.es to match the (fully qualified) template names.

This is an experiment, tests and UI need to be adjusted later.

* scala codegen rootfilter: move filtering to envIFace handling

* doc for --root option

* scalafmt

* test filterTemplatesBy

* release note for --root Scala codegen option
  • Loading branch information
jberthold-da authored Jun 12, 2019
1 parent 2b5c25f commit b0b3e29
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 8 deletions.
6 changes: 6 additions & 0 deletions docs/source/support/release-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ Sandbox
- Introduced a new API for party management.
See `#1312 <https://github.com/digital-asset/daml/issues/1312>`__.

Scala bindings
~~~~~~~~~~~~~~

- New `--root` command-line option for limiting what templates are selected for codegen.
See `#1210 <https://github.com/digital-asset/daml/pull/1210>`__.

.. _release-0-12-24:

0.12.24 - 2019-06-06
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ final case class Conf(
darFiles: Map[Path, Option[String]] = Map(),
outputDirectory: Path,
decoderPkgAndClass: Option[(String, String)] = None,
verbosity: Level = Level.ERROR
verbosity: Level = Level.ERROR,
roots: List[String] = Nil
)

object Conf {
Expand Down Expand Up @@ -57,6 +58,12 @@ object Conf {
.action((l, c) => c.copy(verbosity = l))
.text("Verbosity between 0 (only show errors) and 4 (show all messages) -- defaults to 0")

opt[String]('r', "root")(Read.stringRead)
.unbounded()
.action((rexp, c) => c.copy(roots = rexp :: c.roots))
.text(
"Regular expression for fully-qualified names of templates to generate -- defaults to .*")

help("help").text("This help text")

}
Expand Down
1 change: 1 addition & 0 deletions language-support/scala/codegen/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,6 @@ da_scala_test_suite(
"//daml-lf/archive:daml_lf_archive_scala",
"//daml-lf/data",
"//daml-lf/interface",
"//daml-lf/transaction-scalacheck",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import iface.{Type => _, _}
import com.digitalasset.daml.lf.iface.reader.{Errors, InterfaceReader}
import java.io._

import scala.collection.breakOut
import com.digitalasset.codegen.dependencygraph._
import com.digitalasset.codegen.exception.PackageInterfaceException
import com.digitalasset.daml.lf.data.ImmArray.ImmArraySeq
Expand All @@ -29,7 +28,9 @@ import scalaz.syntax.std.option._
import scalaz.syntax.bind._
import scalaz.syntax.traverse1._

import scala.collection.breakOut
import scala.util.{Failure, Success}
import scala.util.matching.Regex

object CodeGen {

Expand Down Expand Up @@ -59,12 +60,17 @@ object CodeGen {
@throws[PackageInterfaceException](
cause = "either decoding a package from a file or extracting" +
" the package interface failed")
def generateCode(files: List[File], packageName: String, outputDir: File, mode: Mode): Unit =
def generateCode(
files: List[File],
packageName: String,
outputDir: File,
mode: Mode,
roots: Seq[String] = Seq()): Unit =
files match {
case Nil =>
throw PackageInterfaceException("Expected at least one DAR or DALF input file.")
case f :: fs =>
generateCodeSafe(NonEmptyList(f, fs: _*), packageName, outputDir, mode)
generateCodeSafe(NonEmptyList(f, fs: _*), packageName, outputDir, mode, roots)
.fold(es => throw PackageInterfaceException(formatErrors(es)), identity)
}

Expand All @@ -75,9 +81,11 @@ object CodeGen {
files: NonEmptyList[File],
packageName: String,
outputDir: File,
mode: Mode): ValidationNel[String, Unit] =
mode: Mode,
roots: Seq[String]): ValidationNel[String, Unit] =
decodeInterfaces(files).map { ifaces: NonEmptyList[EnvironmentInterface] =>
val combinedIface: EnvironmentInterface = combineEnvInterfaces(ifaces)
val combinedIface: EnvironmentInterface =
combineEnvInterfaces(ifaces map filterTemplatesBy(roots map (_.r)))
packageInterfaceToScalaCode(util(mode, packageName, combinedIface, outputDir))
}

Expand Down Expand Up @@ -135,6 +143,26 @@ object CodeGen {
private def combineEnvInterfaces(as: NonEmptyList[EnvironmentInterface]): EnvironmentInterface =
as.suml1

// Template names can be filtered by given regexes (default: use all templates)
// If a template does not match any regex, it becomes a "normal" datatype.
private[codegen] def filterTemplatesBy(regexes: Seq[Regex])(
ei: EnvironmentInterface): EnvironmentInterface = {

def matchesRoots(qualName: Ref.Identifier): Boolean =
regexes.exists(_.findFirstIn(qualName.qualifiedName.qualifiedName).isDefined)
// scala-2.13-M4: _.matches(qualName.qualifiedName.qualifiedName)

if (regexes.isEmpty) ei
else {
val EnvironmentInterface(tds) = ei
EnvironmentInterface(tds transform {
case (id, tpl @ InterfaceType.Template(_, _)) if !matchesRoots(id) =>
InterfaceType.Normal(tpl.`type`)
case (_, other) => other
})
}
}

private def packageInterfaceToScalaCode(util: Util): Unit = {
val interface = util.iface

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ object Main extends StrictLogging {

def main(args: Array[String]): Unit =
Conf.parse(args) match {
case Some(Conf(darMap, outputDir, decoderPkgAndClass, verbosity)) =>
case Some(Conf(darMap, outputDir, decoderPkgAndClass, verbosity, roots)) =>
setGlobalLogLevel(verbosity)
logUnsupportedEventDecoderOverride(decoderPkgAndClass)
val (dars, packageName) = darsAndOnePackageName(darMap)
CodeGen.generateCode(dars, packageName, outputDir.toFile, CodeGen.Novel)
CodeGen.generateCode(dars, packageName, outputDir.toFile, CodeGen.Novel, roots)
case None =>
throw new IllegalArgumentException(
s"Invalid ${codegenId: String} command line arguments: ${args.mkString(" "): String}")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.digitalasset
package codegen

import daml.lf.data.ImmArray.ImmArraySeq
import daml.lf.data.Ref.Identifier
import daml.lf.iface._
import daml.lf.value.ValueGenerators.idGen

import org.scalatest.{WordSpec, Matchers}
import org.scalatest.prop.GeneratorDrivenPropertyChecks

class CodeGenSpec extends WordSpec with Matchers with GeneratorDrivenPropertyChecks {
import CodeGen.filterTemplatesBy
import CodeGenSpec._

"filterTemplatesBy" should {
"be identity given empty regexes" in forAll(trivialEnvInterfaceGen) { ei =>
filterTemplatesBy(Seq.empty)(ei) should ===(ei)
}

"delete all templates given impossible regex" in forAll(trivialEnvInterfaceGen) { ei =>
val noTemplates = ei copy (ei.typeDecls transform {
case (_, tmpl @ InterfaceType.Template(_, _)) => InterfaceType.Normal(tmpl.`type`)
case (_, v) => v
})
filterTemplatesBy(Seq("(?!a)a".r))(ei) should ===(noTemplates)
}

"match the union of regexes, not intersection" in forAll(trivialEnvInterfaceGen) { ei =>
filterTemplatesBy(Seq("(?s).*".r, "(?!a)a".r))(ei) should ===(ei)
}
}
}

object CodeGenSpec {
import org.scalacheck.{Arbitrary, Gen}
import Arbitrary.arbitrary

val trivialEnvInterfaceGen: Gen[EnvironmentInterface] = {
val fooRec = Record(ImmArraySeq.empty)
val fooTmpl = InterfaceType.Template(fooRec, DefTemplate(Map.empty))
val fooNorm = InterfaceType.Normal(DefDataType(ImmArraySeq.empty, fooRec))
implicit val idArb: Arbitrary[Identifier] = Arbitrary(idGen)
arbitrary[Map[Identifier, Boolean]] map { ids =>
EnvironmentInterface(ids transform { (_, isTemplate) =>
if (isTemplate) fooTmpl else fooNorm
})
}
}
}

0 comments on commit b0b3e29

Please sign in to comment.