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

Conversation

Baccata
Copy link
Contributor

@Baccata Baccata commented Apr 9, 2018

Following com-lihaoyi/Ammonite#792

Builds are now able to load external projects (also built with mill), and depend on them as if they were local submodules, using simple ammonite imports :

import $file.outer.path.build

Without this patch, external builds are compiled using pwd to create millSourcePath, disregarding the original location of the outer projects, and their sources are looked up in the current directory rather than the external one.

@lihaoyi
Copy link
Member

lihaoyi commented Apr 9, 2018

@Baccata since this deals with build.sc files, how about a new ScriptTestSuite in the same vein as https://github.com/lihaoyi/mill/blob/master/main/test/src/mill/main/JavaCompileJarTests.scala#L7? You can set up your dummy build (doesn't need to be a Scala project) in the resources/ folder, with multiple folders with build.sc files, and then exercise the various tasks to make sure they're doing the right things in the right folders

@Baccata Baccata force-pushed the allow-imports-of-external-builds branch from 661fb6b to d0fa222 Compare April 10, 2018 13:31
@Baccata
Copy link
Contributor Author

Baccata commented Apr 10, 2018

@lihaoyi, say I depend on an outer build under outerDir. Should the current pwd/out be used to cache the tasks from the outer build (in which case dest of the outer tasks should probably be amended to retain uniqueness), or should outerDir/out be used ?

I think I know how to implement either ways, it's just a matter of making the decision ^^

@lihaoyi
Copy link
Member

lihaoyi commented Apr 10, 2018

@Baccata one thing that isn't clear to me is what you mean by outer; from the code snippets, it seems like the imported builds would be inside the current folder, not outside. Is your envisioned use case aggregating sub-project builds into the top-level build.sc, or is it to import builds from somewhere else on the filesystem entirely?

Would import $file.^.^.foo.build work?

It's a bit unclear to me whether a shared top-level/enclosing out/ folder (bazel style) or per-build-file out/ folder (SBT style) would be preferable.

Apart from re-wiring up dest, you would also have to re-wire millSourcePath on the imported build file. That may take a bit of fiddling and you may need to make a custom import $build hook to deal with that

@Baccata
Copy link
Contributor Author

Baccata commented Apr 10, 2018

Is your envisioned use case aggregating sub-project builds into the top-level build.sc, or is it to import builds from somewhere else on the filesystem entirely?

My bad, should have been more descriptive. I don't have a definitive answer to that question. My usecase started from the former : aggregating sub-projects builds (for instance, giving the option to split code into git submodules, allowing for faster dev cycles when teams extract code as libraries). However, I realised that since ammonite allows to import external scripts from elsewhere in the filesystem, why not just allow for that ? This case encompasses the case where we only import modules defined in subdirectories.

Would import $file.^.^.foo.build work?

It actually does, with the current changes ! However, whilst writing tests, I realised that with the current modifications, ^.^.foo.build.bar (a task defined in the outer build) and bar (a task present in the current build) would have their dest collide, which breaks the "all tasks have their own unique space in FS to store stuff" guarantee.

It's a bit unclear to me whether a shared top-level/enclosing out/ folder (bazel style) or per-build-file out/ folder (SBT style) would be preferable.

Roll a dice ? 😸 If you don't have a clear preference, I think I'll have a go at implementing the former (bazel style). The only benefit I see from the latter (sbt style) is that it'd save some time when moving from one dir to another as things might already have been cached. Not sure it's worth it

Apart from re-wiring up dest, you would also have to re-wire millSourcePath on the imported build file. That may take a bit of fiddling and you may need to make a custom import $build hook to deal with that

With the current changes, millSourcePath is correctly set to the actual location of the imported build within the file system 😄(and it propagates correctly to its inner modules / tasks).

@Baccata
Copy link
Contributor Author

Baccata commented Apr 10, 2018

@lihaoyi for the sake of the discussion, I pushed some WIP code which include some tests b4f041d . Particularly here

