-
-
Notifications
You must be signed in to change notification settings - Fork 371
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Define a way for users to extend the top-level module? #1612
Comments
The approach with marker trait seems less "magical" than the special import syntax, so it would be easier to understand and make the getting started experience better for new users. |
Best is, we don't need any magic at all. The marker trait is the right direction. We should also review |
I find having a special object in the build as a def scalaVersion = "2.13.8" in the top level for it to compile. |
That's why I suggested to kind of reuse |
@lolgab Ah, now I think I see what you mean. I think when we use a special top level module, we should simply ignore or reject other top level tasks. |
But I don't see the point of having all the extra code, concepts and complexity if then users need to write the same build file but with a def scalaVersion = "2.13.8" instead of the current: object app extends ScalaModule {
def scalaVersion = "2.13.8"
} would make the newcomer experience nicer. Also putting your code in the root is already supported via: def millSourcePath = super.millSourcePath / os.up |
The question is how does Mill know the top level module is a ScalaModule? It could be a JavaModule, a ScalaNativeModule, or something user defined: I often have a FooScalaModule defined in my Mill projects which is ScalaModule with my project-specific customizations, and I'd like my top-level module to inherit that stuff just as my nested modules inherit it Having the top level module hardcoded to ScalaModule would work in trivial cases, but that approach doesnt transition smoothly as the build gets more complex |
@lihaoyi My suggestion is for the supertype to be coming from the mill configuration (like the |
First I want to say having to explicitly declare a top level module isn't IMHO a big deal for newcomer or expert. It's only one line above and a curly brace below the other code, so I think we should not over-use this as couter-argument. What is the alternative? I never liked the much heavier paradigm shift needed in sbt when transitioning between a single and multi project setups. Also having an explicit top module type makes it easy for anybody far away from an IDE to reason about the project and the available targets. So, having that extra line is an advantage in my eyes, and much better than yet another magic import. Speaking about transition cost between single and multi projects. It's rather straight forward to add just a second top level module or a sub-module, so it's nothing a beginner tries to avoid, which is a good thing too. Also, an explicit top level module allows us to start with customizations already applied, e.g. when the top module comes from an Essentially, the only thing we need to change in mill itself is how it's handling the build script. And this brings the question whether there are alternative approaches? I think one alternative is (what @lolgab suggested too) to just handle the Mill cli differently. When we assume or configure a prefix, Mill could automatically handle all cli arguments that don't resolve by applying this prefix. Example: A simple project defines a The pros of this approach is, that we don't need to change anything that affects the compilation and reflection of the build file. And last not least, another thought where I'm not sure it's cleanly possible, but which might feel intuitive. Instead of a marker trait, we could just match on name, e.g. |
Just to make this clear. For me, this RFC is not so much about making the build file smaller but about having a more convenient Mill cli. |
I don't think this is a good idea. The |
This is probably the point of friction that makes us having different ideas. |
I think having a top-level I'm OK with matching on the module name |
As a side note: I like to see Mill as having one single source of truth, the This is an huge advantage over many other build tools and it's impact is probably underestimated. It helps to easily understand build setups and assist reproducibility. |
I can see we get some clearer idea how it could work, nice. Although the explicit About the various commands, I remember from my early Mill days that we had a discussion about moving some of them into generic cli arguments (probably because I asked that). After that, my understanding was then, that we prefer "Evaluator Commands" because they are also reachable from the Mill REPL. But we have other issues with these kind of commands and will replace them by something more main-args |
One idea is to have a By overriding it, we could easily replace the "top level" module. import $file.`my-plugin`
override def mainModule = `my-plugin`.MyCoolTopLevelModule This won't work out of the box. What we really want is an |
... as a consequence, other top-level |
While thinking about a solution to I came across the using directive, which is used by Scala-cli and available as a stand-alone Java library (https://github.com/VirtusLab/using_directives), and which could be a lightweight replacement for Ammonite magic imports (
|
Directly extending a given module: //> using extends "mill.scalalib.ScalaModule"
override def scalaVersion = // ... Extending a (local) self-defined module: This one will probably not work, as //> using extends "Common"
trait Common extends ScalaModule {
def scalaVersion = // ...
}
def ivyDeps = Agg( // ... I'm not sure we want to allow extending without a //> using extends.only "mill.scalalib.ScalaModule"
def scalaVersion = // ... |
While the above examples would allow slick single module projects, we could also go the other way, by not wrapping at all. //> using mill.entry `TopModule`
trait TopModule extends mill.define.Module {
} |
Done in #2377 You can now import mill._, scalalib._
object foo extends RootModule with ScalaModule {
def scalaVersion = "2.13.8"
def ivyDeps = Agg(
ivy"com.lihaoyi::scalatags:0.8.2",
ivy"com.lihaoyi::mainargs:0.4.0"
)
} |
Currently a user can define tasks top-level, and can define tasks and inherit things in submodules, but there is no way to inherit things in the top-level module. Apart from being a bit irregular, this means the common SBT style of doing
sbt run
sbt compile
in a trivial one-module project cannot be done without prefixes like./mill app.run
./mill app.compile
I'm honestly not 100% what this should look like; some options are:
We define a special syntax used for inheriting modules, e.g.
import $extends.
mill.scalalib.ScalaModule``, and have that be spliced into the source code wrapper we run the scripts with. Downstream compilation and usage remains unchanged.We let people annotate a module as "top-level", e.g. via a special name
object build extends ScalaModule{}
or a marker traitobject foo extends ScalaModule with TopLevelModule
, and automatically recurse into the module's contents as part of our target resolve logicSomething else?
This would make working with Mill builds a bit more regular, especially for small ones, and help simplify the getting started experience for Mill, bringing it up to par with SBT
The text was updated successfully, but these errors were encountered: