Skip to content

Commit

Permalink
DAML assistant codegen command (digital-asset#2800)
Browse files Browse the repository at this point in the history
* DAML assistant codegen command, codegen config reader and docs
  • Loading branch information
leo-da authored Sep 12, 2019
1 parent 47d8432 commit 4a9fb1c
Show file tree
Hide file tree
Showing 15 changed files with 524 additions and 28 deletions.
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ DAML SDK documentation
tools/sandbox
tools/visual
tools/navigator/index
tools/codegen

.. toctree::
:titlesonly:
Expand Down
1 change: 1 addition & 0 deletions docs/source/tools/assistant.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ DAML Assistant (``daml``)
- Launch the HTTP JSON API: ``daml json-api``
You can find more information in the
`README <https://github.com/digital-asset/daml/blob/master/ledger-service/http-json/README.md>`_.
- Run :doc:`DAML codegen </tools/codegen>`: ``daml codegen``

- Install new SDK versions manually: ``daml install <version>``

Expand Down
98 changes: 98 additions & 0 deletions docs/source/tools/codegen.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
.. Copyright (c) 2019 The DAML Authors. All rights reserved.
.. SPDX-License-Identifier: Apache-2.0
DAML codegen
############

Introduction
============

You can use the DAML codegen to generate Java and Scala classes representing DAML contract templates. These classes incorporate all boilerplate code for constructing corresponding ledger :ref:`com.digitalasset.ledger.api.v1.CreateCommand` and :ref:`com.digitalasset.ledger.api.v1.ExerciseCommand`.

Running the DAML codegen
========================

The basic command to run the DAML codegen is::

$ daml codegen [java|scala] [options]

There are two modes:

- command line configuration, specifying **all** settings in the command line

- project file configuration, specifying **all** settings in the ``daml.yaml``

Command line configuration
--------------------------

Help for **DAML to Java** codegen::

$ daml codegen java --help

Help for **DAML to Scala** codegen::

$ daml codegen scala --help

Both **DAML to Java** and **DAML to Scala** take the same set of configuration settings::

Usage: codegen [options] <DAR-file[=package-prefix]>...

Code generator for the DAML ledger bindings.

<DAR-file[=package-prefix]>...
DAR file to use as input of the codegen with an optional, but recommend, package prefix for the generated sources.
-o, --output-directory <value>
Output directory for the generated sources
-d, --decoderClass <value>
Fully Qualified Class Name of the optional Decoder utility
-V, --verbosity <value> Verbosity between 0 (only show errors) and 4 (show all messages) -- defaults to 0
-r, --root <value> Regular expression for fully-qualified names of templates to generate -- defaults to .*
--help This help text

Project file configuration
--------------------------

The above settings can be configured in the ``codegen`` element of the DAML project file ``daml.yaml``. Here is an example::

sdk-version: 0.0.0
name: quickstart
source: daml
scenario: Main:setup
parties:
- Alice
- Bob
- USD_Bank
- EUR_Bank
version: 0.0.1
exposed-modules:
- Main
dependencies:
- daml-prim
- daml-stdlib
codegen:
java:
package-prefix: com.digitalasset.quickstart.iou
output-directory: java-codegen/src/main/java
verbosity: 2
scala:
package-prefix: com.digitalasset.quickstart.iou
output-directory: scala-codegen/src/main/scala
verbosity: 2

You can run the above configuration to generate **Java** code::

$ daml codegen java

and to generate **Scala** code::

$ daml codegen scala

The equivalent **DAML to Java** command line configuration::

$ daml codegen java ./.daml/dist/quickstart-0.0.1.dar=com.digitalasset.quickstart.iou --output-directory=java-codegen/src/main/java --verbosity=2

and **DAML to Scala** command line configuration::

$ daml codegen scala ./.daml/dist/quickstart-0.0.1.dar=com.digitalasset.quickstart.iou --output-directory=scala-codegen/src/main/scala --verbosity=2


19 changes: 10 additions & 9 deletions language-support/codegen-common/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,25 @@ load(
"da_scala_test",
)

codegen_common_deps = [
"//3rdparty/jvm/ch/qos/logback:logback_classic",
"//3rdparty/jvm/com/github/scopt",
"//3rdparty/jvm/com/typesafe/scala_logging",
"//3rdparty/jvm/io/circe:circe_core",
"//daml-assistant/scala-daml-project-config",
]

da_scala_library(
name = "codegen-common",
srcs = glob(["src/main/**/*.scala"]),
tags = ["maven_coordinates=com.daml:codegen-common:__VERSION__"],
visibility = ["//visibility:public"],
deps = [
"//3rdparty/jvm/ch/qos/logback:logback_classic",
"//3rdparty/jvm/com/github/scopt",
],
deps = codegen_common_deps,
)

da_scala_test(
name = "test",
srcs = glob(["src/test/**/*.scala"]),
resources = glob(["src/test/resources/**/*"]),
deps = [
":codegen-common",
"//3rdparty/jvm/com/github/scopt",
"//3rdparty/jvm/org/scalatest",
],
deps = [":codegen-common"] + codegen_common_deps,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright (c) 2019 The DAML Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.digitalasset.daml.lf.codegen.conf

import java.io.File
import java.nio.file.Path

import ch.qos.logback.classic.Level
import com.digitalasset.assistant.config._
import io.circe.ACursor

import scala.util.Try

object CodegenConfigReader {

sealed trait CodegenDest
object Java extends CodegenDest
object Scala extends CodegenDest

type Result[A] = Either[ConfigLoadingError, A]

def readFromEnv(dest: CodegenDest): Result[Conf] =
for {
sdkConf <- ProjectConfig.loadFromEnv()
codegenConf <- codegenConf(sdkConf, dest)
} yield codegenConf

def codegenConf(sdkConf: ProjectConfig, dest: CodegenDest): Result[Conf] =
for {
dar <- darPath(sdkConf)
packagePrefix <- packagePrefix(sdkConf, dest)
outputDirectory <- outputDirectory(sdkConf, dest)
decoderPkgAndClass <- decoderPkgAndClass(sdkConf, dest)
verbosity <- verbosity(sdkConf, dest): Result[Option[Int]]
logLevel <- logLevel(verbosity, Level.ERROR)
root <- root(sdkConf, dest): Result[Option[List[String]]]
} yield
Conf(
darFiles = Map(dar -> packagePrefix),
outputDirectory = outputDirectory,
decoderPkgAndClass = decoderPkgAndClass,
verbosity = logLevel,
roots = root.getOrElse(Nil)
)

private def darPath(sdkConf: ProjectConfig): Result[Path] =
for {
name <- name(sdkConf)
version <- version(sdkConf)
dar <- darPath(name, version)
} yield dar

private def name(sdkConf: ProjectConfig): Result[String] =
sdkConf.name.flatMap {
case Some(a) => Right(a)
case None => Left(ConfigMissing("name"))
}

private def version(sdkConf: ProjectConfig): Result[String] =
sdkConf.version.flatMap {
case Some(a) => Right(a)
case None => Left(ConfigMissing("version"))
}

private def darPath(name: String, version: String): Result[Path] =
for {
darFile <- result(new File(darDirectory, s"$name-$version.dar"))
darPath <- result(darFile.toPath)
} yield darPath

private val darDirectory = new File(".daml/dist")

private def packagePrefix(sdkConf: ProjectConfig, mode: CodegenDest): Result[Option[String]] =
codegen(sdkConf, mode)
.downField("package-prefix")
.as[Option[String]]
.left
.map(configParseError)

private def outputDirectory(sdkConf: ProjectConfig, mode: CodegenDest): Result[Path] =
codegen(sdkConf, mode)
.downField("output-directory")
.as[String]
.left
.map(configParseError)
.flatMap(path)

private def decoderPkgAndClass(
sdkConf: ProjectConfig,
mode: CodegenDest): Result[Option[(String, String)]] =
codegen(sdkConf, mode)
.downField("decoderClass")
.as[Option[String]]
.left
.map(configParseError)
.flatMap(decoderClass)

private def decoderClass(fa: Option[String]): Result[Option[(String, String)]] =
fa match {
case Some(a) => decoderClass(a).map(Some(_))
case None => resultR(None)
}

private def decoderClass(s: String): Result[(String, String)] =
result(Conf.readClassName.reads(s))

private def verbosity(sdkConf: ProjectConfig, mode: CodegenDest): Result[Option[Int]] =
codegen(sdkConf, mode)
.downField("verbosity")
.as[Option[Int]]
.left
.map(configParseError)

private def logLevel(fa: Option[Int], default: Level): Result[Level] =
fa.fold(resultR(default))(readVerbosity)

private def readVerbosity(a: Int): Result[Level] =
result(Conf.readVerbosity.reads(a.toString))

private def root(sdkConf: ProjectConfig, mode: CodegenDest): Result[Option[List[String]]] =
codegen(sdkConf, mode)
.downField("root")
.as[Option[List[String]]]
.left
.map(configParseError)

private def codegen(sdkConf: ProjectConfig, mode: CodegenDest): ACursor =
sdkConf.content.hcursor
.downField("codegen")
.downField(dest(mode))

private def dest(a: CodegenDest): String = a match {
case Java => "java"
case Scala => "scala"
}

private def path(a: String): Result[Path] =
result(new File(a).toPath)

private def configParseError(e: Exception): ConfigParseError = ConfigParseError(e.getMessage)

private def result[A](a: => A): Result[A] =
result(Try(a))

private def result[A](fa: Try[A]): Result[A] =
fa.toEither.left.map(e => ConfigLoadError(e.getMessage))

private def resultR[A](a: A): Result[A] =
Right(a): Result[A]
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ object Conf {

private[conf] val readPath: scopt.Read[Path] = scopt.Read.stringRead.map(s => Paths.get(s))

private[conf] val readClassName: scopt.Read[(String, String)] = scopt.Read.stringRead.map {
val readClassName: scopt.Read[(String, String)] = scopt.Read.stringRead.map {
case PackageAndClassRegex(p, c) => (p, c)
case _ =>
throw new IllegalArgumentException("Expected a Full Qualified Class Name")
}

private[conf] val readVerbosity: scopt.Read[Level] = scopt.Read.stringRead.map {
val readVerbosity: scopt.Read[Level] = scopt.Read.stringRead.map {
case "0" => Level.ERROR
case "1" => Level.WARN
case "2" => Level.INFO
Expand Down
Loading

0 comments on commit 4a9fb1c

Please sign in to comment.