From 9ff11aa7fdfeccb7ba748ce55d5d6a9b4df93ccb Mon Sep 17 00:00:00 2001
From: Your Name <haoyi.sg@gmail.com>
Date: Fri, 19 May 2023 20:14:22 -0700
Subject: [PATCH 01/10] .

---
 .../src/mill/scalalib/CrossScalaModule.scala     |  8 --------
 scalalib/src/mill/scalalib/JavaModule.scala      |  6 ++++++
 .../src/mill/scalalib/PlatformScalaModule.scala  | 16 +++++++++-------
 3 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/scalalib/src/mill/scalalib/CrossScalaModule.scala b/scalalib/src/mill/scalalib/CrossScalaModule.scala
index 017ea56f4d1..7c025c90149 100644
--- a/scalalib/src/mill/scalalib/CrossScalaModule.scala
+++ b/scalalib/src/mill/scalalib/CrossScalaModule.scala
@@ -17,12 +17,4 @@ trait CrossScalaModule extends ScalaModule with CrossModuleBase {
     super.sources() ++
       scalaVersionDirectoryNames.map(s => PathRef(millSourcePath / s"src-$s"))
   }
-
-  trait CrossScalaModuleTests extends ScalaModuleTests {
-    override def sources = T.sources {
-      super.sources() ++
-        scalaVersionDirectoryNames.map(s => PathRef(millSourcePath / s"src-$s"))
-    }
-  }
-  trait Tests extends CrossScalaModuleTests
 }
diff --git a/scalalib/src/mill/scalalib/JavaModule.scala b/scalalib/src/mill/scalalib/JavaModule.scala
index c0b71cc633b..d1dd956b0a1 100644
--- a/scalalib/src/mill/scalalib/JavaModule.scala
+++ b/scalalib/src/mill/scalalib/JavaModule.scala
@@ -41,7 +41,13 @@ trait JavaModule
     override def zincWorker: ZincWorkerModule = outer.zincWorker
     override def skipIdea: Boolean = outer.skipIdea
     override def runUseArgsFile: Target[Boolean] = T { outer.runUseArgsFile() }
+    override def sources = T.sources{
+      for(src <- outer.sources()) yield{
+        PathRef(this.millSourcePath / src.path.relativeTo(outer.millSourcePath))
+      }
+    }
   }
+
   trait Tests extends JavaModuleTests
 
   def defaultCommandName(): String = "run"
