Support multiple named exports globally

Add multi-root bundle support across the toolchain:
 - `compile`: Accept multiple definition names via `-x NAME` (repeatable or
   comma-separated). Exports all requested definitions as named roots in a
   single bundle. Defaults to "main" when no names are given.
 - `export`: Accept comma-separated hashes in the positional argument and
   multiple `-n`/`names` flags. Exports all resolved roots in one bundle.
 - Server: Add `GET /bundle/roots?n=...&h=...` endpoint that resolves
   multiple stored-term names and/or raw Merkle hashes, returning a single
   bundle containing all of them as roots.
 - Wire: Export `defaultExportNames` helper for generating default export
   names when none are supplied.
 - Drop `cereal` dependency from `tricu.cabal` (no longer used).
This commit is contained in:
2026-05-06 15:30:56 -05:00
parent 7e16607d96
commit e7a6426060
5 changed files with 125 additions and 44 deletions

View File

@@ -11,11 +11,11 @@ import Lexer
import Parser
import Research
import ContentStore (initContentStore, storeTerm, hashTerm)
import Wire (exportNamedBundle)
import Wire (exportNamedBundle, defaultExportNames)
import Control.Monad ()
import Control.Monad (forM_)
import Data.List (partition)
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Maybe (mapMaybe)
import System.Environment (setEnv)
import System.FilePath (takeDirectory, normalise, (</>))
import System.Exit (die)
@@ -164,23 +164,37 @@ nsVariable moduleName name = moduleName ++ "." ++ name
-- | Compile a tricu source file to a standalone Arborix bundle.
-- Uses a temp content store so it does not collide with the global one.
compileFile :: FilePath -> FilePath -> Maybe T.Text -> IO ()
compileFile inputPath outputPath maybeExportName = do
-- Supports multiple named exports; each is stored separately in the
-- temp store so that resolveExportTarget can look them up by name.
compileFile :: FilePath -> FilePath -> [T.Text] -> IO ()
compileFile inputPath outputPath maybeNames = do
-- Evaluate the file to get the full environment
env <- evaluateFile inputPath
-- Look up the export name: prefer explicit, then fall back to "main"
let name = fromMaybe "main" (T.unpack <$> maybeExportName)
case Map.lookup name env of
Nothing -> die $ "No definition '" ++ name ++ "' found in " ++ inputPath
Just term -> do
-- Create a temp content store
setEnv "TRICU_DB_PATH" "/tmp/tricu-compile.db"
conn <- initContentStore
-- Store the term in the temp store
_ <- storeTerm conn [name] term
-- Export the bundle (exportNamedBundle returns already-encoded bytes)
bundleData <- exportNamedBundle conn [(T.pack name, hashTerm term)]
BL.writeFile outputPath (BL.fromStrict bundleData)
close conn
putStrLn $ "Compiled " ++ inputPath ++ " -> " ++ outputPath
putStrLn $ " export: " ++ name
-- Look up each requested definition name
let defaultNames = ["main"]
wantedNames = if null maybeNames then defaultNames else maybeNames
wantedNamesUnpacked = map T.unpack wantedNames
compiledTerms <- mapM (\n -> case Map.lookup n env of
Nothing -> die $ "No definition '" ++ n ++ "' found in " ++ inputPath
Just t -> return (n, t)) wantedNamesUnpacked
let compiledMap :: Map.Map T.Text T = Map.fromList
$ map (\(n,t) -> (T.pack n, t)) compiledTerms
compiledNames :: [T.Text] = Map.keys compiledMap
compiledTermsList :: [T] = Map.elems compiledMap
-- Create a temp content store
setEnv "TRICU_DB_PATH" "/tmp/tricu-compile.db"
conn <- initContentStore
-- Store each term in the temp store under its requested name
forM_ (zip compiledNames compiledTermsList) $ \(n, t) ->
storeTerm conn [T.unpack n] t
-- Generate default export names when none were supplied
let expNames = if null maybeNames
then defaultExportNames (length compiledNames)
else compiledNames
exports :: [(T.Text, MerkleHash)] = zip expNames (map hashTerm compiledTermsList)
-- Export the bundle (exportNamedBundle returns already-encoded bytes)
bundleData <- exportNamedBundle conn exports
BL.writeFile outputPath (BL.fromStrict bundleData)
close conn
putStrLn $ "Compiled " ++ inputPath ++ " -> " ++ outputPath
putStrLn $ " exports: " ++ T.unpack (T.intercalate ", " expNames)