Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

language-support/scala: add support for specifying dependency roots explicitly #1210

Merged
merged 8 commits into from Jun 12, 2019
6 changes: 6 additions & 0 deletions docs/source/support/release-notes.rst
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
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change is also going to affect java codegen... and the SDK doc, that does not mention root.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leo-da We can remove the option from the driver if you prefer but I do think it is useful to have in all codegen.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jberthold-da that is fine, I was just pointing the things that we will need to address when this PR lands in master.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leo-da so should I take the option out of the CLI/config, or just declare it as unsupported for java?
I do think it is a very useful option.

.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
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",
],
)
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
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
@@ -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
})
}
}
}