language: automatic ts codegen for daml start (#7516)

This executes the code generations specified in the daml.yaml
configuration file on every invocation of `daml start`
and hence frees the user of doing it manually.

CHANGELOG_BEGIN
- [DAML Assistant] The `daml start` now runs all the code generators
specified in the `daml.yaml` project configuration file under the
`codegen` stanza. This frees the user of doing so manually on every
change to the DAML model.
CHANGELOG_END
This commit is contained in:
Robin Krom 2020-10-02 15:43:06 +02:00 committed by GitHub
parent 3badccfb1f
commit 6a328212f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 180 additions and 89 deletions

View File

@ -0,0 +1,67 @@
-- Copyright (c) 2020 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
module DA.Daml.Helper.Codegen
( runCodegen
, Lang(..)
, showLang
) where
import Control.Exception
import Control.Exception.Safe (catchIO)
import DA.Daml.Helper.Util
import DA.Daml.Project.Config
import DA.Daml.Project.Consts
import qualified Data.Text as T
import System.FilePath
import System.IO.Extra
import System.Process.Typed
data Lang
= Java
| Scala
| JavaScript
deriving (Show, Eq, Ord, Enum, Bounded)
showLang :: Lang -> T.Text
showLang = \case
Java -> "java"
Scala -> "scala"
JavaScript -> "js"
runCodegen :: Lang -> [String] -> IO ()
runCodegen lang args =
case lang of
JavaScript -> do
args' <-
if null args
then do
darPath <- getDarPath
projectConfig <- getProjectConfig
outputPath <-
requiredE
"Failed to read output directory for JavaScript code generation" $
queryProjectConfigRequired
["codegen", showLang lang, "output-directory"]
projectConfig
mbNpmScope :: Maybe FilePath <-
requiredE "Failed to read NPM scope for JavaScript code generation" $
queryProjectConfig
["codegen", showLang lang, "npm-scope"]
projectConfig
pure $
[darPath, "-o", outputPath] ++
["-s " <> npmScope | Just npmScope <- [mbNpmScope]]
else pure args
daml2js <- fmap (</> "daml2js" </> "daml2js") getSdkPath
withProcessWait_' (proc daml2js args') (const $ pure ()) `catchIO`
(\e -> hPutStrLn stderr "Failed to invoke daml2js." *> throwIO e)
Java ->
runJar
"daml-sdk/daml-sdk.jar"
(Just "daml-sdk/codegen-logback.xml")
(["codegen", "java"] ++ args)
Scala ->
runJar
"daml-sdk/daml-sdk.jar"
(Just "daml-sdk/codegen-logback.xml")
(["codegen", "scala"] ++ args)

View File

@ -11,20 +11,18 @@ import Data.List.Extra
import Options.Applicative.Extended
import System.Environment
import System.Exit
import System.FilePath
import System.IO
import System.Process (showCommandForUser)
import System.Process.Typed
import Text.Read (readMaybe)
import DA.Signals
import DA.Daml.Project.Consts
import DA.Daml.Helper.Init
import DA.Daml.Helper.Ledger
import DA.Daml.Helper.New
import DA.Daml.Helper.Start
import DA.Daml.Helper.Studio
import DA.Daml.Helper.Util
import DA.Daml.Helper.Codegen
main :: IO ()
main = do
@ -89,7 +87,6 @@ data AppTemplate
| AppTemplateViaOption String
| AppTemplateViaArgument String
data Lang = Java | Scala | JavaScript
commandParser :: Parser Command
commandParser = subparser $ fold
@ -418,19 +415,4 @@ runCommand = \case
LedgerUploadDar {..} -> runLedgerUploadDar flags darPathM
LedgerFetchDar {..} -> runLedgerFetchDar flags pid saveAs
LedgerNavigator {..} -> runLedgerNavigator flags remainingArguments
Codegen {..} ->
case lang of
JavaScript -> do
daml2js <- fmap (</> "daml2js" </> "daml2js") getSdkPath
withProcessWait_' (proc daml2js remainingArguments) (const $ pure ()) `catchIO`
(\e -> hPutStrLn stderr "Failed to invoke daml2js." *> throwIO e)
Java ->
runJar
"daml-sdk/daml-sdk.jar"
(Just "daml-sdk/codegen-logback.xml")
(["codegen", "java"] ++ remainingArguments)
Scala ->
runJar
"daml-sdk/daml-sdk.jar"
(Just "daml-sdk/codegen-logback.xml")
(["codegen", "scala"] ++ remainingArguments)
Codegen {..} -> runCodegen lang remainingArguments

