Skip to content

Added MILL_TEST_FREE_PORT #4931

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

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5ed3b21
Added a Port cli option
Apr 13, 2025
9e6a6b2
Merge remote-tracking branch 'upstream/main' into ports
Apr 13, 2025
1e8fcfc
Added the EnvVar, and renamed the one in MillMain to be named properly
Apr 13, 2025
c653c4f
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 13, 2025
7c16274
added a small test
Apr 13, 2025
fcb906e
Removed needless println
Apr 15, 2025
24dc402
Clarified some things, added a const for target
Apr 15, 2025
52551bc
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 15, 2025
6cc4367
Accidentally repeated a line
Apr 15, 2025
a038193
Merge branch 'main' into ports
albassort Apr 16, 2025
71fc484
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 16, 2025
27a876d
Added PortManager external module and changed a lot of tests
Apr 26, 2025
bbb99ef
deleted duplicate
Apr 27, 2025
cc893ab
typo
Apr 27, 2025
e2f0250
fixed a broken test
Apr 27, 2025
77499c5
temporary revert
Apr 27, 2025
fc62b82
fixed a test
Apr 27, 2025
4b682ed
Removed dead code
Apr 29, 2025
21873fd
Merge remote-tracking branch 'upstream/main' into ports
Apr 29, 2025
8c312bd
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 29, 2025
4c74e9f
resetting stray changes (1?)
Apr 29, 2025
3d47359
fixed some broken tests
Apr 29, 2025
014d164
Merge branch 'main' into ports
albassort Apr 29, 2025
6511b51
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 29, 2025
6d7d58b
forgort to set port
Apr 29, 2025
646ede7
Merge branch 'ports' of https://github.com/albassort/mill into ports
Apr 29, 2025
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
4 changes: 4 additions & 0 deletions core/constants/src/mill/constants/EnvVars.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,8 @@ public class EnvVars {
* the local disk and not from Maven Central
*/
public static final String MILL_BUILD_LIBRARIES = "MILL_BUILD_LIBRARIES";
/**
* Used to store free ports for Mill's tests to use.
*/
public static final String MILL_TEST_FREE_PORT = "MILL_TEST_FREE_PORT";
Copy link
Member

Choose a reason for hiding this comment

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

Is this a single port or multiple. The comment is a bit vague. If it is multi, that the variable should be MILL_TEST_FREE_PORTS and we should document here, which separator is used.

}
1 change: 1 addition & 0 deletions integration/feature/ports-system-prop/resources/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package mill.integration

import mill.testkit.UtestIntegrationTestSuite

import utest._

object ShutdownExitCodeTests extends UtestIntegrationTestSuite {
// Ensure that `shutdown` succeeds even if the prior command failed
val tests: Tests = Tests {
test("test") - integrationTest { tester =>
val result1 = tester.eval(("resolve", "_"))
assert(result1.isSuccess == true)
val result2 = tester.eval("shutdown")
assert(result2.isSuccess == true)

val result3 = tester.eval("doesnt-exit")
assert(result3.isSuccess == false)
val result4 = tester.eval("shutdown")
assert(result4.isSuccess == true)
}
}
}
13 changes: 12 additions & 1 deletion runner/src/mill/runner/MillCliConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,18 @@ case class MillCliConfig(
|There are currently no guarantees that modules don't attempt to fetch remote sources."""
.stripMargin
)
offline: Flag = Flag()
offline: Flag = Flag(),
@arg(
doc =
"""Provides specified ports for Mill to propagate into System Properties.
|The ports must be between 1024 to 65535.
|The ports must be free at the time of Mill launching, and should remain free.
|If no ports are specified, free ports will be determined on startup
"""
.stripMargin,
name = "ports"
)
ports: Option[String]
)

import mainargs.ParserForClass
Expand Down
79 changes: 78 additions & 1 deletion runner/src/mill/runner/MillMain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import java.lang.reflect.InvocationTargetException
import scala.util.control.NonFatal
import scala.util.Using
import mill.server.Server
import java.lang.NumberFormatException
import java.net.{ServerSocket, BindException}

@internal
object MillMain {
Expand Down Expand Up @@ -180,6 +182,7 @@ object MillMain {
(false, RunnerState.empty)

case Result.Success(config) =>

val noColorViaEnv = env.get("NO_COLOR").exists(_.nonEmpty)
val colored = config.color.getOrElse(mainInteractive && !noColorViaEnv)
val colors =
Expand Down Expand Up @@ -209,8 +212,19 @@ object MillMain {
(false, stateCache)

} else {
val ports = validatePorts(config, streams)
if (ports == None) {
return (true, stateCache)
}
val portStr = ports.get.mkString(",")

val portJvmProperties = (
"MILL_TEST_FREE_PORT",
portStr
)

val userSpecifiedProperties =
userSpecifiedProperties0 ++ config.extraSystemProperties
userSpecifiedProperties0 ++ config.extraSystemProperties + portJvmProperties

val threadCount = Some(maybeThreadCount.toOption.get)

Expand Down Expand Up @@ -348,6 +362,69 @@ object MillMain {
}
}

def validatePorts(config: MillCliConfig, streams: SystemStreams): Option[Set[Int]] = {
val result =
if (config.ports.isDefined) {
var ports = Set.empty[Int]
try {
ports = config.ports.get.split(",").map(_.toInt).toSet
} catch {
case e: NumberFormatException =>
streams.err.println(
"Failed to parse --ports, please provide comma sepperated values. E.g 4090,5000,2910"
)
None
}
// Port validation
ports.foreach { case port =>
// 1024 is the min port on windows
if (port < 1024 || port > 65535) {
streams.err.println(
s"Failed to parse $port. Ports provided must be within 1024 to 65535."
)
None
}
try {
new ServerSocket(port).close()
} catch {
case e: BindException => {
streams.err.println(
s"Failed to bind to $port! Please only provide free ports."
)
None
}
}
}
Some(ports)
} else {
var i = 0
var ports = Set.empty[Int]
for (_ <- 1 to 100) {
if (i == 6) {
Copy link
Member

Choose a reason for hiding this comment

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

Magic number 6! A parameter or a named constant might be better.

return Some(ports)
}
val socket = new ServerSocket(0)
try {
val port = socket.getLocalPort
if (!ports.contains(port)) {
ports = ports + port
i += 1
}

} finally {
socket.close()
}
}

streams.err.println(
"Failed to generate enough ports for servers to use. Either there isn't 6 free ports on your computer, or there is a firewall issue. "
)

None
}
result
}

def runBspSession(
streams0: SystemStreams,
logStream: Option[PrintStream],
Expand Down
Loading