mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-19 00:37:23 +03:00
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:
parent
3badccfb1f
commit
6a328212f2
67
daml-assistant/daml-helper/src/DA/Daml/Helper/Codegen.hs
Normal file
67
daml-assistant/daml-helper/src/DA/Daml/Helper/Codegen.hs
Normal 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)
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 ()
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user