diff --git a/scalalib/src/mill/scalalib/PlatformScalaModule.scala b/scalalib/src/mill/scalalib/PlatformScalaModule.scala
index 21f42e1e26f..5a294d77430 100644
--- a/scalalib/src/mill/scalalib/PlatformScalaModule.scala
+++ b/scalalib/src/mill/scalalib/PlatformScalaModule.scala
@@ -16,13 +16,15 @@ trait PlatformScalaModule extends ScalaModule {
   override def millSourcePath = super.millSourcePath / os.up
 
   override def sources = T.sources {
-    val platform = millModuleSegments.parts.last
-    super.sources().flatMap(source =>
-      Seq(
-        source,
-        PathRef(source.path / os.up / s"${source.path.last}-${platform}")
-      )
-    )
+    val platform = millModuleSegments
+      .value
+      .collect { case l: mill.define.Segment.Label => l.value }
+      .last
+
+    super.sources().flatMap { source =>
+      val platformPath = PathRef(source.path / _root_.os.up / s"${source.path.last}-${platform}")
+      Seq(source, platformPath)
+    }
   }
 
   override def artifactNameParts = super.artifactNameParts().dropRight(1)

From 3c20ced378fa66637bdf558289c27388b7d9a50f Mon Sep 17 00:00:00 2001
From: Your Name <haoyi.sg@gmail.com>
Date: Fri, 19 May 2023 20:26:27 -0700
Subject: [PATCH 02/10] wip

---
 .../bar/src-2/BarVersionSpecific.scala        |  4 +
 .../bar/src-3/BarVersionSpecific.scala        |  4 +
 .../bar/src/Bar.scala                         |  5 ++
 .../bar/test/src/BarTests.scala               | 12 +++
 .../web/7-cross-platform-version-2/build.sc   | 77 +++++++++++++++++++
 .../foo/bar/src-2/BarVersionSpecific.scala    |  4 +
 .../foo/bar/src-3/BarVersionSpecific.scala    |  4 +
 .../foo/bar/src/Bar.scala                     |  5 ++
 .../foo/bar/test/src/BarTests.scala           | 12 +++
 .../foo/qux/src-js/QuxPlatformSpecific.scala  |  8 ++
 .../foo/qux/src-jvm/QuxPlatformSpecific.scala |  7 ++
 .../foo/qux/src/Qux.scala                     |  9 +++
 .../foo/qux/test/src/QuxTests.scala           | 12 +++
 .../qux/src-js/QuxPlatformSpecific.scala      |  8 ++
 .../qux/src-jvm/QuxPlatformSpecific.scala     |  7 ++
 .../qux/src/Qux.scala                         |  9 +++
 .../qux/test/src/QuxTests.scala               | 12 +++
 17 files changed, 199 insertions(+)
 create mode 100644 example/web/7-cross-platform-version-2/bar/src-2/BarVersionSpecific.scala
 create mode 100644 example/web/7-cross-platform-version-2/bar/src-3/BarVersionSpecific.scala
 create mode 100644 example/web/7-cross-platform-version-2/bar/src/Bar.scala
 create mode 100644 example/web/7-cross-platform-version-2/bar/test/src/BarTests.scala
 create mode 100644 example/web/7-cross-platform-version-2/build.sc
 create mode 100644 example/web/7-cross-platform-version-2/foo/bar/src-2/BarVersionSpecific.scala
 create mode 100644 example/web/7-cross-platform-version-2/foo/bar/src-3/BarVersionSpecific.scala
 create mode 100644 example/web/7-cross-platform-version-2/foo/bar/src/Bar.scala
 create mode 100644 example/web/7-cross-platform-version-2/foo/bar/test/src/BarTests.scala
 create mode 100644 example/web/7-cross-platform-version-2/foo/qux/src-js/QuxPlatformSpecific.scala
 create mode 100644 example/web/7-cross-platform-version-2/foo/qux/src-jvm/QuxPlatformSpecific.scala
 create mode 100644 example/web/7-cross-platform-version-2/foo/qux/src/Qux.scala
 create mode 100644 example/web/7-cross-platform-version-2/foo/qux/test/src/QuxTests.scala
 create mode 100644 example/web/7-cross-platform-version-2/qux/src-js/QuxPlatformSpecific.scala
 create mode 100644 example/web/7-cross-platform-version-2/qux/src-jvm/QuxPlatformSpecific.scala
 create mode 100644 example/web/7-cross-platform-version-2/qux/src/Qux.scala
 create mode 100644 example/web/7-cross-platform-version-2/qux/test/src/QuxTests.scala

diff --git a/example/web/7-cross-platform-version-2/bar/src-2/BarVersionSpecific.scala b/example/web/7-cross-platform-version-2/bar/src-2/BarVersionSpecific.scala
new file mode 100644
index 00000000000..5a352dfeec7
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/bar/src-2/BarVersionSpecific.scala
@@ -0,0 +1,4 @@
+package bar
+object BarVersionSpecific {
+  def text(): String = "Specific code for Scala 2.x"
+}
diff --git a/example/web/7-cross-platform-version-2/bar/src-3/BarVersionSpecific.scala b/example/web/7-cross-platform-version-2/bar/src-3/BarVersionSpecific.scala
new file mode 100644
index 00000000000..4b7da7c369d
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/bar/src-3/BarVersionSpecific.scala
@@ -0,0 +1,4 @@
+package bar
+object BarVersionSpecific {
+  def text(): String = "Specific code for Scala 3.x"
+}
diff --git a/example/web/7-cross-platform-version-2/bar/src/Bar.scala b/example/web/7-cross-platform-version-2/bar/src/Bar.scala
new file mode 100644
index 00000000000..e8e32282c79
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/bar/src/Bar.scala
@@ -0,0 +1,5 @@
+package bar
+import scalatags.Text.all._
+object Bar {
+  val value = p("world", " ", BarVersionSpecific.text())
+}
diff --git a/example/web/7-cross-platform-version-2/bar/test/src/BarTests.scala b/example/web/7-cross-platform-version-2/bar/test/src/BarTests.scala
new file mode 100644
index 00000000000..8f7f1407f5e
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/bar/test/src/BarTests.scala
@@ -0,0 +1,12 @@
+package bar
+import utest._
+object BarTests extends TestSuite {
+  def tests = Tests {
+    test("test") {
+      val result = Bar.value.toString
+      val matcher = "<p>world Specific code for Scala [23].x</p>".r
+      assert(matcher.matches(result))
+      result
+    }
+  }
+}
diff --git a/example/web/7-cross-platform-version-2/build.sc b/example/web/7-cross-platform-version-2/build.sc
new file mode 100644
index 00000000000..10e1c6704f1
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/build.sc
@@ -0,0 +1,77 @@
+import mill._, scalalib._, scalajslib._, publish._
+
+trait Shared extends CrossScalaModule with PlatformScalaModule with PublishModule {
+  def publishVersion = "0.0.1"
+
+  def pomSettings = PomSettings(
+    description = "Hello",
+    organization = "com.lihaoyi",
+    url = "https://github.com/lihaoyi/example",
+    licenses = Seq(License.MIT),
+    versionControl = VersionControl.github("lihaoyi", "example"),
+    developers = Seq(Developer("lihaoyi", "Li Haoyi", "https://github.com/lihaoyi"))
+  )
+
+  def ivyDeps = Agg(ivy"com.lihaoyi::scalatags::0.12.0")
+
+  object test extends Tests {
+    def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.7.11")
+    def testFramework = "utest.runner.Framework"
+  }
+}
+
+trait SharedJS extends Shared with ScalaJSModule {
+  def scalaJSVersion = "1.13.0"
+}
+
+val scalaVersions = Seq("2.13.8", "3.2.2")
+
+object bar extends Module {
+  object jvm extends Cross[JvmModule](scalaVersions)
+  trait JvmModule extends Shared
+
+  object js extends Cross[JsModule](scalaVersions)
+  trait JsModule extends SharedJS
+}
+
+object qux extends Module{
+  object jvm extends Cross[JvmModule](scalaVersions)
+  trait JvmModule extends Shared{
+    def moduleDeps = Seq(bar.jvm())
+    def ivyDeps = super.ivyDeps() ++ Agg(ivy"com.lihaoyi::upickle::3.0.0")
+  }
+
+  object js extends Cross[JsModule](scalaVersions)
+  trait JsModule extends SharedJS {
+    def moduleDeps = Seq(bar.js())
+  }
+}
+
+// This example demonstrates an alternative way of defining your cross-platform
+// cross-version modules: rather than wrapping them all in a `foo`
+// cross-module to provide the different versions, we instead give each module
+// `bar.jvm`, `bar.js`, `qux.jvm`, `qux.js` its own `Cross` module. This
+// approach can be useful if the different cross modules need to support
+// different sets of Scala versions, as it allows you to specify the
+// `scalaVersions` passed to each individual cross module separately.
+
+/** Usage
+
+> ./mill show qux.js[3.2.2].sources
+[
+  ".../qux/src",
+  ".../qux/src-js",
+  ".../qux/src-3.2.2",
+  ".../qux/src-3.2.2-js",
+  ".../qux/src-3.2",
+  ".../qux/src-3.2-js",
+  ".../qux/src-3",
+  ".../qux/src-3-js"
+]
+
+> ./mill qux.jvm[2.13.8].run
+Bar.value: <p>world Specific code for Scala 2.x</p>
+Parsing JSON with ujson.read
+Qux.main: Set(<p>i</p>, <p>cow</p>, <p>me</p>)
+
+*/
\ No newline at end of file
diff --git a/example/web/7-cross-platform-version-2/foo/bar/src-2/BarVersionSpecific.scala b/example/web/7-cross-platform-version-2/foo/bar/src-2/BarVersionSpecific.scala
new file mode 100644
index 00000000000..5a352dfeec7
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/foo/bar/src-2/BarVersionSpecific.scala
@@ -0,0 +1,4 @@
+package bar
+object BarVersionSpecific {
+  def text(): String = "Specific code for Scala 2.x"
+}
diff --git a/example/web/7-cross-platform-version-2/foo/bar/src-3/BarVersionSpecific.scala b/example/web/7-cross-platform-version-2/foo/bar/src-3/BarVersionSpecific.scala
new file mode 100644
index 00000000000..4b7da7c369d
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/foo/bar/src-3/BarVersionSpecific.scala
@@ -0,0 +1,4 @@
+package bar
+object BarVersionSpecific {
+  def text(): String = "Specific code for Scala 3.x"
+}
diff --git a/example/web/7-cross-platform-version-2/foo/bar/src/Bar.scala b/example/web/7-cross-platform-version-2/foo/bar/src/Bar.scala
new file mode 100644
index 00000000000..e8e32282c79
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/foo/bar/src/Bar.scala
@@ -0,0 +1,5 @@
+package bar
+import scalatags.Text.all._
+object Bar {
+  val value = p("world", " ", BarVersionSpecific.text())
+}
diff --git a/example/web/7-cross-platform-version-2/foo/bar/test/src/BarTests.scala b/example/web/7-cross-platform-version-2/foo/bar/test/src/BarTests.scala
new file mode 100644
index 00000000000..8f7f1407f5e
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/foo/bar/test/src/BarTests.scala
@@ -0,0 +1,12 @@
+package bar
+import utest._
+object BarTests extends TestSuite {
+  def tests = Tests {
+    test("test") {
+      val result = Bar.value.toString
+      val matcher = "<p>world Specific code for Scala [23].x</p>".r
+      assert(matcher.matches(result))
+      result
+    }
+  }
+}
diff --git a/example/web/7-cross-platform-version-2/foo/qux/src-js/QuxPlatformSpecific.scala b/example/web/7-cross-platform-version-2/foo/qux/src-js/QuxPlatformSpecific.scala
new file mode 100644
index 00000000000..d9f57981da4
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/foo/qux/src-js/QuxPlatformSpecific.scala
@@ -0,0 +1,8 @@
+package qux
+import scala.scalajs.js
+object QuxPlatformSpecific {
+  def parseJsonGetKeys(s: String): Set[String] = {
+    println("Parsing JSON with js.JSON.parse")
+    js.JSON.parse(s).asInstanceOf[js.Dictionary[_]].keys.toSet
+  }
+}
diff --git a/example/web/7-cross-platform-version-2/foo/qux/src-jvm/QuxPlatformSpecific.scala b/example/web/7-cross-platform-version-2/foo/qux/src-jvm/QuxPlatformSpecific.scala
new file mode 100644
index 00000000000..8feff343246
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/foo/qux/src-jvm/QuxPlatformSpecific.scala
@@ -0,0 +1,7 @@
+package qux
+object QuxPlatformSpecific {
+  def parseJsonGetKeys(s: String): Set[String] = {
+    println("Parsing JSON with ujson.read")
+    ujson.read(s).obj.keys.toSet
+  }
+}
diff --git a/example/web/7-cross-platform-version-2/foo/qux/src/Qux.scala b/example/web/7-cross-platform-version-2/foo/qux/src/Qux.scala
new file mode 100644
index 00000000000..77bad3e03af
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/foo/qux/src/Qux.scala
@@ -0,0 +1,9 @@
+package qux
+import scalatags.Text.all._
+object Qux {
+  def main(args: Array[String]): Unit = {
+    println("Bar.value: " + bar.Bar.value)
+    val string = """{"i": "am", "cow": "hear", "me": "moo"}"""
+    println("Qux.main: " + QuxPlatformSpecific.parseJsonGetKeys(string).map(p(_)))
+  }
+}
diff --git a/example/web/7-cross-platform-version-2/foo/qux/test/src/QuxTests.scala b/example/web/7-cross-platform-version-2/foo/qux/test/src/QuxTests.scala
new file mode 100644
index 00000000000..bdcf6fa9203
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/foo/qux/test/src/QuxTests.scala
@@ -0,0 +1,12 @@
+package qux
+import utest._
+object QuxTests extends TestSuite {
+  def tests = Tests {
+    test("parseJsonGetKeys") {
+      val string = """{"i": "am", "cow": "hear", "me": "moo}"""
+      val keys = QuxPlatformSpecific.parseJsonGetKeys(string)
+      assert(keys == Set("i", "cow", "me"))
+      keys
+    }
+  }
+}
diff --git a/example/web/7-cross-platform-version-2/qux/src-js/QuxPlatformSpecific.scala b/example/web/7-cross-platform-version-2/qux/src-js/QuxPlatformSpecific.scala
new file mode 100644
index 00000000000..d9f57981da4
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/qux/src-js/QuxPlatformSpecific.scala
@@ -0,0 +1,8 @@
+package qux
+import scala.scalajs.js
+object QuxPlatformSpecific {
+  def parseJsonGetKeys(s: String): Set[String] = {
+    println("Parsing JSON with js.JSON.parse")
+    js.JSON.parse(s).asInstanceOf[js.Dictionary[_]].keys.toSet
+  }
+}
diff --git a/example/web/7-cross-platform-version-2/qux/src-jvm/QuxPlatformSpecific.scala b/example/web/7-cross-platform-version-2/qux/src-jvm/QuxPlatformSpecific.scala
new file mode 100644
index 00000000000..8feff343246
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/qux/src-jvm/QuxPlatformSpecific.scala
@@ -0,0 +1,7 @@
+package qux
+object QuxPlatformSpecific {
+  def parseJsonGetKeys(s: String): Set[String] = {
+    println("Parsing JSON with ujson.read")
+    ujson.read(s).obj.keys.toSet
+  }
+}
diff --git a/example/web/7-cross-platform-version-2/qux/src/Qux.scala b/example/web/7-cross-platform-version-2/qux/src/Qux.scala
new file mode 100644
index 00000000000..77bad3e03af
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/qux/src/Qux.scala
@@ -0,0 +1,9 @@
+package qux
+import scalatags.Text.all._
+object Qux {
+  def main(args: Array[String]): Unit = {
+    println("Bar.value: " + bar.Bar.value)
+    val string = """{"i": "am", "cow": "hear", "me": "moo"}"""
+    println("Qux.main: " + QuxPlatformSpecific.parseJsonGetKeys(string).map(p(_)))
+  }
+}
diff --git a/example/web/7-cross-platform-version-2/qux/test/src/QuxTests.scala b/example/web/7-cross-platform-version-2/qux/test/src/QuxTests.scala
new file mode 100644
index 00000000000..bdcf6fa9203
--- /dev/null
+++ b/example/web/7-cross-platform-version-2/qux/test/src/QuxTests.scala
@@ -0,0 +1,12 @@
+package qux
+import utest._
+object QuxTests extends TestSuite {
+  def tests = Tests {
+    test("parseJsonGetKeys") {
+      val string = """{"i": "am", "cow": "hear", "me": "moo}"""
+      val keys = QuxPlatformSpecific.parseJsonGetKeys(string)
+      assert(keys == Set("i", "cow", "me"))
+      keys
+    }
+  }
+}

From 50a41d7562c668b94af6e7f492445d5910dcae80 Mon Sep 17 00:00:00 2001
From: Your Name <haoyi.sg@gmail.com>
Date: Fri, 19 May 2023 20:41:15 -0700
Subject: [PATCH 03/10] .

---
 .../build.sc                                        |  1 +
 .../foo/bar/src-2/BarVersionSpecific.scala          |  0
 .../foo/bar/src-3/BarVersionSpecific.scala          |  0
 .../foo/bar/src/Bar.scala                           |  0
 .../foo/bar/test/src/BarTests.scala                 |  0
 .../foo/qux/src-js/QuxPlatformSpecific.scala        |  0
 .../foo/qux/src-jvm/QuxPlatformSpecific.scala       |  0
 .../foo/qux/src/Qux.scala                           |  0
 .../foo/qux/test/src/QuxTests.scala                 |  0
 .../bar/src-2/BarVersionSpecific.scala              |  0
 .../bar/src-3/BarVersionSpecific.scala              |  0
 .../bar/src/Bar.scala                               |  0
 .../bar/test/src/BarTests.scala                     |  0
 .../build.sc                                        | 13 ++++++++++++-
 .../foo/bar/src-2/BarVersionSpecific.scala          |  0
 .../foo/bar/src-3/BarVersionSpecific.scala          |  0
 .../foo/bar/src/Bar.scala                           |  0
 .../foo/bar/test/src/BarTests.scala                 |  0
 .../foo/qux/src-js/QuxPlatformSpecific.scala        |  0
 .../foo/qux/src-jvm/QuxPlatformSpecific.scala       |  0
 .../foo/qux/src/Qux.scala                           |  0
 .../foo/qux/test/src/QuxTests.scala                 |  0
 .../qux/src-js/QuxPlatformSpecific.scala            |  0
 .../qux/src-jvm/QuxPlatformSpecific.scala           |  0
 .../qux/src/Qux.scala                               |  0
 .../qux/test/src/QuxTests.scala                     |  0
 26 files changed, 13 insertions(+), 1 deletion(-)
 rename example/web/{6-cross-platform-publishing => 6-cross-version-platform-publishing}/build.sc (99%)
 rename example/web/{6-cross-platform-publishing => 6-cross-version-platform-publishing}/foo/bar/src-2/BarVersionSpecific.scala (100%)
 rename example/web/{6-cross-platform-publishing => 6-cross-version-platform-publishing}/foo/bar/src-3/BarVersionSpecific.scala (100%)
 rename example/web/{6-cross-platform-publishing => 6-cross-version-platform-publishing}/foo/bar/src/Bar.scala (100%)
 rename example/web/{6-cross-platform-publishing => 6-cross-version-platform-publishing}/foo/bar/test/src/BarTests.scala (100%)
 rename example/web/{6-cross-platform-publishing => 6-cross-version-platform-publishing}/foo/qux/src-js/QuxPlatformSpecific.scala (100%)
 rename example/web/{6-cross-platform-publishing => 6-cross-version-platform-publishing}/foo/qux/src-jvm/QuxPlatformSpecific.scala (100%)
 rename example/web/{6-cross-platform-publishing => 6-cross-version-platform-publishing}/foo/qux/src/Qux.scala (100%)
 rename example/web/{6-cross-platform-publishing => 6-cross-version-platform-publishing}/foo/qux/test/src/QuxTests.scala (100%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/bar/src-2/BarVersionSpecific.scala (100%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/bar/src-3/BarVersionSpecific.scala (100%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/bar/src/Bar.scala (100%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/bar/test/src/BarTests.scala (100%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/build.sc (80%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/foo/bar/src-2/BarVersionSpecific.scala (100%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/foo/bar/src-3/BarVersionSpecific.scala (100%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/foo/bar/src/Bar.scala (100%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/foo/bar/test/src/BarTests.scala (100%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/foo/qux/src-js/QuxPlatformSpecific.scala (100%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/foo/qux/src-jvm/QuxPlatformSpecific.scala (100%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/foo/qux/src/Qux.scala (100%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/foo/qux/test/src/QuxTests.scala (100%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/qux/src-js/QuxPlatformSpecific.scala (100%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/qux/src-jvm/QuxPlatformSpecific.scala (100%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/qux/src/Qux.scala (100%)
 rename example/web/{7-cross-platform-version-2 => 7-cross-platform-version-publishing}/qux/test/src/QuxTests.scala (100%)

diff --git a/example/web/6-cross-platform-publishing/build.sc b/example/web/6-cross-version-platform-publishing/build.sc
similarity index 99%
rename from example/web/6-cross-platform-publishing/build.sc
rename to example/web/6-cross-version-platform-publishing/build.sc
index f31b0d39629..22abe960048 100644
--- a/example/web/6-cross-platform-publishing/build.sc
+++ b/example/web/6-cross-version-platform-publishing/build.sc
@@ -92,4 +92,5 @@ Publishing Artifact(com.lihaoyi,foo-bar_sjs1_3,0.0.1) to ivy repo...
 Publishing Artifact(com.lihaoyi,foo-bar_3,0.0.1) to ivy repo...
 Publishing Artifact(com.lihaoyi,foo-qux_sjs1_3,0.0.1) to ivy repo...
 Publishing Artifact(com.lihaoyi,foo-qux_3,0.0.1) to ivy repo...
+
 */
diff --git a/example/web/6-cross-platform-publishing/foo/bar/src-2/BarVersionSpecific.scala b/example/web/6-cross-version-platform-publishing/foo/bar/src-2/BarVersionSpecific.scala
similarity index 100%
rename from example/web/6-cross-platform-publishing/foo/bar/src-2/BarVersionSpecific.scala
rename to example/web/6-cross-version-platform-publishing/foo/bar/src-2/BarVersionSpecific.scala
diff --git a/example/web/6-cross-platform-publishing/foo/bar/src-3/BarVersionSpecific.scala b/example/web/6-cross-version-platform-publishing/foo/bar/src-3/BarVersionSpecific.scala
similarity index 100%
rename from example/web/6-cross-platform-publishing/foo/bar/src-3/BarVersionSpecific.scala
rename to example/web/6-cross-version-platform-publishing/foo/bar/src-3/BarVersionSpecific.scala
diff --git a/example/web/6-cross-platform-publishing/foo/bar/src/Bar.scala b/example/web/6-cross-version-platform-publishing/foo/bar/src/Bar.scala
similarity index 100%
rename from example/web/6-cross-platform-publishing/foo/bar/src/Bar.scala
rename to example/web/6-cross-version-platform-publishing/foo/bar/src/Bar.scala
diff --git a/example/web/6-cross-platform-publishing/foo/bar/test/src/BarTests.scala b/example/web/6-cross-version-platform-publishing/foo/bar/test/src/BarTests.scala
similarity index 100%
rename from example/web/6-cross-platform-publishing/foo/bar/test/src/BarTests.scala
rename to example/web/6-cross-version-platform-publishing/foo/bar/test/src/BarTests.scala
diff --git a/example/web/6-cross-platform-publishing/foo/qux/src-js/QuxPlatformSpecific.scala b/example/web/6-cross-version-platform-publishing/foo/qux/src-js/QuxPlatformSpecific.scala
similarity index 100%
rename from example/web/6-cross-platform-publishing/foo/qux/src-js/QuxPlatformSpecific.scala
rename to example/web/6-cross-version-platform-publishing/foo/qux/src-js/QuxPlatformSpecific.scala
diff --git a/example/web/6-cross-platform-publishing/foo/qux/src-jvm/QuxPlatformSpecific.scala b/example/web/6-cross-version-platform-publishing/foo/qux/src-jvm/QuxPlatformSpecific.scala
similarity index 100%
rename from example/web/6-cross-platform-publishing/foo/qux/src-jvm/QuxPlatformSpecific.scala
rename to example/web/6-cross-version-platform-publishing/foo/qux/src-jvm/QuxPlatformSpecific.scala
diff --git a/example/web/6-cross-platform-publishing/foo/qux/src/Qux.scala b/example/web/6-cross-version-platform-publishing/foo/qux/src/Qux.scala
similarity index 100%
rename from example/web/6-cross-platform-publishing/foo/qux/src/Qux.scala
rename to example/web/6-cross-version-platform-publishing/foo/qux/src/Qux.scala
diff --git a/example/web/6-cross-platform-publishing/foo/qux/test/src/QuxTests.scala b/example/web/6-cross-version-platform-publishing/foo/qux/test/src/QuxTests.scala
similarity index 100%
rename from example/web/6-cross-platform-publishing/foo/qux/test/src/QuxTests.scala
rename to example/web/6-cross-version-platform-publishing/foo/qux/test/src/QuxTests.scala
diff --git a/example/web/7-cross-platform-version-2/bar/src-2/BarVersionSpecific.scala b/example/web/7-cross-platform-version-publishing/bar/src-2/BarVersionSpecific.scala
similarity index 100%
rename from example/web/7-cross-platform-version-2/bar/src-2/BarVersionSpecific.scala
rename to example/web/7-cross-platform-version-publishing/bar/src-2/BarVersionSpecific.scala
diff --git a/example/web/7-cross-platform-version-2/bar/src-3/BarVersionSpecific.scala b/example/web/7-cross-platform-version-publishing/bar/src-3/BarVersionSpecific.scala
similarity index 100%
rename from example/web/7-cross-platform-version-2/bar/src-3/BarVersionSpecific.scala
rename to example/web/7-cross-platform-version-publishing/bar/src-3/BarVersionSpecific.scala
diff --git a/example/web/7-cross-platform-version-2/bar/src/Bar.scala b/example/web/7-cross-platform-version-publishing/bar/src/Bar.scala
similarity index 100%
rename from example/web/7-cross-platform-version-2/bar/src/Bar.scala
rename to example/web/7-cross-platform-version-publishing/bar/src/Bar.scala
diff --git a/example/web/7-cross-platform-version-2/bar/test/src/BarTests.scala b/example/web/7-cross-platform-version-publishing/bar/test/src/BarTests.scala
similarity index 100%
rename from example/web/7-cross-platform-version-2/bar/test/src/BarTests.scala
rename to example/web/7-cross-platform-version-publishing/bar/test/src/BarTests.scala
diff --git a/example/web/7-cross-platform-version-2/build.sc b/example/web/7-cross-platform-version-publishing/build.sc
similarity index 80%
rename from example/web/7-cross-platform-version-2/build.sc
rename to example/web/7-cross-platform-version-publishing/build.sc
index 10e1c6704f1..ebc23254b50 100644
--- a/example/web/7-cross-platform-version-2/build.sc
+++ b/example/web/7-cross-platform-version-publishing/build.sc
@@ -74,4 +74,15 @@ Bar.value: <p>world Specific code for Scala 2.x</p>
 Parsing JSON with ujson.read
 Qux.main: Set(<p>i</p>, <p>cow</p>, <p>me</p>)
 
-*/
\ No newline at end of file
+> ./mill __.publishLocal
+...
+Publishing Artifact(com.lihaoyi,bar_sjs1_2.13,0.0.1) to ivy repo...
+Publishing Artifact(com.lihaoyi,bar_2.13,0.0.1) to ivy repo...
+Publishing Artifact(com.lihaoyi,qux_sjs1_2.13,0.0.1) to ivy repo...
+Publishing Artifact(com.lihaoyi,qux_2.13,0.0.1) to ivy repo...
+Publishing Artifact(com.lihaoyi,bar_sjs1_3,0.0.1) to ivy repo...
+Publishing Artifact(com.lihaoyi,bar_3,0.0.1) to ivy repo...
+Publishing Artifact(com.lihaoyi,qux_sjs1_3,0.0.1) to ivy repo...
+Publishing Artifact(com.lihaoyi,qux_3,0.0.1) to ivy repo...
+
+*/
diff --git a/example/web/7-cross-platform-version-2/foo/bar/src-2/BarVersionSpecific.scala b/example/web/7-cross-platform-version-publishing/foo/bar/src-2/BarVersionSpecific.scala
similarity index 100%
rename from example/web/7-cross-platform-version-2/foo/bar/src-2/BarVersionSpecific.scala
rename to example/web/7-cross-platform-version-publishing/foo/bar/src-2/BarVersionSpecific.scala
diff --git a/example/web/7-cross-platform-version-2/foo/bar/src-3/BarVersionSpecific.scala b/example/web/7-cross-platform-version-publishing/foo/bar/src-3/BarVersionSpecific.scala
similarity index 100%
rename from example/web/7-cross-platform-version-2/foo/bar/src-3/BarVersionSpecific.scala
rename to example/web/7-cross-platform-version-publishing/foo/bar/src-3/BarVersionSpecific.scala
diff --git a/example/web/7-cross-platform-version-2/foo/bar/src/Bar.scala b/example/web/7-cross-platform-version-publishing/foo/bar/src/Bar.scala
similarity index 100%
rename from example/web/7-cross-platform-version-2/foo/bar/src/Bar.scala
rename to example/web/7-cross-platform-version-publishing/foo/bar/src/Bar.scala
diff --git a/example/web/7-cross-platform-version-2/foo/bar/test/src/BarTests.scala b/example/web/7-cross-platform-version-publishing/foo/bar/test/src/BarTests.scala
similarity index 100%
rename from example/web/7-cross-platform-version-2/foo/bar/test/src/BarTests.scala
rename to example/web/7-cross-platform-version-publishing/foo/bar/test/src/BarTests.scala
diff --git a/example/web/7-cross-platform-version-2/foo/qux/src-js/QuxPlatformSpecific.scala b/example/web/7-cross-platform-version-publishing/foo/qux/src-js/QuxPlatformSpecific.scala
similarity index 100%
rename from example/web/7-cross-platform-version-2/foo/qux/src-js/QuxPlatformSpecific.scala
rename to example/web/7-cross-platform-version-publishing/foo/qux/src-js/QuxPlatformSpecific.scala
diff --git a/example/web/7-cross-platform-version-2/foo/qux/src-jvm/QuxPlatformSpecific.scala b/example/web/7-cross-platform-version-publishing/foo/qux/src-jvm/QuxPlatformSpecific.scala
similarity index 100%
rename from example/web/7-cross-platform-version-2/foo/qux/src-jvm/QuxPlatformSpecific.scala
rename to example/web/7-cross-platform-version-publishing/foo/qux/src-jvm/QuxPlatformSpecific.scala
diff --git a/example/web/7-cross-platform-version-2/foo/qux/src/Qux.scala b/example/web/7-cross-platform-version-publishing/foo/qux/src/Qux.scala
similarity index 100%
rename from example/web/7-cross-platform-version-2/foo/qux/src/Qux.scala
rename to example/web/7-cross-platform-version-publishing/foo/qux/src/Qux.scala
diff --git a/example/web/7-cross-platform-version-2/foo/qux/test/src/QuxTests.scala b/example/web/7-cross-platform-version-publishing/foo/qux/test/src/QuxTests.scala
similarity index 100%
rename from example/web/7-cross-platform-version-2/foo/qux/test/src/QuxTests.scala
rename to example/web/7-cross-platform-version-publishing/foo/qux/test/src/QuxTests.scala
diff --git a/example/web/7-cross-platform-version-2/qux/src-js/QuxPlatformSpecific.scala b/example/web/7-cross-platform-version-publishing/qux/src-js/QuxPlatformSpecific.scala
similarity index 100%
rename from example/web/7-cross-platform-version-2/qux/src-js/QuxPlatformSpecific.scala
rename to example/web/7-cross-platform-version-publishing/qux/src-js/QuxPlatformSpecific.scala
diff --git a/example/web/7-cross-platform-version-2/qux/src-jvm/QuxPlatformSpecific.scala b/example/web/7-cross-platform-version-publishing/qux/src-jvm/QuxPlatformSpecific.scala
similarity index 100%
rename from example/web/7-cross-platform-version-2/qux/src-jvm/QuxPlatformSpecific.scala
rename to example/web/7-cross-platform-version-publishing/qux/src-jvm/QuxPlatformSpecific.scala
diff --git a/example/web/7-cross-platform-version-2/qux/src/Qux.scala b/example/web/7-cross-platform-version-publishing/qux/src/Qux.scala
similarity index 100%
rename from example/web/7-cross-platform-version-2/qux/src/Qux.scala
rename to example/web/7-cross-platform-version-publishing/qux/src/Qux.scala
diff --git a/example/web/7-cross-platform-version-2/qux/test/src/QuxTests.scala b/example/web/7-cross-platform-version-publishing/qux/test/src/QuxTests.scala
similarity index 100%
rename from example/web/7-cross-platform-version-2/qux/test/src/QuxTests.scala
rename to example/web/7-cross-platform-version-publishing/qux/test/src/QuxTests.scala

From e46a2bd92691588055259d3cac0e98b25b35040f Mon Sep 17 00:00:00 2001
From: Your Name <haoyi.sg@gmail.com>
Date: Fri, 19 May 2023 20:42:18 -0700
Subject: [PATCH 04/10] .

---
 docs/modules/ROOT/pages/Web_Build_Examples.adoc | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/docs/modules/ROOT/pages/Web_Build_Examples.adoc b/docs/modules/ROOT/pages/Web_Build_Examples.adoc
index a9c588ead60..5f25c8cbb06 100644
--- a/docs/modules/ROOT/pages/Web_Build_Examples.adoc
+++ b/docs/modules/ROOT/pages/Web_Build_Examples.adoc
@@ -27,4 +27,8 @@ include::example/web/5-webapp-scalajs-shared.adoc[]
 
 == Publishing Cross-Platform Scala Modules
 
-include::example/web/6-cross-platform-publishing.adoc[]
\ No newline at end of file
+include::example/web/6-cross-version-platform-publishing.adoc[]
+
+== Publishing Cross-Platform Scala Modules Alternative
+
+include::example/web/7-cross-platform-version-publishing.adoc[]
\ No newline at end of file

From 6f7147b308dc85a29325abda7fa5d8828151b0ac Mon Sep 17 00:00:00 2001
From: Your Name <haoyi.sg@gmail.com>
Date: Fri, 19 May 2023 20:45:52 -0700
Subject: [PATCH 05/10] .

---
 .../web/7-cross-platform-version-publishing/build.sc | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/example/web/7-cross-platform-version-publishing/build.sc b/example/web/7-cross-platform-version-publishing/build.sc
index ebc23254b50..36846295fe7 100644
--- a/example/web/7-cross-platform-version-publishing/build.sc
+++ b/example/web/7-cross-platform-version-publishing/build.sc
@@ -69,6 +69,18 @@ object qux extends Module{
   ".../qux/src-3-js"
 ]
 
+> ./mill show qux.js[3.2.2].test.sources
+[
+  ".../qux/test/src",
+  ".../qux/test/src-js",
+  ".../qux/test/src-3.2.2",
+  ".../qux/test/src-3.2.2-js",
+  ".../qux/test/src-3.2",
+  ".../qux/test/src-3.2-js",
+  ".../qux/test/src-3",
+  ".../qux/test/src-3-js"
+]
+
 > ./mill qux.jvm[2.13.8].run
 Bar.value: <p>world Specific code for Scala 2.x</p>
 Parsing JSON with ujson.read

From 8505cdeda4560401358c532b0745785375ee2592 Mon Sep 17 00:00:00 2001
From: Your Name <haoyi.sg@gmail.com>
Date: Fri, 19 May 2023 21:29:53 -0700
Subject: [PATCH 06/10] .

---
 scalalib/src/mill/scalalib/JavaModule.scala | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/scalalib/src/mill/scalalib/JavaModule.scala b/scalalib/src/mill/scalalib/JavaModule.scala
index d1dd956b0a1..2b17513b3c9 100644
--- a/scalalib/src/mill/scalalib/JavaModule.scala
+++ b/scalalib/src/mill/scalalib/JavaModule.scala
@@ -41,8 +41,8 @@ trait JavaModule
     override def zincWorker: ZincWorkerModule = outer.zincWorker
     override def skipIdea: Boolean = outer.skipIdea
     override def runUseArgsFile: Target[Boolean] = T { outer.runUseArgsFile() }
-    override def sources = T.sources{
-      for(src <- outer.sources()) yield{
+    override def sources = T.sources {
+      for (src <- outer.sources()) yield {
         PathRef(this.millSourcePath / src.path.relativeTo(outer.millSourcePath))
       }
     }

From fec317ad3b3208be437bb53a3670b344f00223e3 Mon Sep 17 00:00:00 2001
From: Your Name <haoyi.sg@gmail.com>
Date: Fri, 19 May 2023 22:32:21 -0700
Subject: [PATCH 07/10] .

---
 scalajslib/src/mill/scalajslib/ScalaJSModule.scala | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/scalajslib/src/mill/scalajslib/ScalaJSModule.scala b/scalajslib/src/mill/scalajslib/ScalaJSModule.scala
index 7183dfce181..5d8401af161 100644
--- a/scalajslib/src/mill/scalajslib/ScalaJSModule.scala
+++ b/scalajslib/src/mill/scalajslib/ScalaJSModule.scala
@@ -188,10 +188,16 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer =>
   }
 
   override def mandatoryScalacOptions = T {
-    super.mandatoryScalacOptions() ++ {
-      if (ZincWorkerUtil.isScala3(scalaVersion())) Seq("-scalajs")
+    // Don't add flag twice, e.g. if a test suite inherits it both directly
+    // ScalaJSModule as well as from the enclosing non-test ScalaJSModule
+    val scalajsFlag =
+      if (
+        ZincWorkerUtil.isScala3(scalaVersion()) &&
+        !super.mandatoryScalacOptions().contains("-scalajs")
+      ) Seq("-scalajs")
       else Seq.empty
-    }
+
+    super.mandatoryScalacOptions() ++ scalajsFlag
   }
 
   override def scalacPluginIvyDeps = T {

From f4bc13047f338253685da143d03378975b643e6d Mon Sep 17 00:00:00 2001
From: Your Name <haoyi.sg@gmail.com>
Date: Sat, 20 May 2023 07:37:47 -0700
Subject: [PATCH 08/10] fix test suites in examples

---
 .../build.sc                                   | 18 +++++++++++++++---
 .../foo/qux/test/src/QuxTests.scala            |  2 +-
 .../build.sc                                   | 18 +++++++++++++++---
 .../foo/bar/src-2/BarVersionSpecific.scala     |  4 ----
 .../foo/bar/src-3/BarVersionSpecific.scala     |  4 ----
 .../foo/bar/src/Bar.scala                      |  5 -----
 .../foo/bar/test/src/BarTests.scala            | 12 ------------
 .../foo/qux/src-js/QuxPlatformSpecific.scala   |  8 --------
 .../foo/qux/src-jvm/QuxPlatformSpecific.scala  |  7 -------
 .../foo/qux/src/Qux.scala                      |  9 ---------
 .../foo/qux/test/src/QuxTests.scala            | 12 ------------
 .../qux/test/src/QuxTests.scala                |  2 +-
 12 files changed, 32 insertions(+), 69 deletions(-)
 delete mode 100644 example/web/7-cross-platform-version-publishing/foo/bar/src-2/BarVersionSpecific.scala
 delete mode 100644 example/web/7-cross-platform-version-publishing/foo/bar/src-3/BarVersionSpecific.scala
 delete mode 100644 example/web/7-cross-platform-version-publishing/foo/bar/src/Bar.scala
 delete mode 100644 example/web/7-cross-platform-version-publishing/foo/bar/test/src/BarTests.scala
 delete mode 100644 example/web/7-cross-platform-version-publishing/foo/qux/src-js/QuxPlatformSpecific.scala
 delete mode 100644 example/web/7-cross-platform-version-publishing/foo/qux/src-jvm/QuxPlatformSpecific.scala
 delete mode 100644 example/web/7-cross-platform-version-publishing/foo/qux/src/Qux.scala
 delete mode 100644 example/web/7-cross-platform-version-publishing/foo/qux/test/src/QuxTests.scala

diff --git a/example/web/6-cross-version-platform-publishing/build.sc b/example/web/6-cross-version-platform-publishing/build.sc
index 22abe960048..ee0e06824a8 100644
--- a/example/web/6-cross-version-platform-publishing/build.sc
+++ b/example/web/6-cross-version-platform-publishing/build.sc
@@ -16,7 +16,7 @@ trait FooModule extends Cross.Module[String] {
 
     def ivyDeps = Agg(ivy"com.lihaoyi::scalatags::0.12.0")
 
-    object test extends Tests {
+    trait FooTestModule extends Tests {
       def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.7.11")
       def testFramework = "utest.runner.Framework"
     }
@@ -27,18 +27,26 @@ trait FooModule extends Cross.Module[String] {
   }
 
   object bar extends Module {
-    object jvm extends Shared
-    object js extends SharedJS
+    object jvm extends Shared{
+      object test extends Tests with FooTestModule
+    }
+    object js extends SharedJS{
+      object test extends Tests with FooTestModule
+    }
   }
 
   object qux extends Module{
     object jvm extends Shared{
       def moduleDeps = Seq(bar.jvm)
       def ivyDeps = super.ivyDeps() ++ Agg(ivy"com.lihaoyi::upickle::3.0.0")
+
+      object test extends Tests with FooTestModule
     }
 
     object js extends SharedJS {
       def moduleDeps = Seq(bar.js)
+
+      object test extends Tests with FooTestModule
     }
   }
 }
@@ -83,6 +91,10 @@ Bar.value: <p>world Specific code for Scala 3.x</p>
 Parsing JSON with js.JSON.parse
 Qux.main: Set(<p>i</p>, <p>cow</p>, <p>me</p>)
 
+> ./mill foo[3.2.2].__.js.test
++ bar.BarTests.test ...  <p>world Specific code for Scala 3.x</p>
++ qux.QuxTests.parseJsonGetKeys ...  Set(i, cow, me)
+
 > ./mill __.publishLocal
 Publishing Artifact(com.lihaoyi,foo-bar_sjs1_2.13,0.0.1) to ivy repo...
 Publishing Artifact(com.lihaoyi,foo-bar_2.13,0.0.1) to ivy repo...
diff --git a/example/web/6-cross-version-platform-publishing/foo/qux/test/src/QuxTests.scala b/example/web/6-cross-version-platform-publishing/foo/qux/test/src/QuxTests.scala
index bdcf6fa9203..7de6c5ff150 100644
--- a/example/web/6-cross-version-platform-publishing/foo/qux/test/src/QuxTests.scala
+++ b/example/web/6-cross-version-platform-publishing/foo/qux/test/src/QuxTests.scala
@@ -3,7 +3,7 @@ import utest._
 object QuxTests extends TestSuite {
   def tests = Tests {
     test("parseJsonGetKeys") {
-      val string = """{"i": "am", "cow": "hear", "me": "moo}"""
+      val string = """{"i": "am", "cow": "hear", "me": "moo"}"""
       val keys = QuxPlatformSpecific.parseJsonGetKeys(string)
       assert(keys == Set("i", "cow", "me"))
       keys
diff --git a/example/web/7-cross-platform-version-publishing/build.sc b/example/web/7-cross-platform-version-publishing/build.sc
index 36846295fe7..00af5db1e44 100644
--- a/example/web/7-cross-platform-version-publishing/build.sc
+++ b/example/web/7-cross-platform-version-publishing/build.sc
@@ -14,7 +14,7 @@ trait Shared extends CrossScalaModule with PlatformScalaModule with PublishModul
 
   def ivyDeps = Agg(ivy"com.lihaoyi::scalatags::0.12.0")
 
-  object test extends Tests {
+  trait SharedTestModule extends Tests {
     def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.7.11")
     def testFramework = "utest.runner.Framework"
   }
@@ -28,10 +28,14 @@ val scalaVersions = Seq("2.13.8", "3.2.2")
 
 object bar extends Module {
   object jvm extends Cross[JvmModule](scalaVersions)
-  trait JvmModule extends Shared
+  trait JvmModule extends Shared{
+    object test extends Tests with SharedTestModule
+  }
 
   object js extends Cross[JsModule](scalaVersions)
-  trait JsModule extends SharedJS
+  trait JsModule extends SharedJS{
+    object test extends Tests with SharedTestModule
+  }
 }
 
 object qux extends Module{
@@ -39,11 +43,15 @@ object qux extends Module{
   trait JvmModule extends Shared{
     def moduleDeps = Seq(bar.jvm())
     def ivyDeps = super.ivyDeps() ++ Agg(ivy"com.lihaoyi::upickle::3.0.0")
+
+    object test extends Tests with SharedTestModule
   }
 
   object js extends Cross[JsModule](scalaVersions)
   trait JsModule extends SharedJS {
     def moduleDeps = Seq(bar.js())
+
+    object test extends Tests with SharedTestModule
   }
 }
 
@@ -86,6 +94,10 @@ Bar.value: <p>world Specific code for Scala 2.x</p>
 Parsing JSON with ujson.read
 Qux.main: Set(<p>i</p>, <p>cow</p>, <p>me</p>)
 
+> ./mill __.js[3.2.2].test
++ bar.BarTests.test ...  <p>world Specific code for Scala 3.x</p>
++ qux.QuxTests.parseJsonGetKeys ...  Set(i, cow, me)
+
 > ./mill __.publishLocal
 ...
 Publishing Artifact(com.lihaoyi,bar_sjs1_2.13,0.0.1) to ivy repo...
diff --git a/example/web/7-cross-platform-version-publishing/foo/bar/src-2/BarVersionSpecific.scala b/example/web/7-cross-platform-version-publishing/foo/bar/src-2/BarVersionSpecific.scala
deleted file mode 100644
index 5a352dfeec7..00000000000
--- a/example/web/7-cross-platform-version-publishing/foo/bar/src-2/BarVersionSpecific.scala
+++ /dev/null
@@ -1,4 +0,0 @@
-package bar
-object BarVersionSpecific {
-  def text(): String = "Specific code for Scala 2.x"
-}
diff --git a/example/web/7-cross-platform-version-publishing/foo/bar/src-3/BarVersionSpecific.scala b/example/web/7-cross-platform-version-publishing/foo/bar/src-3/BarVersionSpecific.scala
deleted file mode 100644
index 4b7da7c369d..00000000000
--- a/example/web/7-cross-platform-version-publishing/foo/bar/src-3/BarVersionSpecific.scala
+++ /dev/null
@@ -1,4 +0,0 @@
-package bar
-object BarVersionSpecific {
-  def text(): String = "Specific code for Scala 3.x"
-}
diff --git a/example/web/7-cross-platform-version-publishing/foo/bar/src/Bar.scala b/example/web/7-cross-platform-version-publishing/foo/bar/src/Bar.scala
deleted file mode 100644
index e8e32282c79..00000000000
--- a/example/web/7-cross-platform-version-publishing/foo/bar/src/Bar.scala
+++ /dev/null
@@ -1,5 +0,0 @@
-package bar
-import scalatags.Text.all._
-object Bar {
-  val value = p("world", " ", BarVersionSpecific.text())
-}
diff --git a/example/web/7-cross-platform-version-publishing/foo/bar/test/src/BarTests.scala b/example/web/7-cross-platform-version-publishing/foo/bar/test/src/BarTests.scala
deleted file mode 100644
index 8f7f1407f5e..00000000000
--- a/example/web/7-cross-platform-version-publishing/foo/bar/test/src/BarTests.scala
+++ /dev/null
@@ -1,12 +0,0 @@
-package bar
-import utest._
-object BarTests extends TestSuite {
-  def tests = Tests {
-    test("test") {
-      val result = Bar.value.toString
-      val matcher = "<p>world Specific code for Scala [23].x</p>".r
-      assert(matcher.matches(result))
-      result
-    }
-  }
-}
diff --git a/example/web/7-cross-platform-version-publishing/foo/qux/src-js/QuxPlatformSpecific.scala b/example/web/7-cross-platform-version-publishing/foo/qux/src-js/QuxPlatformSpecific.scala
deleted file mode 100644
index d9f57981da4..00000000000
--- a/example/web/7-cross-platform-version-publishing/foo/qux/src-js/QuxPlatformSpecific.scala
+++ /dev/null
@@ -1,8 +0,0 @@
-package qux
-import scala.scalajs.js
-object QuxPlatformSpecific {
-  def parseJsonGetKeys(s: String): Set[String] = {
-    println("Parsing JSON with js.JSON.parse")
-    js.JSON.parse(s).asInstanceOf[js.Dictionary[_]].keys.toSet
-  }
-}
diff --git a/example/web/7-cross-platform-version-publishing/foo/qux/src-jvm/QuxPlatformSpecific.scala b/example/web/7-cross-platform-version-publishing/foo/qux/src-jvm/QuxPlatformSpecific.scala
deleted file mode 100644
index 8feff343246..00000000000
--- a/example/web/7-cross-platform-version-publishing/foo/qux/src-jvm/QuxPlatformSpecific.scala
+++ /dev/null
@@ -1,7 +0,0 @@
-package qux
-object QuxPlatformSpecific {
-  def parseJsonGetKeys(s: String): Set[String] = {
-    println("Parsing JSON with ujson.read")
-    ujson.read(s).obj.keys.toSet
-  }
-}
diff --git a/example/web/7-cross-platform-version-publishing/foo/qux/src/Qux.scala b/example/web/7-cross-platform-version-publishing/foo/qux/src/Qux.scala
deleted file mode 100644
index 77bad3e03af..00000000000
--- a/example/web/7-cross-platform-version-publishing/foo/qux/src/Qux.scala
+++ /dev/null
@@ -1,9 +0,0 @@
-package qux
-import scalatags.Text.all._
-object Qux {
-  def main(args: Array[String]): Unit = {
-    println("Bar.value: " + bar.Bar.value)
-    val string = """{"i": "am", "cow": "hear", "me": "moo"}"""
-    println("Qux.main: " + QuxPlatformSpecific.parseJsonGetKeys(string).map(p(_)))
-  }
-}
diff --git a/example/web/7-cross-platform-version-publishing/foo/qux/test/src/QuxTests.scala b/example/web/7-cross-platform-version-publishing/foo/qux/test/src/QuxTests.scala
deleted file mode 100644
index bdcf6fa9203..00000000000
--- a/example/web/7-cross-platform-version-publishing/foo/qux/test/src/QuxTests.scala
+++ /dev/null
@@ -1,12 +0,0 @@
-package qux
-import utest._
-object QuxTests extends TestSuite {
-  def tests = Tests {
-    test("parseJsonGetKeys") {
-      val string = """{"i": "am", "cow": "hear", "me": "moo}"""
-      val keys = QuxPlatformSpecific.parseJsonGetKeys(string)
-      assert(keys == Set("i", "cow", "me"))
-      keys
-    }
-  }
-}
diff --git a/example/web/7-cross-platform-version-publishing/qux/test/src/QuxTests.scala b/example/web/7-cross-platform-version-publishing/qux/test/src/QuxTests.scala
index bdcf6fa9203..7de6c5ff150 100644
--- a/example/web/7-cross-platform-version-publishing/qux/test/src/QuxTests.scala
+++ b/example/web/7-cross-platform-version-publishing/qux/test/src/QuxTests.scala
@@ -3,7 +3,7 @@ import utest._
 object QuxTests extends TestSuite {
   def tests = Tests {
     test("parseJsonGetKeys") {
-      val string = """{"i": "am", "cow": "hear", "me": "moo}"""
+      val string = """{"i": "am", "cow": "hear", "me": "moo"}"""
       val keys = QuxPlatformSpecific.parseJsonGetKeys(string)
       assert(keys == Set("i", "cow", "me"))
       keys

From 3c6fef492a0dc2a7a10be9054031a26ed9487c76 Mon Sep 17 00:00:00 2001
From: Your Name <haoyi.sg@gmail.com>
Date: Sat, 20 May 2023 09:13:40 -0700
Subject: [PATCH 09/10] .

---
 .../test/src/WatchSourceInputTests.scala                  | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/integration/feature/watch-source-input/test/src/WatchSourceInputTests.scala b/integration/feature/watch-source-input/test/src/WatchSourceInputTests.scala
index 42a6e35c2ce..347774f881e 100644
--- a/integration/feature/watch-source-input/test/src/WatchSourceInputTests.scala
+++ b/integration/feature/watch-source-input/test/src/WatchSourceInputTests.scala
@@ -104,8 +104,8 @@ object WatchSourceInputTests extends IntegrationTestSuite {
 
     test("sources") {
 
-      test("noshow") - retry(3) { testWatchSource(false) }
-      test("show") - retry(3) { testWatchSource(true) }
+      test("noshow") - retry(3) { initWorkspace(); testWatchSource(false) }
+      test("show") - retry(3) { initWorkspace(); testWatchSource(true) }
     }
 
     def testWatchInput(show: Boolean) = {
@@ -146,8 +146,8 @@ object WatchSourceInputTests extends IntegrationTestSuite {
 
     test("input") {
 
-      test("noshow") - retry(3) { testWatchInput(false) }
-      test("show") - retry(3) { testWatchInput(true) }
+      test("noshow") - retry(3) { initWorkspace(); testWatchInput(false) }
+      test("show") - retry(3) { initWorkspace(); testWatchInput(true) }
     }
   }
 }

From 276bbd3e32888ad9ee7ccfba4caff28c60462274 Mon Sep 17 00:00:00 2001
From: Your Name <haoyi.sg@gmail.com>
Date: Sat, 20 May 2023 09:14:51 -0700
Subject: [PATCH 10/10] .

---
 .../watch-source-input/test/src/WatchSourceInputTests.scala     | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/integration/feature/watch-source-input/test/src/WatchSourceInputTests.scala b/integration/feature/watch-source-input/test/src/WatchSourceInputTests.scala
index 347774f881e..4344d7af305 100644
--- a/integration/feature/watch-source-input/test/src/WatchSourceInputTests.scala
+++ b/integration/feature/watch-source-input/test/src/WatchSourceInputTests.scala
@@ -104,6 +104,7 @@ object WatchSourceInputTests extends IntegrationTestSuite {
 
     test("sources") {
 
+      // Make sure we clean up the workspace between retries
       test("noshow") - retry(3) { initWorkspace(); testWatchSource(false) }
       test("show") - retry(3) { initWorkspace(); testWatchSource(true) }
     }
@@ -146,6 +147,7 @@ object WatchSourceInputTests extends IntegrationTestSuite {
 
     test("input") {
 
+      // Make sure we clean up the workspace between retries
       test("noshow") - retry(3) { initWorkspace(); testWatchInput(false) }
       test("show") - retry(3) { initWorkspace(); testWatchInput(true) }
     }