diff --git a/src/lake/Lake/Build/Module.lean b/src/lake/Lake/Build/Module.lean index 83ac5af6da22..0e97abb42da1 100644 --- a/src/lake/Lake/Build/Module.lean +++ b/src/lake/Lake/Build/Module.lean @@ -360,6 +360,7 @@ def Package.discriminant (self : Package) := set_option linter.unusedVariables.funArgs false in def fetchImportInfo (fileName : String) (pkgName modName : Name) (header : ModuleHeader) + (silenceWarning : Bool := false) : FetchM (Job ModuleImportInfo) := do let nonModule := !header.isModule let info := ModuleImportInfo.nil modName @@ -369,6 +370,12 @@ def fetchImportInfo logError s!"{fileName}: module imports itself" return .error let mods ← findModules imp.module + if nonModule && !silenceWarning then + if let some mod := mods.find? fun mod => + mod.pkg.requiresModuleSystem && pkgName != mod.pkg.keyName then + logWarning s!"{fileName}: imports `{imp.module}` from package \ + `{mod.pkg.prettyName}`, which is designed for use with the module \ + system; consider adding `module` to the start of this file" let n := mods.size if h : n = 0 then return s @@ -421,6 +428,7 @@ public def Module.importInfoFacetConfig : ModuleFacetConfig importInfoFacet := mkFacetJobConfig fun mod => do let header ← (← mod.header.fetch).await fetchImportInfo mod.relLeanFile.toString mod.pkg.keyName mod.name header + (silenceWarning := mod.pkg.silenceRequiresModuleSystemWarning) def noServerOLeanError := "No server olean generated. Ensure the module system is enabled." @@ -1232,6 +1240,7 @@ def setupEditedModule let fileName := mod.relLeanFile.toString let localImports := directImports.filterMap (·.module?) let impInfoJob ← fetchImportInfo fileName mod.pkg.keyName mod.name header + (silenceWarning := mod.pkg.silenceRequiresModuleSystemWarning) let precompileImports ← if mod.shouldPrecompile then (← computeTransImportsAux fileName localImports).await diff --git a/src/lake/Lake/Config/Package.lean b/src/lake/Lake/Config/Package.lean index 03f66b9b72d4..6d515fd3bd5c 100644 --- a/src/lake/Lake/Config/Package.lean +++ b/src/lake/Lake/Config/Package.lean @@ -294,6 +294,14 @@ public def id? (self : Package) : Option PkgId := @[inline] public def allowImportAll (self : Package) : Bool := self.config.allowImportAll +/-- The package's `requiresModuleSystem` configuration. -/ +@[inline] public def requiresModuleSystem (self : Package) : Bool := + self.config.requiresModuleSystem + +/-- The package's `silenceRequiresModuleSystemWarning` configuration. -/ +@[inline] public def silenceRequiresModuleSystemWarning (self : Package) : Bool := + self.config.silenceRequiresModuleSystemWarning + /-- The package's `dynlibs` configuration. -/ @[inline] public def dynlibs (self : Package) : TargetArray Dynlib := self.config.dynlibs diff --git a/src/lake/Lake/Config/PackageConfig.lean b/src/lake/Lake/Config/PackageConfig.lean index 22b08dea9b0a..8f648d73ef59 100644 --- a/src/lake/Lake/Config/PackageConfig.lean +++ b/src/lake/Lake/Config/PackageConfig.lean @@ -321,6 +321,34 @@ public configuration PackageConfig (p : Name) (n : Name) extends WorkspaceConfig -/ allowImportAll : Bool := false + /-- + Whether this package is designed for use with the module system. + + If enabled, Lake emits a warning whenever a module from another package + imports a module of this package without itself using the module system + (i.e., without a `module` header). This signals to downstream users that + the package's API expects the visibility and elaboration semantics of the + module system. + + Downstream packages can opt out of the warning by setting + `silenceRequiresModuleSystemWarning := true` on their own package. + + Defaults to `false`. + -/ + requiresModuleSystem : Bool := false + + /-- + Whether to silence the warnings produced when this package imports modules + from a dependency that has set `requiresModuleSystem` while not using the + module system itself. + + Use this to opt out of the migration nudge after deciding the package is + knowingly going to keep importing such dependencies without `module`. + + Defaults to `false`. + -/ + silenceRequiresModuleSystemWarning : Bool := false + /-- Whether to run Lake's built-in linter on the package. diff --git a/tests/lake/tests/requiresModule/Test/ModuleConsumer.lean b/tests/lake/tests/requiresModule/Test/ModuleConsumer.lean new file mode 100644 index 000000000000..ac338f16ca73 --- /dev/null +++ b/tests/lake/tests/requiresModule/Test/ModuleConsumer.lean @@ -0,0 +1,3 @@ +module + +import Dep diff --git a/tests/lake/tests/requiresModule/Test/NonModuleConsumer.lean b/tests/lake/tests/requiresModule/Test/NonModuleConsumer.lean new file mode 100644 index 000000000000..f341d235af05 --- /dev/null +++ b/tests/lake/tests/requiresModule/Test/NonModuleConsumer.lean @@ -0,0 +1 @@ +import Dep diff --git a/tests/lake/tests/requiresModule/clean.sh b/tests/lake/tests/requiresModule/clean.sh new file mode 100755 index 000000000000..b81d677c37ca --- /dev/null +++ b/tests/lake/tests/requiresModule/clean.sh @@ -0,0 +1,3 @@ +rm -f produced* +rm -rf dep/.lake dep/lake-manifest.json +rm -rf .lake lake-manifest.json diff --git a/tests/lake/tests/requiresModule/dep/Dep.lean b/tests/lake/tests/requiresModule/dep/Dep.lean new file mode 100644 index 000000000000..bb95e594e436 --- /dev/null +++ b/tests/lake/tests/requiresModule/dep/Dep.lean @@ -0,0 +1,3 @@ +module + +public def Dep.greet : String := "hi" diff --git a/tests/lake/tests/requiresModule/dep/lakefile.toml b/tests/lake/tests/requiresModule/dep/lakefile.toml new file mode 100644 index 000000000000..579044da8e13 --- /dev/null +++ b/tests/lake/tests/requiresModule/dep/lakefile.toml @@ -0,0 +1,5 @@ +name = "dep" +requiresModuleSystem = true + +[[lean_lib]] +name = "Dep" diff --git a/tests/lake/tests/requiresModule/lakefile.toml b/tests/lake/tests/requiresModule/lakefile.toml new file mode 100644 index 000000000000..18ad5103cf9a --- /dev/null +++ b/tests/lake/tests/requiresModule/lakefile.toml @@ -0,0 +1,10 @@ +name = "test" +defaultTargets = ["Test"] + +[[lean_lib]] +name = "Test" +globs = "Test.+" + +[[require]] +name = "dep" +path = "dep" diff --git a/tests/lake/tests/requiresModule/test.sh b/tests/lake/tests/requiresModule/test.sh new file mode 100755 index 000000000000..259ff65b0b81 --- /dev/null +++ b/tests/lake/tests/requiresModule/test.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +source ../common.sh + +./clean.sh + +# --- +# Tests the package-level `requiresModuleSystem` flag. +# A package that sets `requiresModuleSystem = true` should cause Lake +# to warn whenever a downstream module imports it without a `module` header. +# --- + +# Warm up: create the manifest and resolve dependencies so subsequent +# invocations don't emit setup messages that would confuse the warning checks. +test_run resolve-deps + +# A consumer that uses the module system: should build without emitting our warning. +test_not_out "designed for use with the module system" build Test.ModuleConsumer + +# A non-module consumer: should build successfully but emit the warning, +# naming both the importing file and the imported package. +test_out "Test/NonModuleConsumer.lean: imports \`Dep\` from package \`dep\`, which is designed for use with the module system" \ + build Test.NonModuleConsumer + +# Opt out of the warning by setting `silenceRequiresModuleSystemWarning` on the +# importing package. After a clean rebuild, the warning must not appear. +sed_i '1a silenceRequiresModuleSystemWarning = true' lakefile.toml +test_run clean +test_not_out "designed for use with the module system" build Test.NonModuleConsumer + +# Restore the lakefile and clean up. +sed_i '/^silenceRequiresModuleSystemWarning = true$/d' lakefile.toml +rm -f produced*