Skip to content

Commit

Permalink
quickstart-scala DAML Assistant template (#745)
Browse files Browse the repository at this point in the history
* Add quickstart-scala IOU example, DAML Assistant template, #614

* Move all scala examples under language-support/scala/examples

* Removing target dirs which don't get excluded by the glob, #614
  • Loading branch information
leo-da authored May 7, 2019
1 parent bac5c4a commit 8534e28
Show file tree
Hide file tree
Showing 33 changed files with 574 additions and 26 deletions.
14 changes: 14 additions & 0 deletions docs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,20 @@ genrule(
),
)

filegroup(
name = "daml-assistant-iou-setup",
srcs = glob(
["source/getting-started/quickstart/template-root/*"],
# excluding quickstart-java stuff
exclude = [
"source/getting-started/quickstart/template-root/src",
"source/getting-started/quickstart/template-root/pom.xml",
],
exclude_directories = 0,
),
visibility = ["//visibility:public"],
)

genrule(
name = "quickstart-java",
srcs = ["//:component-version"] + glob(["source/getting-started/quickstart/template-root/**"]),
Expand Down
3 changes: 3 additions & 0 deletions docs/source/support/release-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ HEAD — ongoing
- Java Codegen: variants with unserializable cases are now accepted (see `#946 <https://github.com/digital-asset/daml/pull/946>`_)
- Java Bindings: ``CreateAndExerciseCommand`` is now properly converted in the Java Bindings data layer (see `#979 <https://github.com/digital-asset/daml/pull/979>`_)

- Add ``quickstart-scala`` DAML Assistant project template.


0.12.15 - 2019-05-06
--------------------

Expand Down
5 changes: 0 additions & 5 deletions language-support/scala/codegen-sbt-example/README.md

This file was deleted.

48 changes: 48 additions & 0 deletions language-support/scala/examples/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

load("//bazel_tools:pkg.bzl", "pkg_tar")

filegroup(
name = "quickstart-scala-src",
srcs = glob(
["quickstart-scala/*"],
exclude = [
"**/.*",
"**/target",
],
exclude_directories = 0,
),
)

genrule(
name = "quickstart-scala-dir",
srcs = [
":quickstart-scala-src",
"//docs:daml-assistant-iou-setup",
],
outs = ["quickstart-scala"],
cmd = """
mkdir -p $@
cp -rL $(SRCS) $@
rm -rf $@/project/project/*
rm -rf $@/target
rm -rf $@/project/target
rm -rf $@/application/target
rm -rf $@/scala-codegen/target
""",
visibility = ["//visibility:public"],
)

pkg_tar(
name = "quickstart-scala-dist",
srcs = [":quickstart-scala-dir"],
extension = "tar.gz",
mode = "0644",
symlinks = {
"./quickstart-scala/project/project/Versions.scala": "../Versions.scala",
"./quickstart-scala/project/project/Versions.scala.template": "../Versions.scala.template",
"./quickstart-scala/project/project/Artifactory.scala": "../Artifactory.scala",
},
visibility = ["//visibility:public"],
)
18 changes: 18 additions & 0 deletions language-support/scala/examples/codegen-sbt-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Mock scala/codegen example
This example demonstrates how to:
- create a new contract from a template
- instantiate create and exercise ledger commands
```
$ sbt -Dda.sdk.version=100.12.12 mock-example/run
```

# Sandbox scala/codegen example
This examples demonstrates how to:
- start in-process sandbox
- create a new contract from a template
- send corresponding create an exercise commands to the ledger
- receive events from the ledger
- stop in-process sandbox
```
$ sbt -Dda.sdk.version=100.12.12 sandbox-example/run
```
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,9 @@ lazy val `scala-codegen` = project
case Some(darFile) =>
generateScalaFrom(
darFile,
"Main.dalf",
"com.digitalasset.example.daml",
(sourceManaged in Compile).value,
streams.value.cacheDirectory / name.value / "dalfs").toSeq
streams.value.cacheDirectory / name.value).toSeq
})
)

Expand Down Expand Up @@ -81,12 +80,12 @@ lazy val commonSettings = Seq(
)

