From 365ac2f94c6455ad93fd5192e4369e2aa571fa70 Mon Sep 17 00:00:00 2001 From: Fran <231829+associahedron@users.noreply.github.com> Date: Tue, 2 Jul 2019 11:29:19 +0100 Subject: [PATCH] Use published extension by default in daml studio. (#1965) * Use published extension by default in daml studio. * Implement suggestions. * Add release notes. --- .../daml-helper/src/DamlHelper/Main.hs | 5 +- .../daml-helper/src/DamlHelper/Run.hs | 206 +++++++++++++----- unreleased.rst | 10 +- 3 files changed, 162 insertions(+), 59 deletions(-) diff --git a/daml-assistant/daml-helper/src/DamlHelper/Main.hs b/daml-assistant/daml-helper/src/DamlHelper/Main.hs index fbb8dc268c..b8eab70e9a 100644 --- a/daml-assistant/daml-helper/src/DamlHelper/Main.hs +++ b/daml-assistant/daml-helper/src/DamlHelper/Main.hs @@ -46,8 +46,8 @@ commandParser = where damlStudioCmd = DamlStudio <$> option readReplacement (long "replace" <> - help "Whether an existing extension should be overwritten. ('never', 'newer' or 'always', defaults to newer)" <> - value ReplaceExtNewer + help "Whether an existing extension should be overwritten. ('never', 'newer' or 'always' for bundled extension version, 'published' for official published version of extension, defaults to 'published')" <> + value ReplaceExtPublished ) <*> many (argument str (metavar "ARG")) runJarCmd = RunJar @@ -75,6 +75,7 @@ commandParser = "never" -> Just ReplaceExtNever "newer" -> Just ReplaceExtNewer "always" -> Just ReplaceExtAlways + "published" -> Just ReplaceExtPublished _ -> Nothing runCommand :: Command -> IO () diff --git a/daml-assistant/daml-helper/src/DamlHelper/Run.hs b/daml-assistant/daml-helper/src/DamlHelper/Run.hs index d18216667f..7e7e8bdb61 100644 --- a/daml-assistant/daml-helper/src/DamlHelper/Run.hs +++ b/daml-assistant/daml-helper/src/DamlHelper/Run.hs @@ -34,6 +34,7 @@ import Control.Exception.Safe import Control.Monad import Control.Monad.Extra hiding (fromMaybeM) import Control.Monad.Loops (untilJust) +import Data.Either.Extra import Data.Maybe import Data.List.Extra import qualified Data.ByteString as BS @@ -84,53 +85,165 @@ data ReplaceExtension -- ^ Replace the extension if the current extension is newer. | ReplaceExtAlways -- ^ Always replace the extension. + | ReplaceExtPublished + -- ^ Replace with published extension (the default). + +-- | Run VS Code command with arguments, returning the exit code, stdout & stderr. +runVsCodeCommand :: [String] -> IO (ExitCode, String, String) +runVsCodeCommand args = do + originalEnv <- getEnvironment + let strippedEnv = filter ((`notElem` damlEnvVars) . fst) originalEnv + -- ^ Strip DAML environment variables before calling VSCode, to + -- prevent setting DAML_SDK_VERSION too early. See issue #1666. + commandEnv = addVsCodeToPath strippedEnv + -- ^ Ensure "code" is in PATH before running command. + command = unwords ("code" : args) + process = (shell command) { env = Just commandEnv } + readCreateProcessWithExitCode process "" + +-- | Add VSCode bin path to environment PATH. Only need to add it on Mac, as +-- VSCode is installed in PATH by default on the other platforms. +addVsCodeToPath :: [(String, String)] -> [(String,String)] +addVsCodeToPath env | isMac = + let pathM = lookup "PATH" env + newSearchPath = maybe "" (<> [searchPathSeparator]) pathM <> + "/Applications/Visual\\ Studio\\ Code.app/Contents/Resources/app/bin" + in ("PATH", newSearchPath) : filter ((/= "PATH") . fst) env +addVsCodeToPath env = env + +-- | Directory where bundled extension gets installed. +getVsCodeExtensionsDir :: IO FilePath +getVsCodeExtensionsDir = fmap ( ".vscode/extensions") getHomeDirectory + +-- | Name of VS Code extension in the marketplace. +publishedExtensionName :: String +publishedExtensionName = "DigitalAssetHoldingsLLC.daml" + +-- | Name of VS Code extension bundled with the SDK. Legacy, but also +-- useful in a pinch, in case published extension goes bad. +bundledExtensionName :: String +bundledExtensionName = "da-vscode-daml-extension" + +-- | Status of installed VS Code extensions. +data InstalledExtensions = InstalledExtensions + { bundledExtensionVersion :: Maybe SdkVersion + -- ^ bundled extension version, if installed + , publishedExtensionIsInstalled :: Bool + -- ^ true if published extension is installed + } deriving (Show, Eq) + +-- | Get status of installed VS code extensions. +getInstalledExtensions :: IO InstalledExtensions +getInstalledExtensions = do + extensionsDir <- getVsCodeExtensionsDir + + let bundledExtensionDir = extensionsDir bundledExtensionName + bundledExtensionVersion <- readVersionFile bundledExtensionDir + + (_exitCode, extensionsStr, _err) <- runVsCodeCommand ["--list-extensions"] + let extensions = lines extensionsStr + publishedExtensionIsInstalled = publishedExtensionName `elem` extensions + + pure InstalledExtensions {..} + +-- | Read VERSION file in directory. Returns Nothing on failure. +readVersionFile :: FilePath -> IO (Maybe SdkVersion) +readVersionFile dir = do + versionStrE <- tryIO $ readFileUTF8 (dir "VERSION") + pure $ do + versionStr <- eitherToMaybe versionStrE + eitherToMaybe . parseVersion . T.strip . T.pack $ versionStr runDamlStudio :: ReplaceExtension -> [String] -> IO () runDamlStudio replaceExt remainingArguments = do sdkPath <- getSdkPath - vscodeExtensionsDir <- fmap ( ".vscode/extensions") getHomeDirectory - let vscodeExtensionName = "da-vscode-daml-extension" - let vscodeExtensionSrcDir = sdkPath "studio" - let vscodeExtensionTargetDir = vscodeExtensionsDir vscodeExtensionName - whenM (shouldReplaceExtension replaceExt vscodeExtensionTargetDir) $ - removePathForcibly vscodeExtensionTargetDir - installExtension vscodeExtensionSrcDir vscodeExtensionTargetDir - -- Note that it is important that we use `shell` rather than `proc` here as - -- `proc` will look for `code.exe` in PATH which does not exist. - projectPathM <- getProjectPath - let codeCommand - | isMac = "open -a \"Visual Studio Code\"" - | otherwise = "code" - path = fromMaybe "." projectPathM - command = unwords $ codeCommand : path : remainingArguments + vscodeExtensionsDir <- getVsCodeExtensionsDir + InstalledExtensions {..} <- getInstalledExtensions - -- Strip DAML environment variables before calling vscode. - originalEnv <- getEnvironment - let strippedEnv = filter ((`notElem` damlEnvVars) . fst) originalEnv - process = (shell command) { env = Just strippedEnv } + let bundledExtensionSource = sdkPath "studio" + bundledExtensionTarget = vscodeExtensionsDir bundledExtensionName - exitCode <- withCreateProcess process $ \_ _ _ -> waitForProcess - when (exitCode /= ExitSuccess) $ - hPutStrLn stderr $ - "Failed to launch Visual Studio Code." <> - " See https://code.visualstudio.com/Download for installation instructions." - exitWith exitCode + removeBundledExtension = + when (isJust bundledExtensionVersion) $ + removePathForcibly bundledExtensionTarget -shouldReplaceExtension :: ReplaceExtension -> FilePath -> IO Bool -shouldReplaceExtension replaceExt dir = + removePublishedExtension = + when publishedExtensionIsInstalled $ do + (exitCode, _out, err) <- runVsCodeCommand + ["--uninstall-extension", publishedExtensionName] + when (exitCode /= ExitSuccess) $ do + hPutStrLn stderr . unlines $ + [ err + , "Failed to uninstall published version of DAML Studio." + ] + exitWith exitCode + + installBundledExtension' = + installBundledExtension bundledExtensionSource bundledExtensionTarget + + installPublishedExtension = do + when (not publishedExtensionIsInstalled) $ do + (exitCode, _out, err) <- runVsCodeCommand + ["--install-extension", publishedExtensionName] + when (exitCode /= ExitSuccess) $ do + hPutStr stderr . unlines $ + [ err + , "Failed to install DAML Studio extension from marketplace." + , "Installing bundled DAML Studio extension instead." + ] + installBundledExtension' + + -- First, ensure extension is installed as requested. case replaceExt of - ReplaceExtNever -> pure False - ReplaceExtAlways -> pure True + ReplaceExtNever -> + when (isNothing bundledExtensionVersion) + installPublishedExtension + + ReplaceExtAlways -> do + removePublishedExtension + removeBundledExtension + installBundledExtension' + ReplaceExtNewer -> do - let installedVersionFile = dir "VERSION" - ifM (doesFileExist installedVersionFile) - (do installedVersion <- - requiredE "Failed to parse version of VSCode extension" . parseVersion . T.strip . T.pack =<< - readFileUTF8 installedVersionFile - sdkVersion <- requiredE "Failed to parse SDK version" . parseVersion . T.pack =<< getSdkVersion - pure (sdkVersion > installedVersion)) - (pure True) - -- ^ If the VERSION file does not exist, we must have installed an older version. + sdkVersion <- requiredE "Failed to parse SDK version" + . parseVersion . T.pack =<< getSdkVersion + removePublishedExtension + when (maybe True (< sdkVersion) bundledExtensionVersion) $ do + removeBundledExtension + installBundledExtension' + + ReplaceExtPublished -> do + removeBundledExtension + installPublishedExtension + + -- Then, open visual studio code. + projectPathM <- getProjectPath + let path = fromMaybe "." projectPathM + (exitCode, _out, err) <- runVsCodeCommand (path : remainingArguments) + when (exitCode /= ExitSuccess) $ do + hPutStrLn stderr . unlines $ + [ err + , "Failed to launch DAML studio. Make sure Visual Studio Code is installed." + , "See https://code.visualstudio.com/Download for installation instructions." + ] + exitWith exitCode + +installBundledExtension :: FilePath -> FilePath -> IO () +installBundledExtension src target = do + -- Create .vscode/extensions if it does not already exist. + createDirectoryIfMissing True (takeDirectory target) + catchJust + (guard . isAlreadyExistsError) + install + (const $ pure ()) + -- If we get an exception, we just keep the existing extension. + where + install + | isWindows = do + -- We create the directory to throw an isAlreadyExistsError. + createDirectory target + copyDirectory src target + | otherwise = createDirectoryLink src target runJar :: FilePath -> [String] -> IO () runJar jarPath remainingArguments = do @@ -573,23 +686,6 @@ getProjectConfig = do projectPath <- required "Must be called from within a project" =<< getProjectPath readProjectConfig (ProjectPath projectPath) -installExtension :: FilePath -> FilePath -> IO () -installExtension src target = do - -- Create .vscode/extensions if it does not already exist. - createDirectoryIfMissing True (takeDirectory target) - catchJust - (guard . isAlreadyExistsError) - install - (const $ pure ()) - -- If we get an exception, we just keep the existing extension. - where - install - | isWindows = do - -- We create the directory to throw an isAlreadyExistsError. - createDirectory target - copyDirectory src target - | otherwise = createDirectoryLink src target - -- | `waitForConnectionOnPort sleep port` keeps trying to establish a TCP connection on the given port. -- Between each connection request it calls `sleep`. waitForConnectionOnPort :: IO () -> Int -> IO () diff --git a/unreleased.rst b/unreleased.rst index f912e775cc..8bb4084985 100644 --- a/unreleased.rst +++ b/unreleased.rst @@ -15,9 +15,15 @@ HEAD — ongoing - [Scala bindings] Contract keys are exposed on CreatedEvent. See `#1681 `__. - [Navigator] Contract keys are show in the contract details page. See `#1681 `__. - [DAML Standard Library] **BREAKING CHANGE**: Remove the deprecated modules ``DA.Map``, ``DA.Set``, ``DA.Experimental.Map`` and ``DA.Experimental.Set``. Please use ``DA.Next.Map`` and ``DA.Next.Set`` instead. -- [Sandbox] Fixed an issue when CompletionService returns offsets having inclusive semantics when used for re-subscription. +- [Sandbox] Fixed an issue when CompletionService returns offsets having inclusive semantics when used for re-subscription. See `#1932 `__. - + - [DAML Compiler] The default output path for all artifacts is now in the ``.daml`` directory. In particular, the default output path for .dar files in ``daml build`` is now ``.daml/dist/.dar``. + +- [DAML Studio] DAML Studio is now published as an extension in the Visual Studio Code + marketplace. The ``daml studio`` command will now install the published extension by + default, but will revert to the extension bundled with the DAML SDK if installation + fails. You can get the old default behavior of always using the bundled extension + by running ``daml studio --replace=newer`` or ``daml studio --replace=always`` instead.