Skip to content

Commit

Permalink
Reduce the API of proto-lens-protoc. (google#357)
Browse files Browse the repository at this point in the history
Previously we exposed most of the internals as a library, with the hope
that they'd be useful for plugins (i.e. for services).  But now we're
exposing service information at the type level, and the library
continues to be unused.  Removing most of it allows us to change
the internals without requiring a major version bump.  For example,
with this change the migration to ghc-source-gen would have only
required a minor version bump.

Fixes google#347.  I think it's not feasible to completely remove the library
at this time, since we still want to share logic around module
names between proto-lens-protoc and proto-lens-setup.
  • Loading branch information
judah authored Oct 23, 2019
1 parent 47c4a79 commit 1c45c19
Show file tree
Hide file tree
Showing 10 changed files with 49 additions and 54 deletions.
3 changes: 3 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
- Use `ghc-source-gen` instead of `haskell-src-exts`. Removes
`Data.ProtoLens.Compiler.Combinators` and adds
`Data.ProtoLens.Compiler.Definitions`.
- Limit the exposed library to a single module
`Data.ProtoLens.Compiler.ModuleName`. The other modules were not
used in practice, and exposed internals unnecessarily.

### Backwards-Compatible Changes
- Fix a potential naming conflict when message types and enum values
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,19 @@ module Data.ProtoLens.Compiler.Plugin
, ProtoFile(..)
, analyzeProtoFiles
, collectEnvFromDeps
, outputFilePath
, moduleName
, moduleNameStr
) where

import Data.Char (toUpper)
import Data.List (foldl', intercalate)
import Data.List (foldl')
import qualified Data.Map.Strict as Map
import Data.Map.Strict (Map, unions, (!))
#if !MIN_VERSION_base(4,11,0)
import Data.Semigroup ((<>))
#endif
import Data.String (fromString)
import qualified Data.Text as T
import Data.Text (Text)
import Lens.Family2
import Proto.Google.Protobuf.Descriptor (FileDescriptorProto)
import System.FilePath (dropExtension, splitDirectories)

import Data.ProtoLens.Compiler.Definitions
import Data.ProtoLens.Compiler.ModuleName

import GHC.SourceGen (ModuleNameStr, OccNameStr, RdrNameStr)

Expand All @@ -51,12 +44,12 @@ data ProtoFile = ProtoFile

-- Given a list of FileDescriptorProtos, collect information about each file
-- into a map of 'ProtoFile's keyed by 'ProtoFileName'.
analyzeProtoFiles :: Text -> [FileDescriptorProto] -> Map ProtoFileName ProtoFile
analyzeProtoFiles modulePrefix files =
analyzeProtoFiles :: [FileDescriptorProto] -> Map ProtoFileName ProtoFile
analyzeProtoFiles files =
Map.fromList [ (f ^. #name, ingestFile f) | f <- files ]
where
filesByName = Map.fromList [(f ^. #name, f) | f <- files]
moduleNames = fmap (moduleName modulePrefix) filesByName
moduleNames = fmap fdModuleName filesByName
-- The definitions in each input proto file, indexed by filename.
definitionsByName = fmap collectDefinitions filesByName
servicesByName = fmap collectServices filesByName
Expand All @@ -83,31 +76,10 @@ collectEnvFromDeps :: [ProtoFileName] -> Map ProtoFileName ProtoFile -> Env RdrN
collectEnvFromDeps deps filesByName =
unions $ fmap (exportedEnv . (filesByName !)) deps

-- | Get the output file path (for CodeGeneratorResponse.File) for a Haskell
-- ModuleName.
outputFilePath :: String -> Text
outputFilePath n = T.replace "." "/" (T.pack n) <> ".hs"

-- | Get the Haskell 'ModuleName' corresponding to a given .proto file.
moduleName :: Text -> FileDescriptorProto -> ModuleNameStr
moduleName modulePrefix fd
= fromString $ moduleNameStr (T.unpack modulePrefix) (T.unpack $ fd ^. #name)

-- | Get the Haskell module name corresponding to a given .proto file.
moduleNameStr :: String -> FilePath -> String
moduleNameStr prefix path = fixModuleName rawModuleName
where
fixModuleName "" = ""
-- Characters allowed in Bazel filenames but not in module names:
fixModuleName ('.':c:cs) = '.' : toUpper c : fixModuleName cs
fixModuleName ('_':c:cs) = toUpper c : fixModuleName cs
fixModuleName ('-':c:cs) = toUpper c : fixModuleName cs
fixModuleName (c:cs) = c : fixModuleName cs
rawModuleName = intercalate "."
. (prefix :)
. splitDirectories $ dropExtension
$ path

fdModuleName :: FileDescriptorProto -> ModuleNameStr
fdModuleName fd
= fromString $ protoModuleName (T.unpack $ fd ^. #name)

-- | Given a list of .proto files (topologically sorted), determine which
-- files' definitions are exported by which files.
Expand Down
7 changes: 4 additions & 3 deletions app/protoc-gen-haskell.hs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ makeResponse dflags prog request = let
generateFiles :: DynFlags -> ModifyImports -> (FileDescriptorProto -> Text)
-> [FileDescriptorProto] -> [ProtoFileName] -> [(Text, Text)]
generateFiles dflags modifyImports header files toGenerate = let
modulePrefix = "Proto"
filesByName = analyzeProtoFiles modulePrefix files
filesByName = analyzeProtoFiles files
-- The contents of the generated Haskell file for a given .proto file.
modulesToBuild :: ProtoFile -> [CommentedModule]
modulesToBuild f = let
Expand All @@ -92,11 +91,13 @@ generateFiles dflags modifyImports header files toGenerate = let
(definitions f)
(collectEnvFromDeps deps filesByName)
(services f)
in [ ( outputFilePath $ showPpr dflags $ getModuleName modul
in [ ( 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"
25 changes: 10 additions & 15 deletions package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,25 @@ extra-source-files:

dependencies:
- base >= 4.9 && < 4.14
- containers >= 0.5 && < 0.7
- lens-family >= 1.2 && < 2.1
- proto-lens == 0.5.*
- text == 1.2.*
- ghc-source-gen == 0.3.*
- ghc >= 8.2 && < 8.10

- filepath >= 1.4 && < 1.6

library:
source-dirs: src
dependencies:
- filepath >= 1.4 && < 1.6
- pretty == 1.1.*
exposed-modules:
- Data.ProtoLens.Compiler.Definitions
- Data.ProtoLens.Compiler.Generate
- Data.ProtoLens.Compiler.Generate.Commented
- Data.ProtoLens.Compiler.Plugin
- Data.ProtoLens.Compiler.ModuleName

executables:
proto-lens-protoc:
main: protoc-gen-haskell.hs
source-dirs: app
dependencies:
- ghc-paths == 0.1.*
- bytestring == 0.10.*
- containers >= 0.5 && < 0.7
- ghc >= 8.2 && < 8.10
- ghc-paths == 0.1.*
- ghc-source-gen == 0.3.*
- lens-family >= 1.2 && < 2.1
- pretty == 1.1.*
- proto-lens == 0.5.*
- proto-lens-protoc
- text == 1.2.*
24 changes: 24 additions & 0 deletions src/Data/ProtoLens/Compiler/ModuleName.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module Data.ProtoLens.Compiler.ModuleName
( protoModuleName ) where

import Data.Char (toUpper)
import Data.List (intercalate)
import System.FilePath

-- | Get the Haskell module name corresponding to a given .proto file.
protoModuleName :: FilePath -> String
protoModuleName path = fixModuleName rawModuleName
where
fixModuleName "" = ""
-- Characters allowed in Bazel filenames but not in module names:
fixModuleName ('.':c:cs) = '.' : toUpper c : fixModuleName cs
fixModuleName ('_':c:cs) = toUpper c : fixModuleName cs
fixModuleName ('-':c:cs) = toUpper c : fixModuleName cs
fixModuleName (c:cs) = c : fixModuleName cs
rawModuleName = intercalate "."
. (prefix :)
. splitDirectories $ dropExtension
$ path

prefix :: String
prefix = "Proto"

0 comments on commit 1c45c19

Please sign in to comment.