packages feed

proto-lens-protoc-0.9.0.1: app/protoc-gen-haskell.hs

-- Copyright 2016 Google Inc. All Rights Reserved.
--
-- Use of this source code is governed by a BSD-style
-- license that can be found in the LICENSE file or at
-- https://developers.google.com/open-source/licenses/bsd

{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where

import Control.Monad.IO.Class (liftIO)
import qualified Data.ByteString as B
import Data.Map.Strict ((!))
#if !MIN_VERSION_base(4,11,0)
import Data.Semigroup ((<>))
#endif
import qualified Data.Set as Set
import qualified Data.Text as T
import Data.Text (Text, pack)
import Data.Bits
import Data.ProtoLens (defMessage, decodeMessage, encodeMessage)
-- Force the use of the Reflected API when decoding DescriptorProto
-- so that we can run the test suite against the Generated API.
-- TODO: switch back to Data.ProtoLens.Encoding once the Generated encoding is
-- good enough.
import Lens.Family2
import Proto.Google.Protobuf.Compiler.Plugin
    ( CodeGeneratorRequest
    , CodeGeneratorResponse
    , CodeGeneratorResponse'Feature(..)
    )
import Proto.Google.Protobuf.Descriptor (FileDescriptorProto, Edition(..))
import System.Environment (getProgName)
import System.Exit (exitWith, ExitCode(..))
import System.IO as IO

import Data.ProtoLens.Compiler.Generate.Commented (getModuleName)
import Data.ProtoLens.Compiler.Generate
import Data.ProtoLens.Compiler.Plugin

#if MIN_VERSION_ghc(9,0,0)
import GHC.Driver.Session (DynFlags, getDynFlags)
#else
import DynFlags (DynFlags, getDynFlags)
#endif
import GHC (runGhc)
import GHC.Paths (libdir)
import GHC.SourceGen.Pretty (showPpr)

main :: IO ()
main = do
    contents <- B.getContents
    progName <- getProgName
    case decodeMessage contents of
        Left e -> IO.hPutStrLn stderr e >> exitWith (ExitFailure 1)
        Right x -> runGhc (Just libdir) $ do
                      dflags <- getDynFlags
                      liftIO $ B.putStr $ encodeMessage $
                        makeResponse dflags progName x

makeResponse :: DynFlags -> String -> CodeGeneratorRequest -> CodeGeneratorResponse
makeResponse dflags prog request = let
    outputFiles = generateFiles dflags header
                      (request ^. #protoFile)
                      (request ^. #fileToGenerate)
    header :: FileDescriptorProto -> Text
    header f = "{- This file was auto-generated from "
                <> (f ^. #name)
                <> " by the " <> pack prog <> " program. -}\n"
    features = [ CodeGeneratorResponse'FEATURE_PROTO3_OPTIONAL
               , CodeGeneratorResponse'FEATURE_SUPPORTS_EDITIONS
               ]
    preamble = defMessage
               & #supportedFeatures .~
                 foldl (.|.) zeroBits (fmap (toEnum . fromEnum) features)
               -- Do not process actual Protobuf Editions files yet.
               & #minimumEdition .~ fromIntegral (fromEnum EDITION_PROTO2)
               & #maximumEdition .~ fromIntegral (fromEnum EDITION_2024)
    in case outputFiles of
         Right fs -> preamble & #file .~
           [ defMessage
             & #name .~ outputName
             & #content .~ outputContent
           | (outputName, outputContent) <- fs
           ]
         Left e -> preamble & #error .~ e

generateFiles :: DynFlags -> (FileDescriptorProto -> Text)
              -> [FileDescriptorProto] -> [ProtoFileName]
              -> Either Text [(Text, Text)]
generateFiles dflags header files toGenerate = do
  filesByName <- analyzeProtoFiles files

  let modulesToBuild f =
        generateModule (haskellModule f) (descriptor f) imports
            (publicImports f)
            (definitions f)
            (collectEnvFromDeps deps filesByName)
            (services f)
        where
          deps = descriptor f ^. #dependency
          imports = Set.toAscList $ Set.fromList
                    $ map (haskellModule . (filesByName !)) deps


  -- The contents of the generated Haskell file for a given .proto file.
  return [ ( moduleFilePath $ pack $ showPpr dflags (getModuleName modul)
           , header (descriptor f) <> pack (showPpr dflags modul)
           )
         | fileName <- toGenerate
         , let f = filesByName ! fileName
         , modul <- modulesToBuild f
         ]

moduleFilePath :: Text -> Text
moduleFilePath n = T.replace "." "/" n <> ".hs"