Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make builds able to depend on external projects #291

Merged
merged 7 commits into from
Apr 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ build_script:
C:\%CYGWIN_DIR%\bin\bash -lc 'chmod +x /usr/local/bin/mill' &&
C:\%CYGWIN_DIR%\bin\bash -lc "cd /cygdrive/c/mill && mill -i all main.test scalajslib.test")

skip_branch_with_pr: true
skip_branch_with_pr: true
2 changes: 1 addition & 1 deletion build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ object core extends MillModule {
)

def ivyDeps = Agg(
ivy"com.lihaoyi:::ammonite:1.1.0-14-037b8eb",
ivy"com.lihaoyi:::ammonite:1.1.0-16-147fdfe",
// Necessary so we can share the JNA classes throughout the build process
ivy"net.java.dev.jna:jna:4.5.0",
ivy"net.java.dev.jna:jna-platform:4.5.0"
Expand Down
7 changes: 5 additions & 2 deletions core/src/mill/define/BaseModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ object BaseModule{
case class Implicit(value: BaseModule)
}

abstract class BaseModule(millSourcePath0: Path, external0: Boolean = false)
abstract class BaseModule(millSourcePath0: Path,
external0: Boolean = false,
foreign0 : Boolean = false)
(implicit millModuleEnclosing0: sourcecode.Enclosing,
millModuleLine0: sourcecode.Line,
millName0: sourcecode.Name,
Expand All @@ -20,6 +22,7 @@ abstract class BaseModule(millSourcePath0: Path, external0: Boolean = false)
Segments(),
mill.util.Router.Overrides(0),
Ctx.External(external0),
Ctx.Foreign(foreign0),
millFile0
)
){
Expand All @@ -37,7 +40,7 @@ abstract class BaseModule(millSourcePath0: Path, external0: Boolean = false)
abstract class ExternalModule(implicit millModuleEnclosing0: sourcecode.Enclosing,
millModuleLine0: sourcecode.Line,
millName0: sourcecode.Name)
extends BaseModule(ammonite.ops.pwd, external0 = true){
extends BaseModule(ammonite.ops.pwd, external0 = true, foreign0 = false){

implicit def millDiscoverImplicit: Discover[_] = millDiscover
assert(
Expand Down
16 changes: 14 additions & 2 deletions core/src/mill/define/Ctx.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package mill.define


import ammonite.ops.{Path, RelPath}
import ammonite.ops.Path

import scala.annotation.implicitNotFound

Expand Down Expand Up @@ -41,6 +41,7 @@ case class Segments(value: Segment*){
}
head +: stringSegments
}
def last : Segments = Segments(value.last)
def render = value.toList match {
case Nil => ""
case Segment.Label(head) :: rest =>
Expand All @@ -52,6 +53,13 @@ case class Segments(value: Segment*){
}
}

object Segments {

def labels(values : String*) : Segments =
Segments(values.map(Segment.Label):_*)

}

@implicitNotFound("Modules, Targets and Commands can only be defined within a mill Module")
case class Ctx(enclosing: String,
lineNum: Int,
Expand All @@ -60,18 +68,21 @@ case class Ctx(enclosing: String,
segments: Segments,
overrides: Int,
external: Boolean,
foreign: Boolean,
fileName: String){
}

object Ctx{
case class External(value: Boolean)
case class Foreign(value : Boolean)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to choose a name for the concept "builds that originate from other directories", and External was already taken. Obviously I'm not a native English speaker, so perhaps there's a term that would suit the concept better.

implicit def make(implicit millModuleEnclosing0: sourcecode.Enclosing,
millModuleLine0: sourcecode.Line,
millName0: sourcecode.Name,
millModuleBasePath0: BasePath,
segments0: Segments,
overrides0: mill.util.Router.Overrides,
external0: External,
foreign0: Foreign,
fileName: sourcecode.File): Ctx = {
Ctx(
millModuleEnclosing0.value,
Expand All @@ -81,7 +92,8 @@ object Ctx{
segments0,
overrides0.value,
external0.value,
foreign0.value,
fileName.value
)
}
}
}
1 change: 1 addition & 0 deletions core/src/mill/define/Module.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class Module(implicit outerCtx0: mill.define.Ctx)
def millOuterCtx = outerCtx0
def millSourcePath: Path = millOuterCtx.millSourcePath / millOuterCtx.segment.pathSegments
implicit def millModuleExternal: Ctx.External = Ctx.External(millOuterCtx.external)
implicit def millModuleShared: Ctx.Foreign = Ctx.Foreign(millOuterCtx.foreign)
implicit def millModuleBasePath: BasePath = BasePath(millSourcePath)
implicit def millModuleSegments: Segments = {
millOuterCtx.segments ++ Seq(millOuterCtx.segment)
Expand Down
28 changes: 26 additions & 2 deletions core/src/mill/eval/Evaluator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,12 @@ case class Evaluator[T](home: Path,
(newResults, newEvaluated, false)
case Right(labelledNamedTask) =>

val out = if (!labelledNamedTask.task.ctx.external) outPath
else externalOutPath

val paths = Evaluator.resolveDestPaths(
if (!labelledNamedTask.task.ctx.external) outPath else externalOutPath,
labelledNamedTask.segments
out,
destSegments(labelledNamedTask)
)

if (!exists(paths.out)) mkdir(paths.out)
Expand Down Expand Up @@ -191,6 +194,27 @@ case class Evaluator[T](home: Path,
}
}
}

def destSegments(labelledTask : Labelled[_]) : Segments = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the core of the PR I guess. This makes sure that "foreign" modules do not conflict with local modules

import labelledTask.task.ctx
if (ctx.foreign) {
val prefix = "foreign-modules"
// Computing a path in "out" that uniquely reflects the location
// of the foreign module relatively to the current build.
val relative = labelledTask.task
.ctx.millSourcePath
.relativeTo(rootModule.millSourcePath)
// Encoding the number of `/..`
val ups = if (relative.ups > 0) Segments.labels(s"up-${relative.ups}")
else Segments()
Segments.labels(prefix)
.++(ups)
.++(Segments.labels(relative.segments: _*))
.++(labelledTask.segments.last)
} else labelledTask.segments
}


def handleTaskResult(v: Any,
hashCode: Int,
metaPath: Path,
Expand Down
39 changes: 38 additions & 1 deletion docs/pages/5 - Modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,41 @@ that is shared by the entire build: for example,
`mill.scalalib.ScalaWorkerApi/scalaWorker` provides a shared Scala compilation
service & cache that is shared between all `ScalaModule`s, and
`mill.scalalib.GenIdea/idea` lets you generate IntelliJ projects without
needing to define your own `T.command` in your `build.sc` file
needing to define your own `T.command` in your `build.sc` file

## Foreign Modules

Mill can load other mill projects from external (or sub) directories,
using Ammonite's `$file` magic import, allowing to depend on foreign modules.
This allows, for instance, to depend on other projects' sources, or split
your build logic into smaller files.


For instance, assuming the following stucture :

```text
foo/
build.sc
bar/
build.sc
baz/
build.sc
```

you can write the following in `foo/build.sc` :

```scala

import $file.bar.build
import $file.^.baz.build
import mill._

def someFoo = T {

^.baz.build.someBaz(...)
bar.build.someBar(...)
...
}
```

The output of the foreign tasks will be cached under `foo/out/foreign-modules/`.
15 changes: 11 additions & 4 deletions main/src/mill/main/MainRunner.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package mill.main
import java.io.{InputStream, OutputStream, PrintStream}
import java.io.{InputStream, PrintStream}

import ammonite.Main
import ammonite.interp.{Interpreter, Preprocessor}
import ammonite.ops.Path
import ammonite.util.Util.CodeSource
import ammonite.util._
import mill.eval.{Evaluator, PathRef}
import mill.util.PrintLogger
Expand Down Expand Up @@ -120,20 +121,26 @@ class MainRunner(val config: ammonite.main.Cli.Config,

object CustomCodeWrapper extends Preprocessor.CodeWrapper {
def apply(code: String,
pkgName: Seq[ammonite.util.Name],
source: CodeSource,
imports: ammonite.util.Imports,
printCode: String,
indexedWrapperName: ammonite.util.Name,
extraCode: String): (String, String, Int) = {
import source.pkgName
val wrapName = indexedWrapperName.backticked
val literalPath = pprint.Util.literalize(config.wd.toString)
val path = source
.path
.map(path => path.toNIO.getParent)
.getOrElse(config.wd.toNIO)
val literalPath = pprint.Util.literalize(path.toString)
val external = !(path.compareTo(config.wd.toNIO) == 0)
val top = s"""
|package ${pkgName.head.encoded}
|package ${Util.encodeScalaSourcePath(pkgName.tail)}
|$imports
|import mill._
|object $wrapName
|extends mill.define.BaseModule(ammonite.ops.Path($literalPath))
|extends mill.define.BaseModule(ammonite.ops.Path($literalPath), foreign0 = $external)
|with $wrapName{
| // Stub to make sure Ammonite has something to call after it evaluates a script,
| // even if it does nothing...
Expand Down
24 changes: 24 additions & 0 deletions main/test/resources/examples/foreign/conflict/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import $file.inner.{build => innerBuild}
import mill._
import ammonite.ops._

// In this build, we have a local module targeting
// the 'inner sub-directory, and an imported foreign
// module in that same directory. Their sourcePaths
// should be the same, but their dest paths should
// be different to avoid both modules over-writing
// each other's caches .

def checkPaths : T[Unit] = T {
if (innerBuild.millSourcePath != inner.millSourcePath)
throw new Exception("Source paths should be the same")
}

def checkDests : T[Unit] = T {
if (innerBuild.selfDest == inner.selfDest)
throw new Exception("Dest paths should be different")
}

object inner extends mill.Module {
def selfDest = T { T.ctx().dest / up / up }
}
4 changes: 4 additions & 0 deletions main/test/resources/examples/foreign/conflict/inner/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import mill._
import ammonite.ops._

def selfDest = T { T.ctx().dest / up / up }
16 changes: 16 additions & 0 deletions main/test/resources/examples/foreign/outer/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import $file.inner.build
import mill._
import ammonite.ops._

trait PathAware extends mill.Module {
def selfPath = T { millSourcePath }
}

trait DestAware extends mill.Module {
def selfDest = T { T.ctx().dest / up / up }
}

object sub extends PathAware with DestAware {
object sub extends PathAware with DestAware
}

15 changes: 15 additions & 0 deletions main/test/resources/examples/foreign/outer/inner/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import mill._
import ammonite.ops._

trait PathAware extends mill.Module {
def selfPath = T { millSourcePath }
}

trait DestAware extends mill.Module {
def selfDest = T { T.ctx().dest / up / up }
}

object sub extends PathAware with DestAware {
object sub extends PathAware with DestAware
}

82 changes: 82 additions & 0 deletions main/test/resources/examples/foreign/project/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import $file.^.outer.build
import $file.inner.build

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the main test, it checks the consistency of sourcePaths and dest

import ammonite.ops._
import mill._

def assertPaths(p1 : Path, p2 : Path) : Unit = if (p1 != p2) throw new Exception(
s"Paths were not equal : \n- $p1 \n- $p2"
)

object sub extends PathAware with DestAware {

object sub extends PathAware with DestAware

object sub2 extends ^.outer.build.PathAware with ^.outer.build.DestAware

}

def checkProjectPaths = T {
val thisPath : Path = millSourcePath
assert(thisPath.last == "project")
assertPaths(sub.selfPath(), thisPath / 'sub)
assertPaths(sub.sub.selfPath(), thisPath / 'sub / 'sub)
assertPaths(sub.sub2.selfPath(), thisPath / 'sub / 'sub2)
}

def checkInnerPaths = T {
val thisPath : Path = millSourcePath
assertPaths(inner.build.millSourcePath, thisPath / 'inner )
assertPaths(inner.build.sub.selfPath(), thisPath / 'inner / 'sub)
assertPaths(inner.build.sub.sub.selfPath(), thisPath / 'inner / 'sub / 'sub)
}

def checkOuterPaths = T {
val thisPath : Path = millSourcePath
assertPaths(^.outer.build.millSourcePath, thisPath / up / 'outer )
assertPaths(^.outer.build.sub.selfPath(), thisPath / up / 'outer / 'sub)
assertPaths(^.outer.build.sub.sub.selfPath(), thisPath / up / 'outer / 'sub / 'sub)
}

def checkOuterInnerPaths = T {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checking that importing a foreign build which imports a foreign build works as expected

val thisPath : Path = millSourcePath
assertPaths(^.outer.inner.build.millSourcePath, thisPath / up / 'outer / 'inner )
assertPaths(^.outer.inner.build.sub.selfPath(), thisPath / up / 'outer / 'inner /'sub)
assertPaths(^.outer.inner.build.sub.sub.selfPath(), thisPath / up / 'outer / 'inner / 'sub / 'sub)
}

def checkProjectDests = T {
val outPath : Path = millSourcePath / 'out
assertPaths(sub.selfDest(), outPath / 'sub)
assertPaths(sub.sub.selfDest(), outPath / 'sub / 'sub)
assertPaths(sub.sub2.selfDest(), outPath / 'sub / 'sub2)
}

def checkInnerDests = T {
val foreignOut : Path = millSourcePath / 'out / "foreign-modules"
assertPaths(inner.build.sub.selfDest(), foreignOut / 'inner / 'sub)
assertPaths(inner.build.sub.sub.selfDest(), foreignOut / 'inner / 'sub / 'sub)
}

def checkOuterDests = T {
val foreignOut : Path = millSourcePath / 'out / "foreign-modules"
assertPaths(^.outer.build.sub.selfDest(), foreignOut / "up-1" / 'outer/ 'sub )
assertPaths(^.outer.build.sub.sub.selfDest(), foreignOut / "up-1" / 'outer/ 'sub / 'sub)
}

def checkOuterInnerDests = T {
val foreignOut : Path = millSourcePath / 'out / "foreign-modules"
assertPaths(^.outer.inner.build.sub.selfDest(), foreignOut / "up-1" / 'outer/ 'inner / 'sub)
assertPaths(^.outer.inner.build.sub.sub.selfDest(), foreignOut / "up-1" / 'outer/ 'inner / 'sub / 'sub)
}


trait PathAware extends mill.Module {

def selfPath = T { millSourcePath }
}

trait DestAware extends mill.Module {
def selfDest = T { T.ctx().dest / up / up }
}

Loading