Build js packages at code-generation time (#5171)

changelog_begin
changelog_end
This commit is contained in:
Shayne Fletcher 2020-03-25 14:13:37 -04:00 committed by GitHub
parent 1a229ebb7b
commit 9351b7a7b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 192 additions and 187 deletions

View File

@ -71,14 +71,19 @@ da_haskell_test(
name = "integration-tests", name = "integration-tests",
timeout = "long", timeout = "long",
srcs = glob(["src/**/*.hs"]), srcs = glob(["src/**/*.hs"]),
args = [
"$(location //:yarn)",
"daml-types-not-available" if is_windows else "$(location //language-support/ts/daml-types:npm_package)",
],
data = [ data = [
":integration-tests-mvn", ":integration-tests-mvn",
"//release:sdk-release-tarball", "//release:sdk-release-tarball",
"@local_jdk//:bin/java.exe" if is_windows else "@local_jdk//:bin/java", "@local_jdk//:bin/java.exe" if is_windows else "@local_jdk//:bin/java",
"//:yarn",
"//compiler/damlc/tests:generate-simple-dalf", "//compiler/damlc/tests:generate-simple-dalf",
"@mvn_dev_env//:mvn", "@mvn_dev_env//:mvn",
"@tar_dev_env//:tar", "@tar_dev_env//:tar",
], ] + ([] if is_windows else ["//language-support/ts/daml-types:npm_package"]),
# Im sure the mvn stuff will be flaky. # Im sure the mvn stuff will be flaky.
flaky = True, flaky = True,
hackage_deps = [ hackage_deps = [

View File

@ -31,33 +31,43 @@ import Test.Main
import Test.Tasty import Test.Tasty
import Test.Tasty.HUnit import Test.Tasty.HUnit
import qualified Web.JWT as JWT import qualified Web.JWT as JWT
import qualified Data.ByteString.Lazy as BSL
import qualified Data.HashMap.Strict as HMS
import Data.Aeson
import DA.Directory
import DA.Bazel.Runfiles import DA.Bazel.Runfiles
import DA.Daml.Helper.Run import DA.Daml.Helper.Run
import SdkVersion import SdkVersion
main :: IO () main :: IO ()
main = main = do
withTempDir $ \tmpDir -> do setEnv "TASTY_NUM_THREADS" "1" True
-- We manipulate global state via the working directory and -- We manipulate global state via the working directory and
-- the environment so running tests in parallel will cause trouble. -- the environment so running tests in parallel will cause trouble.
setEnv "TASTY_NUM_THREADS" "1" True yarn : damlTypesPath : args <- getArgs
withTempDir $ \tmpDir -> do
oldPath <- getSearchPath oldPath <- getSearchPath
javaPath <- locateRunfiles "local_jdk/bin" javaPath <- locateRunfiles "local_jdk/bin"
mvnPath <- locateRunfiles "mvn_dev_env/bin" mvnPath <- locateRunfiles "mvn_dev_env/bin"
tarPath <- locateRunfiles "tar_dev_env/bin" tarPath <- locateRunfiles "tar_dev_env/bin"
yarnPath <- takeDirectory <$> locateRunfiles (mainWorkspace </> yarn)
damlTypesDir <-
if isWindows
then pure damlTypesPath -- Not available.
else locateRunfiles (mainWorkspace </> damlTypesPath)
-- NOTE: `COMSPEC` env. variable on Windows points to cmd.exe, which is required to be present -- NOTE: `COMSPEC` env. variable on Windows points to cmd.exe, which is required to be present
-- on the PATH as mvn.cmd executes cmd.exe -- on the PATH as mvn.cmd executes cmd.exe
mbComSpec <- getEnv "COMSPEC" mbComSpec <- getEnv "COMSPEC"
let mbCmdDir = takeDirectory <$> mbComSpec let mbCmdDir = takeDirectory <$> mbComSpec
let damlDir = tmpDir </> "daml" let damlDir = tmpDir </> "daml"
withEnv withArgs args (withEnv
[ ("DAML_HOME", Just damlDir) [ ("DAML_HOME", Just damlDir)
, ("PATH", Just $ intercalate [searchPathSeparator] $ ((damlDir </> "bin") : tarPath : javaPath : mvnPath : oldPath) ++ maybeToList mbCmdDir) , ("PATH", Just $ intercalate [searchPathSeparator] $ ((damlDir </> "bin") : tarPath : javaPath : mvnPath : yarnPath : oldPath) ++ maybeToList mbCmdDir)
] $ defaultMain (tests damlDir tmpDir) ] $ defaultMain (tests damlDir tmpDir damlTypesDir))
tests :: FilePath -> FilePath -> TestTree tests :: FilePath -> FilePath -> FilePath -> TestTree
tests damlDir tmpDir = testGroup "Integration tests" tests damlDir tmpDir damlTypesDir = testGroup "Integration tests"
[ testCase "install" $ do [ testCase "install" $ do
releaseTarball <- locateRunfiles (mainWorkspace </> "release" </> "sdk-release-tarball.tar.gz") releaseTarball <- locateRunfiles (mainWorkspace </> "release" </> "sdk-release-tarball.tar.gz")
createDirectory tarballDir createDirectory tarballDir
@ -78,7 +88,7 @@ tests damlDir tmpDir = testGroup "Integration tests"
, quickstartTests quickstartDir mvnDir , quickstartTests quickstartDir mvnDir
, cleanTests cleanDir , cleanTests cleanDir
, deployTest deployDir , deployTest deployDir
, codegenTests codegenDir , codegenTests codegenDir damlTypesDir
] ]
where quickstartDir = tmpDir </> "q-u-i-c-k-s-t-a-r-t" where quickstartDir = tmpDir </> "q-u-i-c-k-s-t-a-r-t"
cleanDir = tmpDir </> "clean" cleanDir = tmpDir </> "clean"
@ -474,12 +484,15 @@ cleanTests baseDir = testGroup "daml clean"
] ]
-- | Check we can generate language bindings. -- | Check we can generate language bindings.
codegenTests :: FilePath -> TestTree codegenTests :: FilePath -> FilePath -> TestTree
codegenTests codegenDir = testGroup "daml codegen" codegenTests codegenDir damlTypes = testGroup "daml codegen" (
[ codegenTestFor "ts" Nothing [ codegenTestFor "java" Nothing
, codegenTestFor "java" Nothing
, codegenTestFor "scala" (Just "com.cookiemonster.nomnomnom") , codegenTestFor "scala" (Just "com.cookiemonster.nomnomnom")
] ] ++
-- The 'daml-types' NPM package is not available on Windows which
-- is required by 'daml2ts'.
[ codegenTestFor "ts" Nothing | not isWindows ]
)
where where
codegenTestFor :: String -> Maybe String -> TestTree codegenTestFor :: String -> Maybe String -> TestTree
codegenTestFor lang namespace = codegenTestFor lang namespace =
@ -490,8 +503,22 @@ codegenTests codegenDir = testGroup "daml codegen"
callCommandQuiet $ unwords ["daml new", projectDir, "skeleton"] callCommandQuiet $ unwords ["daml new", projectDir, "skeleton"]
withCurrentDirectory projectDir $ do withCurrentDirectory projectDir $ do
callCommandQuiet "daml build" callCommandQuiet "daml build"
let darFile = projectDir</> ".daml/dist/proj-" ++ lang ++ "-0.0.1.dar" let darFile = projectDir </> ".daml/dist/proj-" ++ lang ++ "-0.0.1.dar"
outDir = projectDir</> "generated" </> lang outDir = projectDir </> "generated" </> lang
when (lang == "ts") $ do
createDirectoryIfMissing True "generated"
withCurrentDirectory "generated" $ do
-- SDK version is 0.0.0; daml2ts needs
-- 'daml-types' to be here in the filesystem...
copyDirectory damlTypes "daml-types"
BSL.writeFile "package.json" $ encode (
-- ... and this package.json so it can find it.
object
[ "private" .= True
, "workspaces" .= [T.pack lang]
, "resolutions" .= HMS.fromList ([("@daml/types", "file:daml-types")] :: [(T.Text, T.Text)])
]
)
callCommandQuiet $ callCommandQuiet $
unwords [ "daml", "codegen", lang unwords [ "daml", "codegen", lang
, darFile ++ maybe "" ("=" ++) namespace , darFile ++ maybe "" ("=" ++) namespace

View File

@ -21,7 +21,7 @@ Usage
``daml2ts`` is invoked via the DAML SDK assistant. ``daml2ts`` is invoked via the DAML SDK assistant.
In outline, the command to generate TypeScript from DAML is ``daml codegen ts DAR -o OUTDIR main-package-name=PACKAGE`` where ``DAR`` is the path to a DAR file (generated via ``daml build``), ``OUTDIR`` is a directory where you want the TypeScript to be written and ``PACKAGE`` is a desired TypeScript package name. In outline, the command to generate TypeScript from DAML is ``daml codegen ts DAR -o OUTDIR`` where ``DAR`` is the path to a DAR file (generated via ``daml build``), ``OUTDIR`` is a directory where you want the TypeScript to be written and ``PACKAGE`` is a desired TypeScript package name.
Here's a complete example that generates TypeScript from a project produced from the standard "skeleton" template. Here's a complete example that generates TypeScript from a project produced from the standard "skeleton" template.
@ -31,15 +31,15 @@ Here's a complete example that generates TypeScript from a project produced from
daml new my-proj skeleton # Create a new project based off the skeleton template daml new my-proj skeleton # Create a new project based off the skeleton template
cd my-proj # Enter the newly created project directory cd my-proj # Enter the newly created project directory
daml build # Compile the project's DAML files into a DAR daml build # Compile the project's DAML files into a DAR
daml codegen ts .daml/dist/my-proj-0.0.1.dar -o generated/ts --main-package-name=my-proj # Generate Typescript from the DAR daml codegen ts .daml/dist/my-proj-0.0.1.dar -o daml2ts # Generate script bindings from the DAR
- On execution of these commands: - On execution of these commands:
- The directory ``my-proj/generated/ts`` contains the generated TypeScript source files; - The directory ``my-proj/daml2ts`` contains generated TypeScript and Javascript artifacts;
- The files are arranged into directories; - The files are arranged into directories;
- One of those directories will be named as per the ``PACKAGE`` argument and will contain the TypeScript definitions corresponding to the DAML files in the project; - One of those directories will be named as per the ``PACKAGE`` argument and will contain the definitions corresponding to the DAML files in the project;
- For example, ``generated/ts/my-proj/Main.ts`` contains the TypeScript definitions for ``daml/Main.daml``; - For example, ``generated/ts/my-proj/Main.ts`` contains the definitions for ``daml/Main.daml``;
- The remaining directories contain supporting TypeScript corresponding to modules of the DAML standard library; - The remaining directories correspond to modules of the DAML standard library;
- Those directories have numeric names (the names are hashes of the DAML-LF package they are derived from). - Those directories have numeric names (the names are hashes of the DAML-LF package they are derived from).
To get a quickstart idea of how to use what has been generated, you may wish to jump to the `Templates and choices`_ section and return to the reference material that follows as needed. To get a quickstart idea of how to use what has been generated, you may wish to jump to the `Templates and choices`_ section and return to the reference material that follows as needed.

View File

@ -19,6 +19,7 @@ da_haskell_binary(
"hashable", "hashable",
"lens", "lens",
"optparse-applicative", "optparse-applicative",
"process",
"text", "text",
"unordered-containers", "unordered-containers",
"zip-archive", "zip-archive",

View File

@ -3,6 +3,7 @@
{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingStrategies #-}
module TsCodeGenMain (main) where module TsCodeGenMain (main) where
import DA.Directory
import qualified DA.Daml.LF.Proto3.Archive as Archive import qualified DA.Daml.LF.Proto3.Archive as Archive
import qualified DA.Daml.LF.Reader as DAR import qualified DA.Daml.LF.Reader as DAR
import qualified DA.Pretty import qualified DA.Pretty
@ -20,7 +21,6 @@ import Data.Aeson hiding (Options)
import Data.Aeson.Encode.Pretty import Data.Aeson.Encode.Pretty
import Data.Hashable import Data.Hashable
import Control.Exception
import Control.Monad.Extra import Control.Monad.Extra
import DA.Daml.LF.Ast import DA.Daml.LF.Ast
import DA.Daml.LF.Ast.Optics import DA.Daml.LF.Ast.Optics
@ -30,11 +30,12 @@ import Data.Graph
import Data.Maybe import Data.Maybe
import Data.Bifoldable import Data.Bifoldable
import Options.Applicative import Options.Applicative
import System.IO.Error
import System.Directory import System.Directory
import System.FilePath hiding ((<.>), (</>)) import System.FilePath hiding ((<.>), (</>))
import System.FilePath.Posix((</>)) -- Make sure we generate / on all platforms. import System.FilePath.Posix((</>)) -- Make sure we generate / on all platforms.
import qualified System.FilePath as FP import qualified System.FilePath as FP
import System.Process
import System.Exit
import DA.Daml.Project.Consts import DA.Daml.Project.Consts
import DA.Daml.Project.Types import DA.Daml.Project.Types
@ -54,10 +55,7 @@ configConsts sdkVersion = ConfigConsts
, (NpmPackageName "@daml/types", NpmPackageVersion sdkVersion) , (NpmPackageName "@daml/types", NpmPackageVersion sdkVersion)
] ]
, pkgDevDependencies = HMS.fromList , pkgDevDependencies = HMS.fromList
[ (NpmPackageName "@typescript-eslint/eslint-plugin", NpmPackageVersion "2.11.0") [ (NpmPackageName "typescript", NpmPackageVersion "~3.7.3")
, (NpmPackageName "@typescript-eslint/parser", NpmPackageVersion "2.11.0")
, (NpmPackageName "eslint", NpmPackageVersion "^6.7.2")
, (NpmPackageName "typescript", NpmPackageVersion "~3.7.3")
] ]
, pkgScripts = HMS.fromList , pkgScripts = HMS.fromList
[ (ScriptName "build", Script "tsc") [ (ScriptName "build", Script "tsc")
@ -80,7 +78,7 @@ newtype Script = Script {unScript :: T.Text}
data Options = Options data Options = Options
{ optInputDars :: [FilePath] { optInputDars :: [FilePath]
, optOutputDir :: FilePath , optOutputDir :: FilePath
, optInputPackageJson :: FilePath , optInputPackageJson :: Maybe FilePath -- Deprecated.
, optScope :: Scope -- Defaults to 'daml.js'. , optScope :: Scope -- Defaults to 'daml.js'.
} }
@ -95,12 +93,11 @@ optionsParser = Options
<> metavar "DIR" <> metavar "DIR"
<> help "Output directory for the generated packages" <> help "Output directory for the generated packages"
) )
<*> strOption <*> optional (strOption
( short 'p' ( short 'p'
<> metavar "PACKAGE-JSON" <> metavar "PACKAGE-JSON"
<> value "package.json" <> help "This flag is deprecated and ignored"
<> help "Path to a 'package.json' to update (or create if missing)" ))
)
<*> (Scope . ("@" <>) <$> strOption <*> (Scope . ("@" <>) <$> strOption
( short 's' ( short 's'
<> metavar "SCOPE" <> metavar "SCOPE"
@ -159,6 +156,8 @@ mergePackageMap ps = foldM merge Map.empty ps
main :: IO () main :: IO ()
main = do main = do
opts@Options{..} <- execParser optionsParserInfo opts@Options{..} <- execParser optionsParserInfo
when (isJust optInputPackageJson) $
putStrLn "WARNING: Use of the flag '-p' is deprecated. This flag is ignored."
sdkVersionOrErr <- DATypes.parseVersion . T.pack . fromMaybe "0.0.0" <$> getSdkVersionMaybe sdkVersionOrErr <- DATypes.parseVersion . T.pack . fromMaybe "0.0.0" <$> getSdkVersionMaybe
sdkVersion <- case sdkVersionOrErr of sdkVersion <- case sdkVersionOrErr of
Left _ -> fail "Invalid SDK version" Left _ -> fail "Invalid SDK version"
@ -177,13 +176,13 @@ main = do
Just pkgName -> unPackageName pkgName <> " (hash: " <> id <> ")" Just pkgName -> unPackageName pkgName <> " (hash: " <> id <> ")"
T.putStrLn $ "Generating " <> pkgDesc T.putStrLn $ "Generating " <> pkgDesc
daml2ts Daml2TsParams{..} daml2ts Daml2TsParams{..}
setupWorkspace optInputPackageJson optOutputDir dependencies buildPackages sdkVersion optScope optOutputDir dependencies
packageNameText :: PackageId -> Maybe PackageName -> T.Text packageNameText :: PackageId -> Maybe PackageName -> T.Text
packageNameText pkgId mbPkgIdent = maybe (unPackageId pkgId) unPackageName mbPkgIdent packageNameText pkgId mbPkgIdent = maybe (unPackageId pkgId) unPackageName mbPkgIdent
newtype Scope = Scope T.Text newtype Scope = Scope {unScope :: T.Text}
newtype Dependency = Dependency {unDependency :: T.Text} deriving (Eq, Ord) newtype Dependency = Dependency {unDependency :: T.Text} deriving (Eq, Ord)
data Daml2TsParams = Daml2TsParams data Daml2TsParams = Daml2TsParams
{ opts :: Options -- cli args { opts :: Options -- cli args
@ -698,67 +697,47 @@ writePackageJson packageDir sdkVersion (Scope scope) depends =
, "main" .= ("lib/index.js" :: T.Text) , "main" .= ("lib/index.js" :: T.Text)
, "types" .= ("lib/index.d.ts" :: T.Text) , "types" .= ("lib/index.d.ts" :: T.Text)
, "description" .= ("Generated by daml2ts" :: T.Text) , "description" .= ("Generated by daml2ts" :: T.Text)
, "dependencies" .= (pkgDependencies config <> dependencies) , "dependencies" .= dependencies
, "devDependencies" .= pkgDevDependencies config
, "scripts" .= pkgScripts config , "scripts" .= pkgScripts config
] ]
where where
config = configConsts sdkVersion config = configConsts sdkVersion
-- This type describes the format of a "top-level" 'package.json'. We buildPackages :: SdkVersion -> Scope -> FilePath -> [(T.Text, [Dependency])] -> IO ()
-- expect such files to have the format buildPackages sdkVersion optScope optOutputDir dependencies = do
-- {
-- "workspaces: [
-- "path/to/foo",
-- "path/to/bar",
-- ...
-- ],
-- ... perhaps other stuff ...
-- }
data PackageJson = PackageJson
{ workspaces :: [T.Text]
, otherFields :: Object
}
deriving Show
instance Semigroup PackageJson where
l <> r = PackageJson (workspaces l <> workspaces r) (otherFields l <> otherFields r)
instance Monoid PackageJson where
mempty = PackageJson mempty mempty
instance FromJSON PackageJson where
parseJSON (Object v) = PackageJson
<$> v .: "workspaces"
<*> pure (HMS.delete "workspaces" v)
parseJSON _ = mzero
instance ToJSON PackageJson where
toJSON PackageJson{..} = Object (HMS.insert "workspaces" (toJSON workspaces) otherFields)
-- Read the provided 'package.json'; transform it to include the
-- provided workspaces; write it back to disk.
setupWorkspace :: FilePath -> FilePath -> [(T.Text, [Dependency])] -> IO ()
setupWorkspace optInputPackageJson optOutputDir dependencies = do
let (g, nodeFromVertex) = graphFromEdges' let (g, nodeFromVertex) = graphFromEdges'
(map (\(a, ds) -> (a, a, map unDependency ds)) dependencies) (map (\(a, ds) -> (a, a, map unDependency ds)) dependencies)
ps = map (fst3 . nodeFromVertex) $ reverse (topSort g) pkgs = map (T.unpack . fst3 . nodeFromVertex) $ reverse (topSort g)
-- Topologically order our packages. withCurrentDirectory optOutputDir $ do
outBaseDir = T.pack $ takeFileName optOutputDir BSL.writeFile "package.json" $ encodePretty packageJson
-- The leaf directory of the output directory (e.g. often 'daml2ts'). callProcessSilent "yarn" ["install", "--pure-lockfile"]
let ourWorkspaces = map ((outBaseDir <> "/") <>) ps createDirectoryIfMissing True $ "node_modules" </> scope
mbBytes <- catchJust (guard . isDoesNotExistError) mapM_ build pkgs
(Just <$> BSL.readFile optInputPackageJson) (const $ pure Nothing) removeFile "package.json" -- Any subsequent runs will regenerate it.
packageJson <- case mbBytes of -- We don't remove 'node_modules' : subsequent runs can benefit from caching.
Nothing -> pure mempty
Just bytes ->
case eitherDecode @PackageJson bytes of
Left msg -> fail $ "Error : '" <> optInputPackageJson <> "' : " <> msg
Right packageJson -> pure packageJson
transformAndWrite ourWorkspaces outBaseDir packageJson
where where
transformAndWrite :: [T.Text] -> T.Text -> PackageJson -> IO () packageJson :: Value
transformAndWrite ourWorkspaces outBaseDir packageJson = do packageJson = object
let keepWorkspaces = filter (not . T.isPrefixOf outBaseDir) $ workspaces packageJson [ "name" .= ("daml2ts" :: T.Text)
-- Old versions of our packages should be removed. , "version" .= version
allWorkspaces = ourWorkspaces ++ keepWorkspaces , "dependencies" .= pkgDependencies config
-- Our packages need to come before any other existing packages. , "devDependencies" .= pkgDevDependencies config
BSL.writeFile optInputPackageJson $ encodePretty packageJson{workspaces=allWorkspaces} ]
putStrLn $ "'" <> optInputPackageJson <> "' created or updated."
scope = T.unpack $ unScope optScope
config = configConsts version
version = versionToText sdkVersion
build :: String -> IO ()
build pkg = do
putStrLn $ "Building " <> pkg
callProcessSilent "yarn" ["run", "tsc", "--project", pkg </> "tsconfig.json"]
copyDirectory pkg $ "node_modules" </> scope </> pkg
callProcessSilent :: FilePath -> [String] -> IO ()
callProcessSilent cmd args = do
(exitCode, _, err) <- readProcessWithExitCode cmd args ""
unless (exitCode == ExitSuccess) $ do
putStrLn $ "Failure: Command \"" <> cmd <> " " <> unwords args <> "\" exited with " <> show exitCode
putStrLn err
exitFailure

View File

@ -30,7 +30,7 @@ daml_compile(
# - 'ts/codegen/tests/daml/*' # - 'ts/codegen/tests/daml/*'
# The DAML that goes into the DAR ('build-and-lint-1.0.0.dar'); # The DAML that goes into the DAR ('build-and-lint-1.0.0.dar');
# - 'ts/codegen/tests/ts/*' # - 'ts/codegen/tests/ts/*'
# A skeleton yarn workspaces root containing a TypeScript package # A yarn workspaces root containing a single TypeScript package
# 'build-and-lint-test' which houses the test driver 'test.ts'. # 'build-and-lint-test' which houses the test driver 'test.ts'.
# #
# 'build-and-lint' creates a temp directory ('root' say), and uses the # 'build-and-lint' creates a temp directory ('root' say), and uses the
@ -43,10 +43,10 @@ daml_compile(
# __tests__/ # __tests__/
# test.ts # test.ts
# daml2ts/ # daml2ts/
# build-and-lint-1.0.0/ -- The TypeScript generated from 'build-and-lint-1.0.0.dar' # build-and-lint-1.0.0/ -- The artifacts generated of 'build-and-lint-1.0.0.dar'
# 057eed/ # 057eed/
# ... # ...
# package.json -- Enumeration of the workspaces : build-and-lint, daml2ts/build-and-lint-1.0.0, ... # package.json -- Singleton package workspace : build-and-lint-test
# node_modules/ # node_modules/
# @daml/ # @daml/
# types/ # types/
@ -54,8 +54,8 @@ daml_compile(
# ... # ...
# #
# In more detail, the actions of 'build-and-lint' are to: # In more detail, the actions of 'build-and-lint' are to:
# - Invoke 'daml2ts' on 'build-and-lint-1.0.0.dar' produce TypeScript bindings ( # - Invoke 'daml2ts' on 'build-and-lint-1.0.0.dar' to produce bindings
# in the 'daml2ts' directory); # (in the 'daml2ts' directory);
# - Invoke from 'root': # - Invoke from 'root':
# - 'yarn workspaces run install'; # - 'yarn workspaces run install';
# - 'yarn workspaces run build'; # - 'yarn workspaces run build';
@ -136,12 +136,16 @@ da_haskell_test(
"@davl//:released/davl-v5.dar", "@davl//:released/davl-v5.dar",
], ],
hackage_deps = [ hackage_deps = [
"aeson",
"base", "base",
"bytestring",
"extra", "extra",
"filepath", "filepath",
"process", "process",
"tasty", "tasty",
"tasty-hunit", "tasty-hunit",
"text",
"unordered-containers",
], ],
main_function = "DA.Test.Daml2Ts.main", main_function = "DA.Test.Daml2Ts.main",
src_strip_prefix = "src", src_strip_prefix = "src",

View File

@ -57,11 +57,14 @@ cp -rL $DAML_LEDGER/* $TMP_DAML_LEDGER
cd $TMP_DIR cd $TMP_DIR
$DAML2TS -o daml2ts $DAR -p $TMP_DIR/package.json # Call daml2ts.
$YARN install --frozen-lockfile PATH=`dirname $YARN`:$PATH $DAML2TS -o daml2ts $DAR
$YARN workspaces run build
$YARN workspaces run lint
# The previous step provides all the daml2ts Javascript needed by the
# build-and-lint-test workspace.
$YARN install --pure-lockfile > /dev/null 2>&1
$YARN workspaces run build # Build it.
$YARN workspaces run lint # No great value in this but nonetheless, lint it.
# Invoke 'yarn test' in the 'build-and-lint-test' package # Invoke 'yarn test' in the 'build-and-lint-test' package
# directory. Control is thereby passed to # directory. Control is thereby passed to
# 'language-support/ts/codegen/tests/ts/build-and-lint-test/src/__tests__/test.ts'. # 'language-support/ts/codegen/tests/ts/build-and-lint-test/src/__tests__/test.ts'.

View File

@ -15,18 +15,28 @@ import qualified DA.Daml.LF.Ast.Version as LF
import DA.Directory import DA.Directory
import Data.Maybe import Data.Maybe
import Data.List.Extra import Data.List.Extra
import qualified Data.Text.Extended as T
import qualified Data.ByteString.Lazy as BSL
import qualified Data.HashMap.Strict as HMS
import Data.Aeson
import Test.Tasty import Test.Tasty
import Test.Tasty.HUnit import Test.Tasty.HUnit
main :: IO () main :: IO ()
main = do main = do
setEnv "TASTY_NUM_THREADS" "1" True setEnv "TASTY_NUM_THREADS" "1" True
-- We manipulate global state via the working directory and
-- the environment so running tests in parallel will cause trouble.
yarnPath : damlTypesPath : args <- getArgs
damlc <- locateRunfiles (mainWorkspace </> "compiler" </> "damlc" </> exe "damlc") damlc <- locateRunfiles (mainWorkspace </> "compiler" </> "damlc" </> exe "damlc")
daml2ts <- locateRunfiles (mainWorkspace </> "language-support" </> "ts" </> "codegen" </> exe "daml2ts") daml2ts <- locateRunfiles (mainWorkspace </> "language-support" </> "ts" </> "codegen" </> exe "daml2ts")
davl <- locateRunfiles ("davl" </> "released")
yarnPath : damlTypesPath : args <- getArgs
yarn <- locateRunfiles (mainWorkspace </> yarnPath) yarn <- locateRunfiles (mainWorkspace </> yarnPath)
damlTypes <- (</> damlTypesPath) <$> getCurrentDirectory damlTypes <- locateRunfiles (mainWorkspace </> damlTypesPath)
davl <- locateRunfiles ("davl" </> "released")
-- TODO (SF,2020-03-24): Factor out 'withEnv' from
-- 'DA/DamlAssistant/Tests.hs' into a library function and use it here.
oldPath <- getSearchPath
setEnv "PATH" (intercalate [searchPathSeparator] $ takeDirectory yarn : oldPath) True
withArgs args (defaultMain $ tests damlTypes yarn damlc daml2ts davl) withArgs args (defaultMain $ tests damlTypes yarn damlc daml2ts davl)
-- It may help to keep in mind for the following tests, this quick -- It may help to keep in mind for the following tests, this quick
@ -85,8 +95,8 @@ tests damlTypes yarn damlc daml2ts davl = testGroup "daml2ts tests"
step "daml build..." step "daml build..."
buildProject ["-o", ".daml" </> "dist" </> "elmo-1.0.dar"] buildProject ["-o", ".daml" </> "dist" </> "elmo-1.0.dar"]
step "daml2ts..." step "daml2ts..."
setupWorkspace copyDirectory damlTypes "daml-types"
(exitCode, _, err) <- readProcessWithExitCode daml2ts ([groverDar, elmoDar] ++ ["-o", daml2tsDir, "-p", here </> "package.json"]) "" (exitCode, _, err) <- readProcessWithExitCode daml2ts ([groverDar, elmoDar] ++ ["-o", daml2tsDir]) ""
assertBool "A duplicate name for different packages error was expected." (exitCode /= ExitSuccess && isJust (stripInfix "Duplicate name 'grover-1.0' for different packages detected" err)) assertBool "A duplicate name for different packages error was expected." (exitCode /= ExitSuccess && isJust (stripInfix "Duplicate name 'grover-1.0' for different packages detected" err))
, testCaseSteps "Different name, same package test" $ \step -> withTempDir $ \here -> do , testCaseSteps "Different name, same package test" $ \step -> withTempDir $ \here -> do
@ -133,47 +143,18 @@ tests damlTypes yarn damlc daml2ts davl = testGroup "daml2ts tests"
buildProject [] buildProject []
withCurrentDirectory here $ do withCurrentDirectory here $ do
step "daml2ts..." step "daml2ts..."
setupWorkspace copyDirectory damlTypes "daml-types"
(exitCode, _, err) <- readProcessWithExitCode daml2ts ([groverDar, superGroverDar] ++ ["-o", daml2tsDir, "-p", here </> "package.json"]) "" writePackageJson
(exitCode, _, err) <- readProcessWithExitCode daml2ts ([groverDar, superGroverDar] ++ ["-o", daml2tsDir]) ""
assertBool "A different names for same package error was expected." (exitCode /= ExitSuccess && isJust (stripInfix "Different names ('grover-1.0' and 'super-grover-1.0') for the same package detected" err)) assertBool "A different names for same package error was expected." (exitCode /= ExitSuccess && isJust (stripInfix "Different names ('grover-1.0' and 'super-grover-1.0') for the same package detected" err))
, testCaseSteps "Bad package.json test" $ \step -> withTempDir $ \here -> do
let grover = here </> "grover"
groverDaml = grover </> "daml"
daml2tsDir = here </> "daml2ts"
groverDar = grover </> ".daml" </> "dist" </> "grover-1.0.dar"
createDirectoryIfMissing True groverDaml
withCurrentDirectory grover $ do
writeFileUTF8 (groverDaml </> "Grover.daml") $ unlines
[ "module Grover where"
, "template Grover"
, " with puppeteer : Party"
, " where"
, " signatory puppeteer"
, " choice Grover_GoSuper: ContractId Grover"
, " controller puppeteer"
, " do"
, " return self"
]
writeDamlYaml "grover" ["Grover"] ["daml-prim", "daml-stdlib"] Nothing
step "daml build..."
buildProject []
withCurrentDirectory here $ do
step "daml2ts..."
setupWorkspace
writeFileUTF8 (here </> "package.json") .
replace " \"@daml/types\": \"file:daml-types\""
" \"@daml/types\": \"file:daml-types\","
=<< readFileUTF8' (here </> "package.json")
(exitCode, _, err) <- readProcessWithExitCode daml2ts ([groverDar] ++ ["-o", daml2tsDir]) ""
assertBool "An error decoding package.json was expected." (exitCode /= ExitSuccess && isJust (stripInfix "'package.json' : Error in $: Failed reading: satisfy. Expecting object value)" err))
, testCaseSteps "Same package, same name test" $ \step -> withTempDir $ \here -> do , testCaseSteps "Same package, same name test" $ \step -> withTempDir $ \here -> do
let grover = here </> "grover" let grover = here </> "grover"
groverDaml = grover </> "daml" groverDaml = grover </> "daml"
daml2tsDir = here </> "daml2ts" daml2tsDir = here </> "daml2ts"
groverTs = daml2tsDir </> "grover-1.0" groverTs = daml2tsDir </> "grover-1.0"
groverTsSrc = groverTs </> "src" groverTsSrc = groverTs </> "src"
groverTsLib = groverTs </> "lib"
groverDar = grover </> ".daml" </> "dist" </> "grover-1.0.dar" groverDar = grover </> ".daml" </> "dist" </> "grover-1.0.dar"
createDirectoryIfMissing True groverDaml createDirectoryIfMissing True groverDaml
withCurrentDirectory grover $ do withCurrentDirectory grover $ do
@ -193,55 +174,65 @@ tests damlTypes yarn damlc daml2ts davl = testGroup "daml2ts tests"
buildProject [] buildProject []
withCurrentDirectory here $ do withCurrentDirectory here $ do
step "daml2ts..." step "daml2ts..."
setupWorkspace writePackageJson
daml2tsProject [groverDar, groverDar] daml2tsDir (here </> "package.json") copyDirectory damlTypes "daml-types"
daml2tsProject [groverDar, groverDar] daml2tsDir
assertFileExists (groverTsSrc </> "Grover.ts") assertFileExists (groverTsSrc </> "Grover.ts")
assertFileExists (groverTsLib </> "Grover.js")
assertFileExists (groverTsLib </> "Grover.d.ts")
assertFileExists (groverTsSrc </> "packageId.ts") assertFileExists (groverTsSrc </> "packageId.ts")
assertFileExists (groverTsLib </> "packageId.js")
assertFileExists (groverTsLib </> "packageId.d.ts")
, testCaseSteps "DAVL test" $ \step -> withTempDir $ \here -> do , testCaseSteps "DAVL test" $ \step -> withTempDir $ \here -> do
let daml2tsDir = here </> "daml2ts" let daml2tsDir = here </> "daml2ts"
withCurrentDirectory here $ do withCurrentDirectory here $ do
copyDirectory damlTypes "daml-types"
step "daml2ts..." step "daml2ts..."
-- Call daml2ts once without a 'package.json'. writePackageJson
callProcessSilent daml2ts $ callProcessSilent daml2ts $
[ davl </> "davl-v4.dar" [ davl </> "davl-v4.dar"
, davl </> "davl-v5.dar" , davl </> "davl-v5.dar"
, davl </> "davl-upgrade-v4-v5.dar" ] ++ , davl </> "davl-upgrade-v4-v5.dar" ] ++
["-o", daml2tsDir] ["-o", daml2tsDir]
assertFileExists (here </> "package.json")
-- Overwrite the 'package.json' that daml2ts generated because
-- we need to adjust module resolution for @daml/types and
-- @daml/ledger.
setupWorkspace
-- Call daml2ts again which will this time update 'package.json'.
callProcessSilent daml2ts $
[ davl </> "davl-v4.dar"
, davl </> "davl-v5.dar"
, davl </> "davl-upgrade-v4-v5.dar" ] ++
["-o", daml2tsDir] -- There's no need to pass '-p
-- here/package.json' but we could and it would mean the same.
assertFileExists (daml2tsDir </> "davl-0.0.4" </> "src" </> "DAVL.ts") assertFileExists (daml2tsDir </> "davl-0.0.4" </> "src" </> "DAVL.ts")
assertFileExists (daml2tsDir </> "davl-0.0.5" </> "src" </> "DAVL.ts")
assertFileExists (daml2tsDir </> "davl-upgrade-v4-v5-0.0.5" </> "src" </> "Upgrade.ts")
step "yarn install..."
yarnProject ["install"]
step "yarn workspaces run build..."
yarnProject ["workspaces", "run", "build"]
assertFileExists (daml2tsDir </> "davl-0.0.4" </> "lib" </> "DAVL.js") assertFileExists (daml2tsDir </> "davl-0.0.4" </> "lib" </> "DAVL.js")
assertFileExists (daml2tsDir </> "davl-0.0.4" </> "lib" </> "DAVL.d.ts")
assertFileExists (daml2tsDir </> "davl-0.0.5" </> "src" </> "DAVL.ts")
assertFileExists (daml2tsDir </> "davl-0.0.5" </> "lib" </> "DAVL.js") assertFileExists (daml2tsDir </> "davl-0.0.5" </> "lib" </> "DAVL.js")
assertFileExists (daml2tsDir </> "davl-0.0.5" </> "lib" </> "DAVL.d.ts")
assertFileExists (daml2tsDir </> "davl-upgrade-v4-v5-0.0.5" </> "src" </> "Upgrade.ts")
assertFileExists (daml2tsDir </> "davl-upgrade-v4-v5-0.0.5" </> "lib" </> "Upgrade.js") assertFileExists (daml2tsDir </> "davl-upgrade-v4-v5-0.0.5" </> "lib" </> "Upgrade.js")
step "yarn workspaces run lint..." assertFileExists (daml2tsDir </> "davl-upgrade-v4-v5-0.0.5" </> "lib" </> "Upgrade.d.ts")
yarnProject ["workspaces", "run", "lint"] step "eslint..."
] withCurrentDirectory daml2tsDir $ do
pkgs <- (\\ ["package.json", "node_modules"]) <$> listDirectory daml2tsDir
BSL.writeFile "package.json" $ encode (
object
[ "private" .= True
, "devDependencies" .= HMS.fromList
([ ("eslint", "^6.7.2")
, ("@typescript-eslint/eslint-plugin", "2.11.0")
, ("@typescript-eslint/parser", "2.11.0")
] :: [(T.Text, T.Text)]
)
, "dependencies" .= HMS.fromList
([ ("@daml/types", "file:../daml-types")
, ("@mojotech/json-type-validation", "^3.1.0")
] :: [(T.Text, T.Text)])
, "workspaces" .= pkgs
, "name" .= ("daml2ts" :: T.Text)
, "version" .= ("0.0.0" :: T.Text)
])
callProcessSilent yarn ["install", "--pure-lockfile"]
callProcessSilent yarn ["workspaces", "run", "lint"]
]
where where
buildProject :: [String] -> IO () buildProject :: [String] -> IO ()
buildProject args = callProcessSilent damlc (["build"] ++ args) buildProject args = callProcessSilent damlc (["build"] ++ args)
daml2tsProject :: [FilePath] -> FilePath -> FilePath -> IO () daml2tsProject :: [FilePath] -> FilePath -> IO ()
daml2tsProject dars outDir packageJson = callProcessSilent daml2ts $ dars ++ ["-o", outDir, "-p", packageJson] daml2tsProject dars outDir = callProcessSilent daml2ts $ dars ++ ["-o", outDir]
yarnProject :: [String] -> IO ()
yarnProject args = callProcessSilent yarn args
callProcessSilent :: FilePath -> [String] -> IO () callProcessSilent :: FilePath -> [String] -> IO ()
callProcessSilent cmd args = do callProcessSilent cmd args = do
@ -252,19 +243,6 @@ tests damlTypes yarn damlc daml2ts davl = testGroup "daml2ts tests"
hPutStrLn stderr $ unlines ["stderr:", err] hPutStrLn stderr $ unlines ["stderr:", err]
exitFailure exitFailure
setupWorkspace :: IO ()
setupWorkspace = do
copyDirectory damlTypes "daml-types"
writeFileUTF8 "package.json" $ unlines
[ "{"
, " \"private\": true,"
, " \"workspaces\": [],"
, " \"resolutions\": {"
, " \"@daml/types\": \"file:daml-types\""
, " }"
, "}"
]
writeDamlYaml :: String -> [String] -> [String] -> Maybe LF.Version -> IO () writeDamlYaml :: String -> [String] -> [String] -> Maybe LF.Version -> IO ()
writeDamlYaml mainPackageName exposedModules dependencies mbLfVersion = writeDamlYaml mainPackageName exposedModules dependencies mbLfVersion =
writeFileUTF8 "daml.yaml" $ unlines $ writeFileUTF8 "daml.yaml" $ unlines $
@ -278,5 +256,14 @@ tests damlTypes yarn damlc daml2ts davl = testGroup "daml2ts tests"
[" - " ++ dependency | dependency <- dependencies] ++ [" - " ++ dependency | dependency <- dependencies] ++
["build-options: [--target=" <> LF.renderVersion ver <> "]" | Just ver <- [mbLfVersion]] ["build-options: [--target=" <> LF.renderVersion ver <> "]" | Just ver <- [mbLfVersion]]
writePackageJson :: IO ()
writePackageJson = BSL.writeFile "package.json" $ encode packageJson
where
packageJson = object
[ "private" .= True
, "workspaces" .= (["daml2ts"] :: [T.Text])
, "resolutions" .= HMS.fromList ([("@daml/types", "file:daml-types")] :: [(T.Text, T.Text)])
]
assertFileExists :: FilePath -> IO () assertFileExists :: FilePath -> IO ()
assertFileExists file = doesFileExist file >>= assertBool (file ++ " was not created") assertFileExists file = doesFileExist file >>= assertBool (file ++ " was not created")

View File

@ -5,8 +5,7 @@
"description": "Tests exercising '@daml.js/build-and-lint-1.0.0", "description": "Tests exercising '@daml.js/build-and-lint-1.0.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@daml/ledger": "0.0.0", "@daml/ledger": "file:../daml-ledger",
"@daml/types": "0.0.0",
"@daml.js/build-and-lint-1.0.0": "file:../daml2ts/build-and-lint-1.0.0", "@daml.js/build-and-lint-1.0.0": "file:../daml2ts/build-and-lint-1.0.0",
"p-event": "^4.1.0" "p-event": "^4.1.0"
}, },

View File

@ -1,10 +1,10 @@
{ {
"private": true, "private": true,
"workspaces": [ "workspaces": [
"build-and-lint-test" "build-and-lint-test",
"daml2ts"
], ],
"resolutions": { "resolutions": {
"@daml/types": "file:daml-types", "@daml/types": "file:daml-types"
"@daml/ledger": "file:daml-ledger"
} }
} }