@lihaoyi
Copy link
Member

lihaoyi commented Apr 10, 2018

I suppose the main use case of SBT-style per-subproject out/ folders is you could move around the filesystem of a large project and build things in each folder without rebuilding the world each time you cd.

This can also be accomplished by having the sub projects "discover" what the outer-most build.sc file is, e.g. what Git or Bazel do. Bazel has the problem of needing a special marker file to demarcate the "outermost" boundary, while git has the problem of sometimes noticing outer .git folders even when you don't want it to.

Maybe we can just punt on the problem for now and accept the a bit of unnecessary re-building when you cd.

w.r.t. filesystem-collisions, there are a few approaches we could take:

  • We could prohibit the case where you both have a foo/build.sc and a object foo in the root build.sc. For example, using a special import $build.foo hook that injects a import $file.foo.build; final val foo = build into the current build.sc file. Not sure how this would work for outer ^ builds, but it should suffice for inner builds

  • We could come up with a scheme to disambiguate the two. e.g. inner builds could be put in some dest/inner-build/ folder, since modules can't have dashes in their names (we already make use of this for e.g. the mill-worker or mill-profile misc files)

  • We could just punt it and hope people don't make an object and a inner-folder-with-build-file of the same name. This is the Python foo.py/foo/__init__.py strategy

@Baccata
Copy link
Contributor Author

Baccata commented Apr 10, 2018

Maybe we can just punt on the problem for now and accept the a bit of unnecessary re-building when you cd.

Agreed

We could come up with a scheme to disambiguate the two. e.g. inner builds could be put in some dest/inner-build/ folder, since modules can't have dashes in their names (we already make use of this for e.g. the mill-worker or mill-profile misc files)

I think I'll roll with this. Adding another magic import that is not fully orthogonal with $file does not feel right to me, and disambiguating looks easy enough.

Thanks for the answers !

Baccata added 2 commits April 13, 2018 09:51
Builds are now able to load external projects and depend on them
as if they were local submodules.

