{- Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. -} {- Generic indexer for SCIP, "semantic code intelligence protocol": a typed lsif-like indexer format from SourceGraph. See https://github.com/sourcegraph/scip -} module Glean.SCIP.Driver ( ScipIndexerParams(..), runIndexer, processSCIP, LanguageId(..), ) where import Data.Maybe ( fromMaybe ) import Control.Monad import System.Directory import System.FilePath ( (), takeBaseName ) import System.IO.Temp ( withSystemTempDirectory ) import System.Process ( callProcess ) import Text.Printf ( printf ) import Util.Log ( logInfo ) import qualified Data.Aeson as Aeson import qualified Data.ByteString as B import Data.SCIP.Angle ( scipToAngle, LanguageId(..) ) data ScipIndexerParams = ScipIndexerParams { scipBinary :: FilePath , scipArgs :: FilePath -> [String] , scipOutDir :: Maybe FilePath , scipRoot :: FilePath , scipWritesLocal :: Bool -- ^ e.g. rust-analyzer always writes index.scip to repoDir , scipLanguage :: Maybe LanguageId -- ^ a default language if known , scipRustIndexer :: Maybe FilePath } -- | Run a generic SCIP-producing indexer, and convert to a Glean's scip.angle -- database returning a single JSON value that can be sent to the Glean server runIndexer :: ScipIndexerParams -> IO Aeson.Value runIndexer params@ScipIndexerParams{..} = do repoDir <- makeAbsolute scipRoot withDirOrTmp scipOutDir $ \scipDir -> do let scipFile = scipDir "index.scip" runSCIPIndexer params { scipRoot = repoDir } scipFile when scipWritesLocal $ do copyFile (repoDir "index.scip") scipFile removeFile (repoDir "index.scip") case scipRustIndexer of Nothing -> processSCIP scipLanguage False Nothing Nothing scipFile Just rustIndexer -> do let jsonPath = scipDir "index.json" callProcess rustIndexer ["--input", scipFile, "--infer-language", "--output", jsonPath] mJson <- Aeson.decodeFileStrict jsonPath return $ fromMaybe (error "rust indexer did not produce JSON") mJson withDirOrTmp :: Maybe FilePath -> (FilePath -> IO a) -> IO a withDirOrTmp Nothing f = withSystemTempDirectory "glean-scip" f withDirOrTmp (Just dir) f = withCurrentDirectory dir $ f dir -- | Run a SCIP indexer on a repository, put scip dump output into outputFile runSCIPIndexer :: ScipIndexerParams -> FilePath -> IO () runSCIPIndexer ScipIndexerParams{..} outputFile = withCurrentDirectory scipRoot $ do logInfo $ printf "Indexing %s with %s" (takeBaseName scipRoot) scipBinary let args = scipArgs outputFile logInfo $ printf "Running command: %s %s" scipBinary (unwords args) callProcess scipBinary args -- | Convert an scip protobufs encoded file into Glean lsif.angle JSON object processSCIP :: Maybe LanguageId -> Bool -> Maybe FilePath -> Maybe FilePath -> FilePath -> IO Aeson.Value processSCIP mlang inferLanguage mPathPrefix mStripPrefix scipFile = do logInfo $ "Using SCIP from " <> scipFile scipToAngle mlang inferLanguage mPathPrefix mStripPrefix <$> B.readFile scipFile