lazy val sandboxDependencies = Seq(
"com.daml.scala" %% "bindings-akka" % sdkVersion,
"com.digitalasset.platform" %% "sandbox" % sdkVersion,
"com.daml.scala" %% "bindings-akka" % daSdkVersion,
"com.digitalasset.platform" %% "sandbox" % daSdkVersion,
)

lazy val codeGenDependencies = Seq(
"com.daml.scala" %% "bindings" % sdkVersion,
"com.daml.scala" %% "bindings" % daSdkVersion,
)

// ##################################### inlined sbt-daml ##############################
Expand Down Expand Up @@ -135,23 +134,17 @@ def compileDaml(

def generateScalaFrom(
darFile: File,
mainDalfName: String,
packageName: String,
srcManagedDir: File,
outputDir: File,
cacheDir: File): Set[File] = {

require(darFile.getPath.endsWith(".dar"))
require(darFile.exists(), s"DAR file doest not exist: ${darFile.getPath: String}")

// directory containing the dar is used as a work directory
val outDir = darFile.getParentFile

// use a FileFunction.cached on the dar
val cache = FileFunction.cached(cacheDir, FileInfo.hash) { _ =>
if (srcManagedDir.exists) // purge output if exists
IO.delete(srcManagedDir.listFiles)

CodeGen.generateCode(List(darFile), packageName, srcManagedDir, Novel)
(srcManagedDir ** "*.scala").get.toSet
if (outputDir.exists) IO.delete(outputDir.listFiles)
CodeGen.generateCode(List(darFile), packageName, outputDir, Novel)
(outputDir ** "*.scala").get.toSet
}
cache(Set(darFile))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
// SPDX-License-Identifier: Apache-2.0

object Versions {
val sdkVersion: String = sdkVersionFromSysProps().getOrElse("100.12.6")
private val daSdkVersionKey = "da.sdk.version"

println(s"DA sdkVersion: $sdkVersion")
val daSdkVersion: String = sys.props.get(daSdkVersionKey).getOrElse("100.12.12")
println(s"$daSdkVersionKey: $daSdkVersion")

lazy val detectedOs: String = sys.props("os.name") match {
case "Mac OS X" => "osx"
case _ => "linux"
}

private def sdkVersionFromSysProps(): Option[String] =
sys.props.get("DA.sdkVersion").map(_.toString)
}
17 changes: 17 additions & 0 deletions language-support/scala/examples/quickstart-scala/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# quickstart-scala example

This example demonstrates how to:
- set up and configure Scala codegen
- instantiate a contract and send a corresponding create command to the ledger
- how to exercise a choice and send a corresponding exercise command
- subscribe to receive ledger events and decode them into generated Scala ADTs

This examples requires a running sandbox. To start a sandbox, run the following command from within a DAML Assistant project directory:
```
$ daml start
```

To run the quickstart example:
```
$ sbt -Dda.sdk.version=<DA_SDK_VERSION> -Ddar.file=<DAR_FILE_PATH> "application/runMain com.digitalasset.quickstart.iou.IouMain localhost 6865"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %level - %msg%n</pattern>
</encoder>
</appender>

<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.digitalasset.quickstart.iou

import java.util.UUID

import akka.stream.Materializer
import akka.stream.scaladsl.{Sink, Source}
import akka.{Done, NotUsed}
import com.digitalasset.api.util.TimeProvider
import com.digitalasset.api.util.TimestampConversion.fromInstant
import com.digitalasset.ledger.api.refinements.ApiTypes.{ApplicationId, WorkflowId}
import com.digitalasset.ledger.api.v1.command_submission_service.SubmitRequest
import com.digitalasset.ledger.api.v1.commands.Commands
import com.digitalasset.ledger.api.v1.ledger_offset.LedgerOffset
import com.digitalasset.ledger.api.v1.transaction.Transaction
import com.digitalasset.ledger.api.v1.transaction_filter.{Filters, TransactionFilter}
import com.digitalasset.ledger.client.LedgerClient
import com.digitalasset.ledger.client.binding.{Primitive => P}
import com.digitalasset.quickstart.iou.FutureUtil.toFuture
import com.google.protobuf.empty.Empty

import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}

class ClientUtil(
client: LedgerClient,
applicationId: ApplicationId,
ttl: Duration,
timeProvider: TimeProvider) {

import ClientUtil._

private val ledgerId = client.ledgerId
private val commandClient = client.commandClient
private val transactionClient = client.transactionClient

def ledgerEnd(implicit ec: ExecutionContext): Future[LedgerOffset] =
transactionClient.getLedgerEnd.flatMap(response => toFuture(response.offset))

def submitCommand[T](
sender: P.Party,
workflowId: WorkflowId,
command: P.Update[P.ContractId[T]]): Future[Empty] = {
commandClient.submitSingleCommand(submitRequest(sender, workflowId, command))
}

def submitRequest[T](
party: P.Party,
workflowId: WorkflowId,
seq: P.Update[P.ContractId[T]]*): SubmitRequest = {
val now = timeProvider.getCurrentTime
val commands = Commands(
ledgerId = ledgerId,
workflowId = WorkflowId.unwrap(workflowId),
applicationId = ApplicationId.unwrap(applicationId),
commandId = uniqueId,
party = P.Party.unwrap(party),
ledgerEffectiveTime = Some(fromInstant(now)),
maximumRecordTime = Some(fromInstant(now.plusNanos(ttl.toNanos))),
commands = seq.map(_.command)
)
SubmitRequest(Some(commands), None)
}

def nextTransaction(party: P.Party, offset: LedgerOffset)(
implicit mat: Materializer): Future[Transaction] =
transactionClient
.getTransactions(offset, None, transactionFilter(party))
.take(1L)
.runWith(Sink.head)

def subscribe(party: P.Party, offset: LedgerOffset, max: Option[Long])(f: Transaction => Unit)(
implicit mat: Materializer): Future[Done] = {
val source: Source[Transaction, NotUsed] =
transactionClient.getTransactions(offset, None, transactionFilter(party))
max.fold(source)(n => source.take(n)) runForeach f
}

override lazy val toString: String = s"ClientUtil{ledgerId=$ledgerId}"
}

object ClientUtil {
def transactionFilter(ps: P.Party*): TransactionFilter =
TransactionFilter(P.Party.unsubst(ps).map((_, Filters.defaultInstance)).toMap)

def uniqueId: String = UUID.randomUUID.toString

def workflowIdFromParty(p: P.Party): WorkflowId =
WorkflowId(s"${P.Party.unwrap(p): String} Workflow")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.digitalasset.quickstart.iou

import com.digitalasset.ledger.api.v1.event.{ArchivedEvent, CreatedEvent, Event}
import com.digitalasset.ledger.api.v1.transaction.Transaction
import com.digitalasset.ledger.api.v1.{value => V}
import com.digitalasset.ledger.client.binding.{Contract, Template, ValueDecoder, Primitive => P}

object DecodeUtil {
def decodeAllCreated[A <: Template[A]: ValueDecoder](transaction: Transaction): Seq[Contract[A]] =
for {
event <- transaction.events
created <- event.event.created.toList
a <- decodeCreated(created)
} yield a

def decodeCreated[A <: Template[A]: ValueDecoder](transaction: Transaction): Option[Contract[A]] =
for {
event <- transaction.events.headOption: Option[Event]
created <- event.event.created: Option[CreatedEvent]
a <- decodeCreated(created)
} yield a

def decodeCreated[A <: Template[A]: ValueDecoder](event: CreatedEvent): Option[Contract[A]] = {
val decoder = implicitly[ValueDecoder[A]]
for {
record <- event.createArguments: Option[V.Record]
a <- decoder.read(V.Value.Sum.Record(record)): Option[A]
} yield Contract(P.ContractId(event.contractId), a)
}

def decodeArchived[A <: Template[A]](transaction: Transaction): Option[P.ContractId[A]] = {
for {
event <- transaction.events.headOption: Option[Event]
archived <- event.event.archived: Option[ArchivedEvent]
} yield P.ContractId(archived.contractId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.digitalasset.quickstart.iou
import scala.concurrent.Future

object FutureUtil {
def toFuture[A](o: Option[A]): Future[A] =
o.fold(Future.failed[A](new IllegalStateException(s"Empty option: $o")))(a =>
Future.successful(a))
}
Loading

0 comments on commit 8534e28

Please sign in to comment.