`import $file.external.path.build`
* Calling modules loaded from external directories "Foreign" to avoid
conflicting with the already existing concept of "ExternalModule".
* Amended the way `dest` is computed for foreign modules
* Added tests to check that the source paths and dest are as expected
* Added a test to show that local modules do not conflict with foreign
modules when they are named the same
@Baccata Baccata force-pushed the allow-imports-of-external-builds branch from b4f041d to 60d1cdc Compare April 13, 2018 07:51
@@ -30,7 +30,6 @@ case class Labelled[T](task: NamedTask[T],
}
case class Evaluator[T](home: Path,
outPath: Path,
externalOutPath: Path,
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 parameter was always equal to outPath (except in one test), so I took the liberty to remove. I think I could add a external-modules segment in out in the new destSegments conditioned by the module/task being "external". Thoughts ?

Copy link
Member

Choose a reason for hiding this comment

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

We re-used externalOutPaths in tests to speed up the test suite; otherwise re-initializing the Zinc incremental compiler made them take forever. Do you have a good reason to believe this is no longer the case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah ! Fair enough, reverted there 1607f78

@@ -191,6 +191,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

@@ -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

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

@@ -8,28 +8,29 @@ import utest._
abstract class ScriptTestSuite(fork: Boolean) extends TestSuite{
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Add to tweak this a bit to accommodate the new tests. Changes are not breaking

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.

@Baccata Baccata force-pushed the allow-imports-of-external-builds branch from b49d657 to 6d2b1f3 Compare April 13, 2018 12:07
.appveyor.yml Outdated
@@ -46,6 +46,7 @@ build_script:
C:\%CYGWIN_DIR%\bin\bash -lc "curl -Lo /usr/local/bin/mill %MILL_URL%" &&
C:\%CYGWIN_DIR%\bin\bash -lc 'sed -i '"'"'0,/-cp "\$0"/{s/-cp "\$0"/-cp `cygpath -w "\$0"`/}; 0,/-cp "\$0"/{s/-cp "\$0"/-cp `cygpath -w "\$0"`/}'"'"' /usr/local/bin/mill' &&
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")
C:\%CYGWIN_DIR%\bin\bash -lc "cd /cygdrive/c/mill && mill -i all __.publishLocal release" &&
Copy link
Contributor Author

Choose a reason for hiding this comment

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

the cygwin job was not bootstrapping before testing, leading the tests to fail

Copy link
Member

Choose a reason for hiding this comment

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

Can you explain why this is necessary? I don't see any reason the changes I've reviewed would cause bootstrapping issues

Copy link
Contributor Author

@Baccata Baccata Apr 14, 2018

Choose a reason for hiding this comment

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

Sure. I momentarily switched output stream to System.out in failing test to understand what was happening.

As far as I understand, without bootstrapping the foreign0 param was not defined in the version of mill used to compile this wrapping code

This started happened locally as well after I rebased the changes against the master (I had been out of sync for ~10 days I think)

Copy link
Member

Choose a reason for hiding this comment

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

Running the unit tests via mill all main.test should use the freshly-compiled version of Mill; the fact that it's including foreign0 in the code wrapper is evidence of this. Thus I do not see why that freshly-compiled Mill would not be able to handle foreign0 during compilation.

Furthermore, the Travis build ran the foreign module tests also without bootstrapping, but successfully.

Can you dig into this a bit deeper? We should figure out what is going on here rather than just working around the issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do

@Baccata
Copy link
Contributor Author

Baccata commented Apr 13, 2018

@lihaoyi, I think I've reached a satisfying state for this. If you're happy with it, I'll proceed and add some documentation

@lihaoyi
Copy link
Member

lihaoyi commented Apr 13, 2018

Apart from one comment looks ok i guess

* Add documentation for foreign-modules
@Baccata Baccata force-pushed the allow-imports-of-external-builds branch from 3945ce4 to 1607f78 Compare April 14, 2018 10:07
@Baccata
Copy link
Contributor Author

Baccata commented Apr 18, 2018

@lihaoyi, regarding the failed appveyor build when using the cygwin environment, here's what I found so far :

  • The notable differences between the cygwin one and (either of) the others :
    1. the tests are not run using a bootstrapped mill
    2. JDK 9 is used
  • Switched to JDK 9 locally , and found out the problem is reproducible (mill main.test).
  • Wondered why JavaCompileJarTests was green in the same build, found that it does not execute when java version is 9 . When the condition is commented, these tests fail for the same reason
  • Here's a minimal example that show the issue occurring when running the tests in JDK 9.

I'm happy to investigate further, but this shows the PR is not at cause. I'd like to make the CI green again, which I can make by either re-adding the bootstrapping line in the cygwin env, or disabling the tests like in JavaCompileJarTests. So my next questions would be :

  • Any reason why bootstrapping does not occur in the cygwin build, or was it just an oversight ?
  • I assume you had a good reason for disabling JavaCompileJarTests in java9. What would that be ?

@lihaoyi
Copy link
Member

lihaoyi commented Apr 18, 2018

@Baccata we wanted to run our test suite with a mix of bootstrapped and non-bootstraped runs, to make sure it works on both.

I don't remember the reason for disabling JavaCompileJarTests in java9; it probably just slipped in during the chaos of getting java9 working.

I think your options are to either figure out what's causing the non-bootstrapped test to fail, or disabling it in java 9. I don't think turning on bootstrapping temporarily to get them working is viable: that just pushes the problem down the line to when it fails some time in future.

If you disable it in java9, open an issue to write down what you know about the problem.

@lihaoyi
Copy link
Member

lihaoyi commented Apr 18, 2018

Looks good to me

@lihaoyi lihaoyi merged commit 7898368 into com-lihaoyi:master Apr 18, 2018
@Baccata Baccata deleted the allow-imports-of-external-builds branch April 20, 2018 09:29
@lefou lefou added this to the 0.2.1 milestone May 2, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants