-- Copyright (c) Microsoft. All rights reserved. -- Licensed under the MIT license. See LICENSE file in the project root for full license information. {-# LANGUAGE RecordWildCards #-} import System.Environment (getArgs, withArgs) import System.Directory import System.FilePath import Data.Maybe import Data.Monoid import qualified Data.Foldable as F import Control.Monad import Prelude import Control.Concurrent.Async import GHC.Conc (getNumProcessors, setNumCapabilities) import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as LT import qualified Data.Text.Lazy.IO as LTIO import Data.Aeson (encode) import qualified Data.ByteString.Lazy as BL import Language.Bond.Syntax.Types (Bond(..), Declaration(..), Import, Type(..)) import Language.Bond.Syntax.JSON() import Language.Bond.Syntax.SchemaDef import Language.Bond.Codegen.Util import Language.Bond.Codegen.Templates import Language.Bond.Codegen.TypeMapping import Options import IO type Template = MappingContext -> String -> [Import] -> [Declaration] -> (String, Text) main :: IO() main = do args <- getArgs options <- (if null args then withArgs ["--help=all"] else id) getOptions setJobs $ jobs options case options of Cpp {..} -> cppCodegen options Cs {..} -> csCodegen options Java {..} -> javaCodegen options Schema {..} -> writeSchema options _ -> print options setJobs :: Maybe Int -> IO () setJobs Nothing = return () setJobs (Just n) | n > 0 = setNumCapabilities n | otherwise = do numProc <- getNumProcessors -- if n is less than 0 use that many fewer jobs than processors setNumCapabilities $ max 1 (numProc + n) concurrentlyFor_ :: [a] -> (a -> IO b) -> IO () concurrentlyFor_ = (void .) . flip mapConcurrently createDir :: FilePath -> IO () createDir path = do -- recursive = True createDirectoryIfMissing True path writeSchema :: Options -> IO() writeSchema Schema {..} = concurrentlyFor_ files $ \file -> do let fileName = takeBaseName file bond <- parseFile import_dir file if runtime_schema then forM_ (bondDeclarations bond) (writeSchemaDef fileName) else BL.writeFile (output_dir fileName <.> "json") $ encode bond where writeSchemaDef fileName s@Struct{..} | null declParams = BL.writeFile (output_dir fileName <.> declName <.> "json") $ encodeSchemaDef $ BT_UserDefined s [] writeSchemaDef _ _ = return () writeSchema _ = error "writeSchema: impossible happened." cppCodegen :: Options -> IO() cppCodegen options@Cpp {..} = do let typeMappingAliases = maybe cppTypeMapping (cppCustomAllocTypeMapping scoped_alloc_enabled) allocator let typeMapping = if type_aliases_enabled then typeMappingAliases else cppExpandAliasesTypeMapping typeMappingAliases concurrentlyFor_ files $ codeGen options typeMapping templates where applyProto = map snd $ filter (enabled apply) protocols enabled a p = null a || fst p `elem` a protocols = [ (Compact, ProtocolReader " ::bond::CompactBinaryReader<::bond::InputBuffer>") , (Compact, ProtocolWriter " ::bond::CompactBinaryWriter<::bond::OutputBuffer>") , (Compact, ProtocolWriter " ::bond::CompactBinaryWriter<::bond::OutputBuffer>::Pass0") , (Fast, ProtocolReader " ::bond::FastBinaryReader<::bond::InputBuffer>") , (Fast, ProtocolWriter " ::bond::FastBinaryWriter<::bond::OutputBuffer>") , (Simple, ProtocolReader " ::bond::SimpleBinaryReader<::bond::InputBuffer>") , (Simple, ProtocolWriter " ::bond::SimpleBinaryWriter<::bond::OutputBuffer>") ] templates = concat $ map snd $ filter fst codegen_templates codegen_templates = [ (core_enabled, core_files) , (grpc_enabled, [grpc_h export_attribute, grpc_cpp]) ] core_files = [ reflection_h export_attribute , types_h export_attribute header enum_header allocator alloc_ctors_enabled type_aliases_enabled scoped_alloc_enabled , types_cpp , apply_h applyProto export_attribute , apply_cpp applyProto ] <> [ enum_h | enum_header] cppCodegen _ = error "cppCodegen: impossible happened." csCodegen :: Options -> IO() csCodegen options@Cs {..} = do concurrentlyFor_ files $ codeGen options typeMapping templates where typeMapping = if collection_interfaces then csCollectionInterfacesTypeMapping else csTypeMapping fieldMapping = if readonly_properties then ReadOnlyProperties else if fields then PublicFields else Properties constructorOptions = if constructor_parameters then ConstructorParameters else DefaultWithProtectedBase templates = concat $ map snd $ filter fst codegen_templates codegen_templates = [ (structs_enabled, [types_cs Class fieldMapping constructorOptions]) , (grpc_enabled, [grpc_cs]) ] csCodegen _ = error "csCodegen: impossible happened." anyServiceInheritance :: [Declaration] -> Bool anyServiceInheritance = getAny . F.foldMap serviceWithBase where serviceWithBase Service{..} = Any $ isJust serviceBase serviceWithBase _ = Any False codeGen :: Options -> TypeMapping -> [Template] -> FilePath -> IO () codeGen options typeMapping templates file = do let outputDir = output_dir options let baseName = takeBaseName file aliasMapping <- parseAliasMappings $ using options namespaceMapping <- parseNamespaceMappings $ namespace options (Bond imports namespaces declarations) <- parseFile (import_dir options) file let mappingContext = MappingContext typeMapping aliasMapping namespaceMapping namespaces case (anyServiceInheritance declarations, service_inheritance_enabled options, grpc_enabled options) of (True, False, _) -> fail "Use --enable-service-inheritance to enable service inheritance syntax." (True, True, True) -> fail "Service inheritance is not supported in gRPC codegen." _ -> forM_ templates $ \template -> do let (suffix, code) = template mappingContext baseName imports declarations let fileName = baseName ++ suffix createDirectoryIfMissing True outputDir let content = if (no_banner options) then code else (commonHeader "//" file fileName <> code) LTIO.writeFile (outputDir fileName) content -- Java's class-per-file and package-as-path requirements make it difficult to -- share code with languages where there is a known set of generated files for -- each bondfile. javaCodegen :: Options -> IO () javaCodegen Java {..} = do namespaceMapping <- parseNamespaceMappings namespace concurrentlyFor_ files $ \bondFile -> do (Bond imports namespaces declarations) <- parseFile import_dir bondFile -- AliasMappings not implemented. let mappingContext = MappingContext javaTypeMapping [] namespaceMapping namespaces forM_ declarations $ \declaration -> do let javaNamespace = getDeclNamespace mappingContext declaration let packageDir = output_dir case javaNamespace of x:xs -> foldl () x xs [] -> error "declaration " ++ declName declaration ++ " has no namespace" let javaFile = declName declaration ++ ".java" let code = case declaration of Struct {} -> class_java mappingContext imports declaration Enum {} -> enum_java mappingContext declaration _ -> mempty if LT.null code then return () else do let content = if no_banner then code else (commonHeader "//" safeBondFile safeJavaFile <> code) where -- javac will always treat "\u" as the start -- of a unicode escape sequence, and will -- error out if it isn't followed by a valid -- code. This breaks compilation of generated -- code if either path has components that -- start with u. safeBondFile = slashForward bondFile safeJavaFile = slashForward javaFile createDir packageDir LTIO.writeFile (packageDir javaFile) content javaCodegen _ = error "javaCodegen: impossible happened."