View File

@ -48,6 +48,7 @@ import DA.Daml.Helper.Util
import DA.Daml.Project.Config
import DA.Daml.Project.Consts
import DA.Daml.Project.Types
import DA.Daml.Helper.Codegen
-- [Note] The `platform-version` field:
--
@ -223,6 +224,13 @@ runStart
jsonApiOpts <- withOptsFromProjectConfig "json-api-options" jsonApiOpts projectConfig
scriptOpts <- withOptsFromProjectConfig "script-options" scriptOpts projectConfig
doBuild
forM_ [minBound :: Lang .. maxBound :: Lang] $ \lang -> do
mbOutputPath :: Maybe String <-
requiredE ("Failed to parse codegen entry for " <> showLang lang) $
queryProjectConfig
["codegen", showLang lang, "output-directory"]
projectConfig
when (isJust mbOutputPath) $ runCodegen lang []
let scenarioArgs = maybe [] (\scenario -> ["--scenario", scenario]) mbScenario
withSandbox sandboxClassic sandboxPort (darPath : scenarioArgs ++ sandboxOpts) $ \sandboxPh sandboxPort -> do
withNavigator' shouldStartNavigator sandboxPh sandboxPort navigatorPort navigatorOpts $ \navigatorPh -> do

View File

@ -63,8 +63,8 @@ tests =
withTempDir $ \tmpDir' -> do
-- npm gets confused when the temp dir is not under /private on mac.
let tmpDir
| isMac = "/private" <> tmpDir'
| otherwise = tmpDir'
| isMac = "/private" <> tmpDir'
| otherwise = tmpDir'
step "Create app from template"
withCurrentDirectory tmpDir $ do
callCommandSilent $ "daml new " <> projectName <> " --template create-daml-app"
@ -111,8 +111,6 @@ tests =
forM_ [file | file <- genFiles, takeFileName file == "package.json"] (patchTsDependencies uiDir)
withCurrentDirectory uiDir $ do
patchTsDependencies uiDir "package.json"
step "Install UI dependencies again to incorporate the changes"
callCommandSilent "npm-cli.js install --frozen-lockfile"
step "Run linter again"
callCommandSilent "npm-cli.js run-script lint -- --max-warnings 0"
step "Build the new UI"
@ -130,6 +128,11 @@ tests =
T.writeFileUtf8
(uiDir </> "src" </> "index.test.ts")
(T.replace "create-daml-app" (T.pack projectName) testFileContent)
-- patch daml.yaml, remove JavaScript code generation entry so that patched generated code
-- is not overwritten
let damlYaml = cdaDir </> "daml.yaml"
content <- readFileUTF8' damlYaml
writeFileUTF8 damlYaml $ unlines $ dropEnd 4 $ lines content
callCommandSilent "CI=yes npm-cli.js run-script test -- --ci --all"
addTestDependencies :: FilePath -> FilePath -> IO ()

View File

