diff --git a/src/Lean/Data/Json/FromToJson.lean b/src/Lean/Data/Json/FromToJson.lean index a6774aff3d..a69bcd6e5c 100644 --- a/src/Lean/Data/Json/FromToJson.lean +++ b/src/Lean/Data/Json/FromToJson.lean @@ -95,6 +95,22 @@ instance : FromJson Name where instance : ToJson Name where toJson n := toString n +instance [FromJson α] : FromJson (NameMap α) where + fromJson? + | .obj obj => obj.foldM (init := {}) fun m k v => do + if k == "[anonymous]" then + return m.insert .anonymous (← fromJson? v) + else + let n := k.toName + if n.isAnonymous then + throw s!"expected a `Name`, got '{k}'" + else + return m.insert n (← fromJson? v) + | j => throw s!"expected a `NameMap`, got '{j}'" + +instance [ToJson α] : ToJson (NameMap α) where + toJson m := Json.obj <| m.fold (fun n k v => n.insert compare k.toString (toJson v)) .leaf + /-- Note that `USize`s and `UInt64`s are stored as strings because JavaScript cannot represent 64-bit numbers. -/ def bignumFromJson? (j : Json) : Except String Nat := do diff --git a/src/Lean/Data/NameMap.lean b/src/Lean/Data/NameMap.lean index eed36180e1..67fa15bf75 100644 --- a/src/Lean/Data/NameMap.lean +++ b/src/Lean/Data/NameMap.lean @@ -18,6 +18,8 @@ def NameMap (α : Type) := RBMap Name α Name.quickCmp namespace NameMap variable {α : Type} +instance [Repr α] : Repr (NameMap α) := inferInstanceAs (Repr (RBMap Name α Name.quickCmp)) + instance (α : Type) : EmptyCollection (NameMap α) := ⟨mkNameMap α⟩ instance (α : Type) : Inhabited (NameMap α) where diff --git a/src/Lean/Elab/Frontend.lean b/src/Lean/Elab/Frontend.lean index 1ad2e54c58..2bcab8d314 100644 --- a/src/Lean/Elab/Frontend.lean +++ b/src/Lean/Elab/Frontend.lean @@ -145,6 +145,7 @@ def runFrontend (errorOnKinds : Array Name := #[]) (plugins : Array System.FilePath := #[]) (printStats : Bool := false) + (setupFileName? : Option System.FilePath := none) : IO (Option Environment) := do let startTime := (← IO.monoNanosNow).toFloat / 1000000000 let inputCtx := Parser.mkInputContext input fileName @@ -152,8 +153,28 @@ def runFrontend -- default to async elaboration; see also `Elab.async` docs let opts := Elab.async.setIfNotSet opts true let ctx := { inputCtx with } + let setup stx := do + if let some file := setupFileName? then + let setup ← ModuleSetup.load file + liftM <| setup.dynlibs.forM Lean.loadDynlib + return .ok { + trustLevel + mainModuleName := setup.name + isModule := setup.isModule + imports := setup.imports + plugins := plugins ++ setup.plugins + modules := setup.modules + -- override cmdline options with header options + opts := opts.mergeBy (fun _ _ hOpt => hOpt) setup.options.toOptions + } + else + return .ok { + imports := stx.imports + isModule := stx.isModule + mainModuleName, opts, trustLevel, plugins + } let processor := Language.Lean.process - let snap ← processor (fun _ => pure <| .ok { mainModuleName, opts, trustLevel, plugins }) none ctx + let snap ← processor setup none ctx let snaps := Language.toSnapshotTree snap let severityOverrides := errorOnKinds.foldl (·.insert · .error) {} diff --git a/src/Lean/Elab/Import.lean b/src/Lean/Elab/Import.lean index 1dbfbf7e94..4454bf1e69 100644 --- a/src/Lean/Elab/Import.lean +++ b/src/Lean/Elab/Import.lean @@ -10,7 +10,15 @@ import Lean.CoreM namespace Lean.Elab -def headerToImports : TSyntax ``Parser.Module.header → Array Import +abbrev HeaderSyntax := TSyntax ``Parser.Module.header + +def HeaderSyntax.startPos (header : HeaderSyntax) : String.Pos := + header.raw.getPos?.getD 0 + +def HeaderSyntax.isModule (header : HeaderSyntax) : Bool := + !header.raw[0].isNone + +def HeaderSyntax.imports : HeaderSyntax → Array Import | `(Parser.Module.header| $[module%$moduleTk]? $[prelude%$preludeTk]? $importsStx*) => let imports := if preludeTk.isNone then #[{ module := `Init : Import }] else #[] imports ++ importsStx.map fun @@ -19,17 +27,14 @@ def headerToImports : TSyntax ``Parser.Module.header → Array Import | _ => unreachable! | _ => unreachable! -/-- -Elaborates the given header syntax into an environment. +abbrev headerToImports := @HeaderSyntax.imports -If `mainModule` is not given, `Environment.setMainModule` should be called manually. This is a -backwards compatibility measure not compatible with the module system. --/ -def processHeader (header : TSyntax ``Parser.Module.header) (opts : Options) (messages : MessageLog) - (inputCtx : Parser.InputContext) (trustLevel : UInt32 := 0) - (plugins : Array System.FilePath := #[]) (leakEnv := false) (mainModule := Name.anonymous) +def processHeaderCore + (startPos : String.Pos) (imports : Array Import) (isModule : Bool) + (opts : Options) (messages : MessageLog) (inputCtx : Parser.InputContext) + (trustLevel : UInt32 := 0) (plugins : Array System.FilePath := #[]) (leakEnv := false) + (mainModule := Name.anonymous) (arts : NameMap ModuleArtifacts := {}) : IO (Environment × MessageLog) := do - let isModule := !header.raw[0].isNone let level := if isModule then if Elab.inServer.get opts then .server @@ -38,7 +43,6 @@ def processHeader (header : TSyntax ``Parser.Module.header) (opts : Options) (me else .private let (env, messages) ← try - let imports := headerToImports header for i in imports do if !isModule && i.importAll then throw <| .userError "cannot use `import all` without `module`" @@ -47,15 +51,30 @@ def processHeader (header : TSyntax ``Parser.Module.header) (opts : Options) (me if !isModule && !i.isExported then throw <| .userError "cannot use `private import` without `module`" let env ← - importModules (leakEnv := leakEnv) (loadExts := true) (level := level) imports opts trustLevel plugins + importModules (leakEnv := leakEnv) (loadExts := true) (level := level) + imports opts trustLevel plugins arts pure (env, messages) catch e => let env ← mkEmptyEnvironment - let spos := header.raw.getPos?.getD 0 - let pos := inputCtx.fileMap.toPosition spos + let pos := inputCtx.fileMap.toPosition startPos pure (env, messages.add { fileName := inputCtx.fileName, data := toString e, pos := pos }) return (env.setMainModule mainModule, messages) +/-- +Elaborates the given header syntax into an environment. + +If `mainModule` is not given, `Environment.setMainModule` should be called manually. This is a +backwards compatibility measure not compatible with the module system. +-/ +@[inline] def processHeader + (header : HeaderSyntax) + (opts : Options) (messages : MessageLog) (inputCtx : Parser.InputContext) + (trustLevel : UInt32 := 0) (plugins : Array System.FilePath := #[]) (leakEnv := false) + (mainModule := Name.anonymous) + : IO (Environment × MessageLog) := do + processHeaderCore header.startPos header.imports header.isModule + opts messages inputCtx trustLevel plugins leakEnv mainModule + def parseImports (input : String) (fileName : Option String := none) : IO (Array Import × Position × MessageLog) := do let fileName := fileName.getD "" let inputCtx := Parser.mkInputContext input fileName diff --git a/src/Lean/Environment.lean b/src/Lean/Environment.lean index a2a7691b25..0c1f9d1130 100644 --- a/src/Lean/Environment.lean +++ b/src/Lean/Environment.lean @@ -11,6 +11,7 @@ import Init.System.Promise import Lean.ImportingFlag import Lean.Data.NameTrie import Lean.Data.SMap +import Lean.Setup import Lean.Declaration import Lean.LocalContext import Lean.Util.Path @@ -93,18 +94,6 @@ instance : GetElem? (Array α) ModuleIdx α (fun a i => i.toNat < a.size) where abbrev ConstMap := SMap Name ConstantInfo -structure Import where - module : Name - /-- `import all`; whether to import and expose all data saved by the module. -/ - importAll : Bool := false - /-- Whether to activate this import when the current module itself is imported. -/ - isExported : Bool := true - deriving Repr, Inhabited - -instance : Coe Name Import := ⟨({module := ·})⟩ - -instance : ToString Import := ⟨fun imp => toString imp.module⟩ - /-- A compacted region holds multiple Lean objects in a contiguous memory region, which can be read/written to/from disk. Objects inside the region do not have reference counters and cannot be freed individually. The contents of .olean @@ -1794,7 +1783,35 @@ abbrev ImportStateM := StateRefT ImportState IO @[inline] nonrec def ImportStateM.run (x : ImportStateM α) (s : ImportState := {}) : IO (α × ImportState) := x.run s -partial def importModulesCore (imports : Array Import) (forceImportAll := true) : +def ModuleArtifacts.oleanParts (arts : ModuleArtifacts) : Array System.FilePath := Id.run do + let mut fnames := #[] + -- Opportunistically load all available parts. + -- Producer (e.g., Lake) should limit parts to the proper import level. + if let some mFile := arts.olean? then + fnames := fnames.push mFile + if let some sFile := arts.oleanServer? then + fnames := fnames.push sFile + if let some pFile := arts.oleanPrivate? then + fnames := fnames.push pFile + return fnames + +private def findOLeanParts (mod : Name) : IO (Array System.FilePath) := do + let mFile ← findOLean mod + unless (← mFile.pathExists) do + throw <| IO.userError s!"object file '{mFile}' of module {mod} does not exist" + let mut fnames := #[mFile] + -- Opportunistically load all available parts. + -- Necessary because the import level may be upgraded a later import. + let sFile := OLeanLevel.server.adjustFileName mFile + if (← sFile.pathExists) then + fnames := fnames.push sFile + let pFile := OLeanLevel.private.adjustFileName mFile + if (← pFile.pathExists) then + fnames := fnames.push pFile + return fnames + +partial def importModulesCore + (imports : Array Import) (forceImportAll := true) (arts : NameMap ModuleArtifacts := {}) : ImportStateM Unit := go where go := do for i in imports do @@ -1811,19 +1828,14 @@ where go := do if let some mod := mod.mainModule? then importModulesCore (forceImportAll := true) mod.imports continue - let mFile ← findOLean i.module - unless (← mFile.pathExists) do - throw <| IO.userError s!"object file '{mFile}' of module {i.module} does not exist" - let mut fnames := #[mFile] - -- opportunistically load all available parts in case `importPrivate` is upgraded by a later - -- import - -- TODO: use Lake data to retrieve ultimate import level immediately - let sFile := OLeanLevel.server.adjustFileName mFile - if (← sFile.pathExists) then - fnames := fnames.push sFile - let pFile := OLeanLevel.private.adjustFileName mFile - if (← pFile.pathExists) then - fnames := fnames.push pFile + let fnames ← + if let some arts := arts.find? i.module then + let fnames := arts.oleanParts + if fnames.isEmpty then + findOLeanParts i.module + else pure fnames + else + findOLeanParts i.module let parts ← readModuleDataParts fnames -- `imports` is identical for each part let some (baseMod, _) := parts[0]? | unreachable! @@ -1995,13 +2007,14 @@ as if no `module` annotations were present in the imports. -/ def importModules (imports : Array Import) (opts : Options) (trustLevel : UInt32 := 0) (plugins : Array System.FilePath := #[]) (leakEnv := false) (loadExts := false) - (level := OLeanLevel.private) : IO Environment := profileitIO "import" opts do + (level := OLeanLevel.private) (arts : NameMap ModuleArtifacts := {}) + : IO Environment := profileitIO "import" opts do for imp in imports do if imp.module matches .anonymous then throw <| IO.userError "import failed, trying to import module with anonymous name" withImporting do plugins.forM Lean.loadPlugin - let (_, s) ← importModulesCore (forceImportAll := level == .private) imports |>.run + let (_, s) ← importModulesCore (forceImportAll := level == .private) imports arts |>.run finalizeImport (leakEnv := leakEnv) (loadExts := loadExts) (level := level) s imports opts trustLevel diff --git a/src/Lean/Language/Lean.lean b/src/Lean/Language/Lean.lean index d448b58bd8..2f2525a304 100644 --- a/src/Lean/Language/Lean.lean +++ b/src/Lean/Language/Lean.lean @@ -283,10 +283,16 @@ simple uses, these can be computed eagerly without looking at the imports. structure SetupImportsResult where /-- Module name of the file being processed. -/ mainModuleName : Name + /-- Whether the file is participating in the module system. -/ + isModule : Bool := false + /-- Direct imports of the file being processed. -/ + imports : Array Import /-- Options provided outside of the file content, e.g. on the cmdline or in the lakefile. -/ opts : Options /-- Kernel trust level. -/ trustLevel : UInt32 := 0 + /-- Pre-resolved artifacts of related modules (e.g., this module's transitive imports). -/ + modules : NameMap ModuleArtifacts := {} /-- Lean plugins to load as part of the environment setup. -/ plugins : Array System.FilePath := #[] @@ -367,7 +373,7 @@ General notes: the `sync` parameter on `parseCmd` and spawn an elaboration task when we leave it. -/ partial def process - (setupImports : TSyntax ``Parser.Module.header → ProcessingT IO (Except HeaderProcessedSnapshot SetupImportsResult)) + (setupImports : HeaderSyntax → ProcessingT IO (Except HeaderProcessedSnapshot SetupImportsResult)) (old? : Option InitialSnapshot) : ProcessingM InitialSnapshot := do parseHeader old? |>.run (old?.map (·.ictx)) where @@ -453,7 +459,7 @@ where } } - processHeader (stx : TSyntax ``Parser.Module.header) (parserState : Parser.ModuleParserState) : + processHeader (stx : HeaderSyntax) (parserState : Parser.ModuleParserState) : LeanProcessingM (SnapshotTask HeaderProcessedSnapshot) := do let ctx ← read SnapshotTask.ofIO stx none (some ⟨0, ctx.input.endPos⟩) <| @@ -471,9 +477,9 @@ where if !stx.raw[0].isNone && !experimental.module.get opts then throw <| IO.Error.userError "`module` keyword is experimental and not enabled here" -- allows `headerEnv` to be leaked, which would live until the end of the process anyway - let (headerEnv, msgLog) ← Elab.processHeader (leakEnv := true) - (mainModule := setup.mainModuleName) stx opts .empty ctx.toInputContext setup.trustLevel - setup.plugins + let (headerEnv, msgLog) ← Elab.processHeaderCore (leakEnv := true) + stx.startPos setup.imports setup.isModule setup.opts .empty ctx.toInputContext + setup.trustLevel setup.plugins setup.mainModuleName setup.modules let stopTime := (← IO.monoNanosNow).toFloat / 1000000000 let diagnostics := (← Snapshot.Diagnostics.ofMessageLog msgLog) if msgLog.hasErrors then diff --git a/src/Lean/Server/FileWorker.lean b/src/Lean/Server/FileWorker.lean index c300ffdfb6..5984689812 100644 --- a/src/Lean/Server/FileWorker.lean +++ b/src/Lean/Server/FileWorker.lean @@ -417,6 +417,7 @@ def setupImports return .ok { mainModuleName := meta.mod + imports opts plugins := fileSetupResult.plugins } diff --git a/src/Lean/Setup.lean b/src/Lean/Setup.lean new file mode 100644 index 0000000000..34a031556c --- /dev/null +++ b/src/Lean/Setup.lean @@ -0,0 +1,65 @@ +/- +Copyright (c) 2019 Microsoft Corporation. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Leonardo de Moura, Mac Malone +-/ +prelude +import Lean.Data.Json +import Lean.Util.LeanOptions + +/-! +# Module Setup Information + +Data types used by Lean module headers and the `--setup` CLI. +-/ + +namespace Lean + +structure Import where + module : Name + /-- `import all`; whether to import and expose all data saved by the module. -/ + importAll : Bool := false + /-- Whether to activate this import when the current module itself is imported. -/ + isExported : Bool := true + deriving Repr, Inhabited, ToJson, FromJson + +instance : Coe Name Import := ⟨({module := ·})⟩ + +instance : ToString Import := ⟨fun imp => toString imp.module⟩ + +/-- Files containing data for a single module. -/ +structure ModuleArtifacts where + lean? : Option System.FilePath := none + olean? : Option System.FilePath := none + oleanServer? : Option System.FilePath := none + oleanPrivate? : Option System.FilePath := none + ilean? : Option System.FilePath := none + deriving Repr, Inhabited, ToJson, FromJson + +/-- +A module's setup information as described by a JSON file. +Supercedes the module's header when the `--setup` CLI option is used. +-/ +structure ModuleSetup where + /-- Name of the module. -/ + name : Name + /-- Whether the module is participating in the module system. -/ + isModule : Bool := false + /- The module's direct imports. -/ + imports : Array Import := #[] + /-- Pre-resolved artifacts of related modules (e.g., this module's transitive imports). -/ + modules : NameMap ModuleArtifacts := {} + /-- Dynamic libraries to load with the module. -/ + dynlibs : Array System.FilePath := #[] + /-- Plugins to initialize with the module. -/ + plugins : Array System.FilePath := #[] + /-- Additional options for the module. -/ + options : LeanOptions := {} + deriving Repr, Inhabited, ToJson, FromJson + +/-- Load a `ModuleSetup` from a JSON file. -/ +def ModuleSetup.load (path : System.FilePath) : IO ModuleSetup := do + let contents ← IO.FS.readFile path + match Json.parse contents >>= fromJson? with + | .ok info => pure info + | .error msg => throw <| IO.userError s!"failed to load header from {path}: {msg}" diff --git a/src/Lean/Util/LeanOptions.lean b/src/Lean/Util/LeanOptions.lean index 9fe332f7dd..cc369f172b 100644 --- a/src/Lean/Util/LeanOptions.lean +++ b/src/Lean/Util/LeanOptions.lean @@ -60,9 +60,11 @@ def LeanOptionValue.asCliFlagValue : (v : LeanOptionValue) → String /-- Options that are used by Lean as if they were passed using `-D`. -/ structure LeanOptions where - values : RBMap Name LeanOptionValue Name.cmp + values : NameMap LeanOptionValue deriving Inhabited, Repr +instance : EmptyCollection LeanOptions := ⟨⟨∅⟩⟩ + def LeanOptions.toOptions (leanOptions : LeanOptions) : Options := Id.run do let mut options := KVMap.empty for ⟨name, optionValue⟩ in leanOptions.values do @@ -77,17 +79,9 @@ def LeanOptions.fromOptions? (options : Options) : Option LeanOptions := do return ⟨values⟩ instance : FromJson LeanOptions where - fromJson? - | Json.obj obj => do - let values ← obj.foldM (init := RBMap.empty) fun acc k v => do - let optionValue ← fromJson? v - return acc.insert k.toName optionValue - return ⟨values⟩ - | _ => Except.error "invalid LeanOptions type" + fromJson? j := LeanOptions.mk <$> fromJson? j instance : ToJson LeanOptions where - toJson options := - Json.obj <| options.values.fold (init := RBNode.leaf) fun acc k v => - acc.insert (cmp := compare) k.toString (toJson v) + toJson options := toJson options.values end Lean diff --git a/src/lake/Lake/Util/Name.lean b/src/lake/Lake/Util/Name.lean index 1dbf3c8b82..ee58ebc93c 100644 --- a/src/lake/Lake/Util/Name.lean +++ b/src/lake/Lake/Util/Name.lean @@ -35,18 +35,6 @@ abbrev OrdNameMap α := RBArray Name α Name.quickCmp abbrev DNameMap α := DRBMap Name α Name.quickCmp @[inline] def DNameMap.empty : DNameMap α := DRBMap.empty -instance [ToJson α] : ToJson (NameMap α) where - toJson m := Json.obj <| m.fold (fun n k v => n.insert compare k.toString (toJson v)) .leaf - -instance [FromJson α] : FromJson (NameMap α) where - fromJson? j := do - (← j.getObj?).foldM (init := {}) fun m k v => - let k := k.toName - if k.isAnonymous then - throw "expected name" - else - return m.insert k (← fromJson? v) - /-! # Name Helpers -/ namespace Name diff --git a/src/util/shell.cpp b/src/util/shell.cpp index ab1345c97e..308ecc0430 100644 --- a/src/util/shell.cpp +++ b/src/util/shell.cpp @@ -223,6 +223,7 @@ static void display_help(std::ostream & out) { #endif std::cout << " --plugin=file load and initialize Lean shared library for registering linters etc.\n"; std::cout << " --load-dynlib=file load shared library to make its symbols available to the interpreter\n"; + std::cout << " --setup=file JSON file with module setup data (supersedes the file's header)\n"; std::cout << " --json report Lean output (e.g., messages) as JSON (one per line)\n"; std::cout << " -E --error=kind report Lean messages of kind as errors\n"; std::cout << " --deps just print dependencies of a Lean input\n"; @@ -273,6 +274,7 @@ static struct option g_long_options[] = { #endif {"plugin", required_argument, 0, 'p'}, {"load-dynlib", required_argument, 0, 'l'}, + {"setup", required_argument, 0, 'u'}, {"error", required_argument, 0, 'E'}, {"json", no_argument, &json_output, 1}, {"print-prefix", no_argument, &print_prefix, 1}, @@ -340,6 +342,7 @@ extern "C" object * lean_run_frontend( object * error_kinds, object * plugins, bool print_stats, + object * header_file_name, object * w ); option_ref run_new_frontend( @@ -351,7 +354,8 @@ option_ref run_new_frontend( optional const & ilean_file_name, uint8_t json_output, array_ref const & error_kinds, - bool print_stats + bool print_stats, + optional const & setup_file_name ) { return get_io_result>(lean_run_frontend( mk_string(input), @@ -365,6 +369,7 @@ option_ref run_new_frontend( error_kinds.to_obj_arg(), mk_empty_array(), print_stats, + setup_file_name ? mk_option_some(mk_string(*setup_file_name)) : mk_option_none(), io_mk_world() )); } @@ -487,6 +492,7 @@ extern "C" LEAN_EXPORT int lean_main(int argc, char ** argv) { bool run = false; optional olean_fn; optional ilean_fn; + optional setup_fn; bool use_stdin = false; unsigned trust_lvl = LEAN_BELIEVER_TRUST_LEVEL + 1; bool only_deps = false; @@ -638,6 +644,10 @@ extern "C" LEAN_EXPORT int lean_main(int argc, char ** argv) { lean::load_dynlib(optarg); forwarded_args.push_back(string_ref("--load-dynlib=" + std::string(optarg))); break; + case 'u': + check_optarg("u"); + setup_fn = optarg; + break; case 'E': check_optarg("E"); error_kinds.push_back(string_to_name(std::string(optarg))); @@ -755,7 +765,10 @@ extern "C" LEAN_EXPORT int lean_main(int argc, char ** argv) { if (!main_module_name) main_module_name = name("_stdin"); - option_ref opt_env = run_new_frontend(contents, opts, mod_fn, *main_module_name, trust_lvl, olean_fn, ilean_fn, json_output, error_kinds, stats); + option_ref opt_env = run_new_frontend( + contents, opts, mod_fn, *main_module_name, trust_lvl, + olean_fn, ilean_fn, json_output, error_kinds, stats, setup_fn + ); if (opt_env) { elab_environment env = opt_env.get_val(); diff --git a/tests/pkg/setup/Dep.lean b/tests/pkg/setup/Dep.lean new file mode 100644 index 0000000000..5d95528add --- /dev/null +++ b/tests/pkg/setup/Dep.lean @@ -0,0 +1 @@ +def hello := "hello" diff --git a/tests/pkg/setup/Test.lean b/tests/pkg/setup/Test.lean new file mode 100644 index 0000000000..fac75d77bd --- /dev/null +++ b/tests/pkg/setup/Test.lean @@ -0,0 +1 @@ +#eval hello diff --git a/tests/pkg/setup/clean.sh b/tests/pkg/setup/clean.sh new file mode 100755 index 0000000000..2c4fabf32b --- /dev/null +++ b/tests/pkg/setup/clean.sh @@ -0,0 +1 @@ +rm -f Dep.olean diff --git a/tests/pkg/setup/setup.json b/tests/pkg/setup/setup.json new file mode 100644 index 0000000000..ab86b7d2a4 --- /dev/null +++ b/tests/pkg/setup/setup.json @@ -0,0 +1,19 @@ +{ + "name": "Dep", + "isModule": false, + "imports": [ + { + "module": "Dep", + "importAll": false, + "isExported": true + } + ], + "modules": { + "Dep": { + "olean": "Dep.olean" + } + }, + "dynlibs": [], + "plugins": [], + "options": {} +} diff --git a/tests/pkg/setup/test.sh b/tests/pkg/setup/test.sh new file mode 100755 index 0000000000..ff76796b2f --- /dev/null +++ b/tests/pkg/setup/test.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Test that Lean will use the specified olean from `setup.json` +lean Dep.lean -o Dep.olean +lean Test.lean --setup setup.json