DAML REPL - Explicit package imports (#6707)

* DAML REPL - Explicit package imports

changelog_begin
- [DAML REPL] The REPL no longer imports all modules from the main DALFs
  of all specified DARs automatically at start-up. Instead, the REPL
  will only import modules from packages specified on the command-line
  using the `--import` flag.
changelog_end

* Accept package-name or package-id

* REPL test case for --import flag

* DAML REPL use `UnitId` for import packages

Addressing review comment
https://github.com/digital-asset/daml/pull/6707#discussion_r453731353

* DAML REPL Parse package-name/id at CLI

* DAML REPL Simplify unversioned pkgs

Co-authored-by: Andreas Herrmann <andreas.herrmann@tweag.io>
This commit is contained in:
Andreas Herrmann 2020-07-14 13:38:23 +02:00 committed by GitHub
parent 3f2938ecf6
commit ae65f93e01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 100 additions and 16 deletions

View File

@ -13,7 +13,6 @@ import qualified DA.Daml.LF.Ast as LF
import DA.Daml.LF.Ast.Optics (packageRefs)
import qualified DA.Daml.LF.ReplClient as ReplClient
import DA.Daml.LFConversion.UtilGHC
import DA.Daml.Options.Packaging.Metadata
import DA.Daml.Options.Types
import qualified DA.Daml.Preprocessor.Records as Preprocessor
import Data.Bifunctor (first)
@ -21,6 +20,7 @@ import Data.Functor.Alt
import Data.Foldable
import Data.Generics.Uniplate.Data (descendBi)
import Data.Graph
import Data.List (intercalate)
import qualified Data.Map.Strict as Map
import Data.Maybe
import qualified Data.NameMap as NM
@ -40,6 +40,7 @@ import HsPat (Pat(..))
import HscTypes (HscEnv(..))
import Language.Haskell.GhclibParserEx.Parse
import Lexer (ParseResult(..))
import Module (unitIdString)
import OccName (occName, OccSet, elemOccSet, mkOccSet, mkVarOcc)
import Outputable (ppr, showSDoc)
import RdrName (mkRdrUnqual)
@ -183,9 +184,9 @@ parseReplInput input dflags =
-- | Load all packages in the given session.
--
-- Returns the list of modules in the main DALFs.
loadPackages :: ReplClient.Handle -> IdeState -> IO [ImportDecl GhcPs]
loadPackages replClient ideState = do
-- Returns the list of modules in the specified import packages.
loadPackages :: [(LF.PackageName, Maybe LF.PackageVersion)] -> ReplClient.Handle -> IdeState -> IO [ImportDecl GhcPs]
loadPackages importPkgs replClient ideState = do
-- Load packages
Just (PackageMap pkgs) <- runAction ideState (use GeneratePackageMap "Dummy.daml")
Just stablePkgs <- runAction ideState (use GenerateStablePackages "Dummy.daml")
@ -196,19 +197,29 @@ loadPackages replClient ideState = do
hPutStrLn stderr ("Package could not be loaded: " <> show err)
exitFailure
Right _ -> pure ()
-- Determine module names in main DALFs.
md <- readMetadata (toNormalizedFilePath' ".")
-- Determine module names in imported DALFs.
let unversionedPkgs = Map.mapKeys (fst . LF.splitUnitId) pkgs
toUnitId (pkgName, mbVersion) = pkgNameVersion pkgName mbVersion
lookupPkg (pkgName, Nothing) = Map.lookup pkgName unversionedPkgs
lookupPkg (toUnitId -> unitId) = Map.lookup unitId pkgs
importLfPkgs <- forM importPkgs $ \importPkg ->
case lookupPkg importPkg of
Just dalf -> pure $ LF.extPackagePkg $ LF.dalfPackagePkg dalf
Nothing -> do
hPutStrLn stderr $
"Could not find package for import: " <> unitIdString (toUnitId importPkg) <> "\n"
<> "Known packages: " <> intercalate ", " (unitIdString <$> Map.keys pkgs)
exitFailure
pure
[ simpleImportDecl . mkModuleName . T.unpack . LF.moduleNameString $ mod
| dep <- directDependencies md
, let pkg = LF.extPackagePkg $ LF.dalfPackagePkg $ pkgs Map.! dep
| pkg <- importLfPkgs
, mod <- NM.names $ LF.packageModules pkg
]
runRepl :: Options -> ReplClient.Handle -> IdeState -> IO ()
runRepl opts replClient ideState = do
imports <- loadPackages replClient ideState
runRepl :: [(LF.PackageName, Maybe LF.PackageVersion)] -> Options -> ReplClient.Handle -> IdeState -> IO ()
runRepl importPkgs opts replClient ideState = do
imports <- loadPackages importPkgs replClient ideState
let initReplState = ReplState
{ imports = imports
, bindings = []

View File

@ -29,6 +29,7 @@ import DA.Daml.LF.ScenarioServiceClient (readScenarioServiceConfig, withScenario
import qualified DA.Daml.LF.ReplClient as ReplClient
import DA.Daml.Compiler.Validate (validateDar)
import qualified DA.Daml.LF.Ast as LF
import DA.Daml.LF.Ast.Util (splitUnitId)
import qualified DA.Daml.LF.Proto3.Archive as Archive
import DA.Daml.LF.Reader
import DA.Daml.LanguageServer
@ -70,7 +71,7 @@ import Development.IDE.Types.Location
import Development.IDE.Types.Options (clientSupportsProgress)
import "ghc-lib-parser" DynFlags
import GHC.Conc
import "ghc-lib-parser" Module (unitIdString)
import "ghc-lib-parser" Module (unitIdString, stringToUnitId)
import qualified Network.Socket as NS
import Options.Applicative.Extended
import qualified Proto3.Suite as PS
@ -91,6 +92,7 @@ import "ghc-lib" HscStats
import "ghc-lib-parser" HscTypes
import qualified "ghc-lib-parser" Outputable as GHC
import qualified SdkVersion
import "ghc-lib-parser" Util (looksLikePackageName)
--------------------------------------------------------------------------------
-- Commands
@ -261,6 +263,7 @@ cmdRepl numProcessors =
<*> strOption (long "script-lib" <> value "daml-script" <> internal)
-- ^ This is useful for tests and `bazel run`.
<*> many (strArgument (help "DAR to load in the repl" <> metavar "DAR"))
<*> many packageImport
<*> strOption (long "ledger-host" <> help "Host of the ledger API")
<*> strOption (long "ledger-port" <> help "Port of the ledger API")
<*> accessTokenFileFlag
@ -271,6 +274,18 @@ cmdRepl numProcessors =
help "Optional max inbound message size in bytes."
)
<*> timeModeFlag
packageImport = option readPackage $
long "import"
<> short 'i'
<> help "Import modules of these packages into the REPL"
<> metavar "PACKAGE"
where
readPackage = eitherReader $ \s -> do
let pkg@(name, _) = splitUnitId (stringToUnitId s)
strName = T.unpack . LF.unPackageName $ name
unless (looksLikePackageName strName) $
fail $ "Illegal package name: " ++ strName
pure pkg
accessTokenFileFlag = optional . option str $
long "access-token-file"
<> metavar "TOKEN_PATH"
@ -583,14 +598,14 @@ execBuild projectOpts opts mbOutFile incrementalBuild initPkgDb =
execRepl
:: ProjectOpts
-> Options
-> FilePath -> [FilePath]
-> FilePath -> [FilePath] -> [(LF.PackageName, Maybe LF.PackageVersion)]
-> String -> String
-> Maybe FilePath
-> Maybe ReplClient.ClientSSLConfig
-> Maybe ReplClient.MaxInboundMessageSize
-> ReplClient.ReplTimeMode
-> Command
execRepl projectOpts opts scriptDar dars ledgerHost ledgerPort mbAuthToken mbSslConf mbMaxInboundMessageSize timeMode = Command Repl (Just projectOpts) effect
execRepl projectOpts opts scriptDar dars importPkgs ledgerHost ledgerPort mbAuthToken mbSslConf mbMaxInboundMessageSize timeMode = Command Repl (Just projectOpts) effect
where effect = do
-- We change directory so make this absolute
dars <- mapM makeAbsolute dars
@ -619,7 +634,7 @@ execRepl projectOpts opts scriptDar dars ledgerHost ledgerPort mbAuthToken mbSsl
initPackageDb opts (InitPkgDb True)
-- We want diagnostics to go to stdout in the repl.
withDamlIdeState opts logger (hDiagnosticsLogger stdout)
(Repl.runRepl opts replHandle)
(Repl.runRepl importPkgs opts replHandle)
-- | Remove any build artifacts if they exist.
execClean :: ProjectOpts -> Command

View File

@ -521,6 +521,7 @@ da_haskell_test(
visibility = ["//visibility:public"],
deps = [
"//:sdk-version-hs-lib",
"//compiler/daml-lf-ast",
"//compiler/damlc:damlc-lib",
"//compiler/damlc/daml-compiler",
"//compiler/damlc/daml-ide-core",

View File

@ -57,6 +57,11 @@ main = do
staticTimeTests damlc scriptDar testDar getSandboxPort
, withSandbox defaultSandboxConf $ \getSandboxPort ->
noPackageTests damlc scriptDar getSandboxPort
, withSandbox defaultSandboxConf
{ dars = [testDar]
, mbLedgerId = Just testLedgerId
} $ \getSandboxPort ->
importTests damlc scriptDar testDar getSandboxPort
]
withTokenFile :: (IO FilePath -> TestTree) -> TestTree
@ -126,6 +131,8 @@ testConnection damlc scriptDar testDar ledgerPort mbTokenFile mbCaCrt = do
, "--script-lib"
, scriptDar
, testDar
, "--import"
, "repl-test"
]
, [ "--access-token-file=" <> tokenFile | Just tokenFile <- [mbTokenFile] ]
, [ "--cacrt=" <> cacrt | Just cacrt <- [mbCaCrt] ]
@ -188,4 +195,52 @@ testSetTime damlc scriptDar testDar ledgerPort = do
, "--script-lib"
, scriptDar
, testDar
, "--import"
, "repl-test"
]
-- | Test the @--import@ flag
importTests :: FilePath -> FilePath -> FilePath -> IO Int -> TestTree
importTests damlc scriptDar testDar getSandboxPort = testGroup "import"
[ testCase "none" $ do
port <- getSandboxPort
testImport damlc scriptDar testDar port [] False
, testCase "unversioned" $ do
port <- getSandboxPort
testImport damlc scriptDar testDar port ["repl-test"] True
, testCase "versioned" $ do
port <- getSandboxPort
testImport damlc scriptDar testDar port ["repl-test-0.1.0"] True
]
testImport
:: FilePath
-> FilePath
-> FilePath
-> Int
-> [String]
-> Bool
-> Assertion
testImport damlc scriptDar testDar ledgerPort imports successful = do
out <- readCreateProcess cp $ unlines
[ "alice <- allocateParty \"Alice\""
, "debug (T alice alice)"
]
let regexString :: String
regexString
| successful = "^daml> daml> .*: T {proposer = '.*', accepter = '.*'}\ndaml> Goodbye.\n$"
| otherwise = "^daml> daml> File: .*\nHidden: .*\nRange: .*\nSource: .*\nSeverity: DsError\nMessage: .*: error:Data constructor not in scope: T : Party -> Party -> .*\ndaml> Goodbye.\n$"
let regex = makeRegexOpts defaultCompOpt { multiline = False } defaultExecOpt regexString
unless (matchTest regex out) $
assertFailure (show out <> " did not match " <> show regexString <> ".")
where cp = proc damlc $ concat
[ [ "repl"
, "--ledger-host=localhost"
, "--ledger-port"
, show ledgerPort
, "--script-lib"
, scriptDar
, testDar
]
, [ "--import=" <> pkg | pkg <- imports ]
]

View File

@ -11,6 +11,7 @@ import DA.Bazel.Runfiles
import DA.Cli.Damlc.Packaging
import DA.Cli.Output
import DA.Daml.Compiler.Repl
import qualified DA.Daml.LF.Ast as LF
import qualified DA.Daml.LF.ReplClient as ReplClient
import DA.Daml.Options.Types
import DA.Daml.Package.Config
@ -264,7 +265,8 @@ testInteraction replClient serviceOut options ideState steps = do
withBinaryFile stdinFile ReadMode $ \readIn ->
redirectingHandle stdin readIn $ do
Right () <- ReplClient.clearResults replClient
capture_ $ runRepl options replClient ideState
let imports = [(LF.PackageName name, Nothing) | name <- ["repl-test", "repl-test-two"]]
capture_ $ runRepl imports options replClient ideState
-- Write output to a file so we can conveniently read individual characters.
withTempFile $ \clientOutFile -> do
writeFileUTF8 clientOutFile out