@ -339,7 +339,7 @@ packagingTests = testGroup "packaging"
, "--json-api-option=--port-file=jsonapi.port"
]
withCurrentDirectory tmpDir $
withCreateProcess startProc $ \_ _ _ _ph -> do
withCreateProcess startProc $ \_ _ _ ph -> do
jsonApiPort <- readPortFile maxRetries "jsonapi.port"
let token = JWT.encodeSigned (JWT.HMACSecret "secret") mempty mempty
{ JWT.unregisteredClaims = JWT.ClaimsMap $
@ -359,6 +359,48 @@ packagingTests = testGroup "packaging"
manager <- newManager defaultManagerSettings
queryResponse <- httpLbs queryRequest manager
responseBody queryResponse @?= "{\"result\":{\"identifier\":\"Alice\",\"isLocal\":true},\"status\":200}"
-- waitForProcess' will block on Windows so we explicitly kill the process.
terminateProcess ph
, testCase "daml start invokes codegen" $ withTempDir $ \tmpDir -> do
writeFileUTF8 (tmpDir </> "daml.yaml") $ unlines
[ "sdk-version: " <> sdkVersion
, "name: codegen"
, "version: \"1.0\""
, "source: ."
, "dependencies:"
, "- daml-prim"
, "- daml-stdlib"
, "start-navigator: false"
, "codegen:"
, " js:"
, " output-directory: ui/daml.js"
, " java:"
, " output-directory: ui/java"
, " scala:"
, " output-directory: ui/scala"
-- this configuration option shouldn't be mandatory according to docs, but it is.
-- See https://github.com/digital-asset/daml/issues/7547.
, " package-prefix: com.digitalasset"
]
let startProc = shell $ unwords
[ "daml"
, "start"
]
withCurrentDirectory tmpDir $
withCreateProcess startProc $ \_ _ _ startPh -> do
race_ (waitForProcess' startProc startPh) $ do
-- 15 secs for all the codegens to complete
threadDelay 15000000
didGenerateJsCode <-
doesFileExist ("ui" </> "daml.js" </> "codegen-1.0" </> "package.json")
didGenerateJavaCode <-
doesFileExist
("ui" </> "java" </> "da" </> "internal" </> "template" </> "Archive.java")
didGenerateScalaCode <- doesFileExist ("ui" </> "scala" </> "com" </> "digitalasset" </> "PackageIDs.scala" )
didGenerateJsCode @?= True
didGenerateJavaCode @?= True
didGenerateScalaCode @?= True
terminateProcess startPh
, testCase "daml start with relative DAML_PROJECT path" $ withTempDir $ \tmpDir -> do
let projDir = tmpDir </> "project"
createDirectoryIfMissing True (projDir </> "daml")

View File

@ -64,30 +64,26 @@ As with the ``Follow`` choice, there are a few aspects to note here.
- The body of the choice first ensures that the sender is a user that the receiver is following and then creates the ``Message`` contract with the ``receiver`` being the signatory of the ``User`` contract.
This completes the workflow for messaging in our app.
Now let's integrate this functionality into the UI.
TypeScript Code Generation
==========================
Running the New Feature
=======================
Remember that we interface with the DAML model from the UI components using generated TypeScript.
Since we have changed our DAML code, we also need to rerun the TypeScript code generator.
Open a new terminal and run the following commands::
We need to terminate the previous ``daml start`` process and run it again, as we need to have a Sandbox instance with a DAR file containing the new feature. As a reminder, by running ``daml start`` again we will
daml build
daml codegen js .daml/dist/create-daml-app-0.1.0.dar -o ui/daml.js
- Compile our DAML code into a *DAR file containing the new feature*
- Generate a JavaScript library under ``ui/daml.js`` to connect the UI with your DAML code
- Run a fresh instance of the *Sandbox with the new DAR file*
- Start the HTTP JSON API
The result is an up-to-date TypeScript interface to our DAML model, in particular to the new ``Message`` template and ``SendMessage`` choice.
First, navigate to the terminal window where the ``daml start`` process is running and terminate the active process by hitting ``Ctrl-C``.
This shuts down the previous instances of the sandbox.
Then in the root ``create-daml-app`` folder run ``daml start``.
To make sure that Yarn picks up the newly generated JavaScript code,
we have to run the following command in the ``ui`` directory::
As mentioned at the beginning of this *Getting Started with DAML* guide, DAML Sandbox uses an
in-memory store, which means it loses its state when stopped or restarted. That means that all user
data and follower relationships are lost.
npm install --frozen-lockfile
Once that command finishes, you have to close Visual Studio Code
and restart it by running ``daml studio`` from the root directory of
your project.
We can now implement our messaging feature in the UI!
Now let's integrate the new functionality into the UI.
Messaging UI
============
@ -182,22 +178,11 @@ You can see we simply follow the formatting of the previous panels and include t
That is all for the implementation!
Let's give the new functionality a spin.
Running the New Feature
=======================
We need to terminate the previous ``daml start`` process and run it again, as we need to have a Sandbox instance with a DAR file containing the new feature. As a reminder, by running ``daml start`` again we will
- Compile our DAML code into a *DAR file containing the new feature*
- Run a fresh instance of the *Sandbox with the new DAR file*
- Start the HTTP JSON API
First, navigate to the terminal window where the ``daml start`` process is running and terminate the active process by hitting ``Ctrl-C``.
This shuts down the previous instances of the sandbox.
Then in the root ``create-daml-app`` folder run ``daml start``.
As mentioned at the beginning of this *Getting Started with DAML* guide, DAML Sandbox uses an in-memory store, which means it loses its state when stopped or restarted. That means that all user data and follower relationships are lost.
Running the updated UI
======================
If you have the frontend UI up and running you're all set. In case you don't have the UI running open a new terminal window and navigate to the ``create-daml-app/ui`` folder and run the ``npm start`` command, which will start the UI.
Once you've done all these changes you should see the same login page as before at http://localhost:3000.
.. figure:: images/create-daml-app-login-screen.png

View File

@ -32,7 +32,7 @@ Please make sure that you have the DAML SDK, Java 8 or higher, and Visual Studio
You will also need some common software tools to build and interact with the template project.
- `Git <https://git-scm.com/downloads>`_ version control system
- `Node <https://docs.npmjs.com/downloading-and-installing-node-js-and-npm>`_ package manager for JavaScript.
- `Node <https://docs.npmjs.com/downloading-and-installing-node-js-and-npm>`_ package manager for JavaScript.
Note: On Ubuntu 18.04, NodeJS 8.10 will be installed but its too old.
- A terminal application for command line interaction
@ -54,25 +54,6 @@ Change to the new folder::
cd create-daml-app
Next we need to compile the DAML code to a DAR file::
daml build
Once the DAR file is created you will see this message in terminal ``Created .daml/dist/create-daml-app-0.1.0.dar``.
Any commands starting with ``daml`` are using the :doc:`DAML Assistant </tools/assistant>`, a command line tool in the DAML SDK for building and running DAML apps.
In order to connect the UI code to this DAML, we need to run a code generation step::
daml codegen js .daml/dist/create-daml-app-0.1.0.dar -o ui/daml.js
Now, changing to the ``ui`` folder, use ``npm`` to install the project dependencies::
cd ui
npm install --frozen-lockfile
This step may take a couple of moments (it's worth it!).
You should see ``success Saved lockfile.`` in the output if everything worked as expected.
.. TODO: Give instructions for possible failures.
We can now run the app in two steps.
@ -81,17 +62,28 @@ In one terminal, at the root of the ``create-daml-app`` directory, run the comma
daml start
Any commands starting with ``daml`` are using the :doc:`DAML Assistant </tools/assistant>`, a
command line tool in the DAML SDK for building and running DAML apps.
You will know that the command has started successfully when you see the ``INFO com.daml.http.Main$ - Started server: ServerBinding(/127.0.0.1:7575)`` message in the terminal. The command does a few things:
1. Compiles the DAML code to a DAR file as in the previous ``daml build`` step.
2. Starts an instance of the :doc:`Sandbox </tools/sandbox>`, an in-memory ledger useful for development, loaded with our DAR.
3. Starts a server for the :doc:`HTTP JSON API </json-api/index>`, a simple way to run commands against a DAML ledger (in this case the running Sandbox).
1. Compiles the DAML code to a DAR file.
2. Generates a JavaScript library in ``ui/daml.js`` to connect the UI with your DAML code.
3. Starts an instance of the :doc:`Sandbox </tools/sandbox>`, an in-memory ledger useful for development, loaded with our DAR.
4. Starts a server for the :doc:`HTTP JSON API </json-api/index>`, a simple way to run commands against a DAML ledger (in this case the running Sandbox).
We'll leave these processes running to serve requests from our UI.
In a second terminal, navigate to the ``create-daml-app/ui`` folder and run the application::
In a second terminal, navigate to the ``create-daml-app/ui`` folder and use ``npm`` to install the project dependencies::
cd create-daml-app/ui
npm install --frozen-lockfile
This step may take a couple of moments (it's worth it!).
You should see ``success Saved lockfile.`` in the output if everything worked as expected.
Now you can start the UI with::
cd ui
npm start
This starts the web UI connected to the running Sandbox and JSON API server.

View File

@ -54,8 +54,9 @@ Help for each specific codegen::
Project file configuration (Java and Scala)
-------------------------------------------
For **Java** and **Scala** the above settings can be configured in the ``codegen`` element of the DAML project file ``daml.yaml``.
At present the **JavaScript/TypeScript** ``codegen`` does not support this. See `this issue <https://github.com/digital-asset/daml/issues/6355>`_ for status on this feature.
The above settings can be configured in the ``codegen`` element of the DAML project file
``daml.yaml``. See `this issue <https://github.com/digital-asset/daml/issues/6355>`_ for status on
this feature.
Here is an example::
@ -75,6 +76,9 @@ Here is an example::
- daml-prim
- daml-stdlib
codegen:
js:
output-directory: ui/daml.js
npm-scope: daml.js
java:
package-prefix: com.daml.quickstart.iou
output-directory: java-codegen/src/main/java
@ -86,14 +90,18 @@ Here is an example::
You can then run the above configuration to generate your **Java** or **Scala** code::
$ daml codegen [java|scala]
$ daml codegen [js|java|scala]
The equivalent **Java** or **Scala** command line configuration would be::
The equivalent **JavaScript** command line configuration would be::
$ daml codegen js ./.daml/dist/quickstart-0.0.1.dar -o ui/daml.js -s daml.js
and the equivalent **Java** or **Scala** command line configuration::
$ daml codegen [java|scala| ./.daml/dist/quickstart-0.0.1.dar=com.daml.quickstart.iou --output-directory=java-codegen/src/main/java --verbosity=2
In order to compile the resulting **Java** or **Scala** classes, you need to
add the corresponding dependencies to your build tools.
add the corresponding dependencies to your build tools.
For **Scala**, you can depend on::
@ -109,4 +117,4 @@ For **Java**, add the following **Maven** dependency::
.. note::
Replace ``YOUR_SDK_VERSION`` with the version of your DAML SDK
Replace ``YOUR_SDK_VERSION`` with the version of your DAML SDK

View File

@ -72,7 +72,7 @@ beforeAll(async () => {
// Getting Started Guide.
const startArgs = [
'start',
`--json-api-option=--port-file=${JSON_API_PORT_FILE_NAME}`
`--json-api-option=--port-file=${JSON_API_PORT_FILE_NAME}`,
];
startProc = spawn('daml', startArgs, startOpts);

View File

@ -14,3 +14,7 @@ sandbox-options:
- --wall-clock-time
- --ledgerid=__PROJECT_NAME__-sandbox
start-navigator: false
codegen:
js:
output-directory: ui/daml.js
npm-scope: daml.js