Skip to content

universal version of scriptPath not restricted to '.sc' scripts #3799

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

Draft
wants to merge 22 commits into
base: main
Choose a base branch
from

Conversation

philwalk
Copy link
Contributor

@philwalk philwalk commented Jul 22, 2025

Provides property scala.sources for determining the source files(s) for non-script scala-cli source types.
This provides a universal scriptPath function that's not restricted to '.sc' files. Without it, a working '.sc' file can't be easily converted to a '.scala' program if it accesses the scriptPath wrapper field.

Example usage: file printSourcePath.scala:

#!/usr/bin/env -S scala-cli shebang
object Main {
  def main(args: Array[String]): Unit = {
    printf("source path: [%s]\n", sys.props("scala.sources"))
  }
}
source path: [scriptSourcePath.scala]

Currently, scripts are able to access scriptPath (the path to the script), but .scala files have no equivalent, except in limited cases. For example, the following code works in SHELL environments, but not in CMD.EXE or powershell.

val scriptPath = Option(System.getenv("_")).getOrElse("")

Other approaches based on scala3 macros or stack-walking either provide less than the full path, or only work in some environments.

This eliminates a limitation scala has suffered relative to virtually all other scripting platforms:

  • the ability of published libraries to provide usage messages that include the source path
  • resolves a missing piece for migrating code from pre-3.0 scala.

Doesn't replace the scriptPath method in the script wrapper class, but fills in most if not all of the gaps in availability of source file path.

@philwalk
Copy link
Contributor Author

This implements #3800

@philwalk philwalk changed the title Provide a universal version of scriptPath that's available independent of script filename extension. universal version of scriptPath not restricted to '.sc' scripts Jul 22, 2025
@philwalk
Copy link
Contributor Author

philwalk commented Jul 23, 2025

Perhaps it would be better to use the System property script.path for backwards compatibility with the original scala3 bash wrapper script.

An update to the documentation is also needed.

A slightly different approach would be to set environment variable "_" if not already defined. There would be less need to document it since SHELL environments already define it correctly, and would mostly extend the coverage to Windows CMD and powershell and possibly IDE contexts.

@philwalk
Copy link
Contributor Author

Here's a suprise I discovered w.r.t. the nightly build.
My hunch is that it's a side-effect of this which treats a '.scala' source as a script. The interesting thing is how well it works, and it almost obviates the need for this PR.

After running the following script, named "biz.scala":

#!/usr/bin/env -S scala-cli.exe shebang -S 3.nightly
object Biz {
  def main(args: Array[String]): Unit = {
    printf("[%s]\n", scriptPath)
  }
}

I expected to see this:

# scala-cli.exe biz.scala
Compiling project (Scala 3.7.1, JVM (21))
[error] .\biz.scala:5:22
[error] Not found: scriptPath
[error]     printf("[%s]\n", scriptPath)
[error]                      ^^^^^^^^^^
Error compiling project (Scala 3.7.1, JVM (21))
Compilation failed

But instead, I got this:

./biz.scala
Compiling project (Scala 3.7.3-RC1-bin-20250701-542ca19-NIGHTLY, JVM (21))
Compiled project (Scala 3.7.3-RC1-bin-20250701-542ca19-NIGHTLY, JVM (21))
[./biz.scala]

Then I looked at the wrapper, which has an odd double-extension: .scala-build/scala-cli-july17_62d441ef6c-c8768589e0/src_generated/main/biz.scala.scala
Sure enough, it wraps the script as though it were a '.sc' script:

final class biz$_ {
def args = biz_sc.args$
def scriptPath = """./biz.scala"""
/*<script>*/
...

@philwalk
Copy link
Contributor Author

I reverted a side-effect of my previous PR that causes .scala files to be treated as scripts. I intended to push it in its' own branch, but accidentally added it here.

@Gedochao
Copy link
Contributor

 ==> X scala.cli.integration.RunTestsDefault.print command  2.628s java.io.IOException: Cannot run program "_=/home/runner/work/scala-cli/scala-cli/out/integration/tmpDirBase.dest/working-dir/run-1138417905/test-587/simple.sc" (in directory "/home/runner/work/scala-cli/scala-cli/out/integration/tmpDirBase.dest/working-dir/run-1138417905/test-587"): error=2, No such file or directory

still need to fix this test failure.
is it still a WIP? wondering, since it's not a draft PR 🤔

@Gedochao
Copy link
Contributor

Another thought - if you're adding this feature to .scala files, I'm thinking we should add it to .java & .md, as well, to be consistent.

@Gedochao
Copy link
Contributor

And - please add tests for the possible input configurations (including odd ones, like snippets and such).

Copy link
Contributor

@Gedochao Gedochao left a comment

Choose a reason for hiding this comment

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

Having the list in a property is perhaps less elegant than the old scriptPath field, since it also takes away the ability to differentiate the paths from the code in individual sources... but I'm not sure how useful that is.

And that being said, I do not think we want to generate an extra field (or anything, really) for non-script inputs, so perhaps that's the best we could do, indeed.

@Gedochao Gedochao linked an issue Jul 24, 2025 that may be closed by this pull request
@philwalk
Copy link
Contributor Author

philwalk commented Jul 24, 2025

still need to fix this test failure.

 ==> X scala.cli.integration.RunTestsDefault.print command  2.628s java.io.IOException: Cannot run program "_=/home/runner/work/scala-cli/scala-cli/out/integration/tmpDirBase.dest/working-dir/run-1138417905/test-587/simple.sc" (in directory "/home/runner/work/scala-cli/scala-cli/out/integration/tmpDirBase.dest/working-dir/run-1138417905/test-587"): error=2, No such file or directory

I'm still trying to figure out how to compose a command line to run an individual test. Is it documented somewhere? the JVM test runs for hours on my system ...

Here's what I tried:

# ./mill -i integration.test.testOnly 'scala.cli.integration.RunTests213'
[132/132] integration.test.testOnly
[132] scala.cli.integration.RunTests213:
[132] ==> X scala.cli.integration.RunTests213.initializationError  0.002s java.lang.ExceptionInInitializerError: null
[132]     at scala.cli.integration.RunScalaJsTestDefinitions.$init$(RunScalaJsTestDefinitions.scala:242)
[132]     at scala.cli.integration.RunTestDefinitions.<init>(RunTestDefinitions.scala:14)
[132]     at scala.cli.integration.RunTests213.<init>(RunTests213.scala:5)
[132]     at jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
[132]     at jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
[132]     at jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
[132]     at java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
[132]     at java.lang.reflect.ReflectAccess.newInstance(ReflectAccess.java:128)
[132]     at jdk.internal.reflect.ReflectionFactory.newInstance(ReflectionFactory.java:347)
[132]     at java.lang.Class.newInstance(Class.java:645)
[132]     at munit.MUnitRunner$$anonfun$$lessinit$greater$1.apply(MUnitRunner.scala:32)
[132]     at munit.MUnitRunner$$anonfun$$lessinit$greater$1.apply(MUnitRunner.scala:32)
[132]     at munit.MUnitRunner.<init>(MUnitRunner.scala:34)
[132]     at munit.MUnitRunner.<init>(MUnitRunner.scala:32)
[132]     at jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
[132]     at jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
[132]     at jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
[132]     at java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
[132]     at java.lang.reflect.Constructor.newInstance(Constructor.java:480)
[132]     at munit.internal.junitinterface.JUnitComputer.getRunner(JUnitComputer.java:75)
[132]     at munit.internal.junitinterface.JUnitComputer$1.runnerForClass(JUnitComputer.java:65)
[132]     ...
[132] Caused by: java.lang.NullPointerException: Cannot invoke "String.startsWith(String)" because the return value of "scala.cli.integration.TestUtil$.cliKind()" is null
[132]     at scala.cli.integration.TestUtil$.<clinit>(TestUtil.scala:20)
[132]     ... 42 more
[132/132, 1 failed] ============================== integration.test.testOnly scala.cli.integration.RunTests213 ============================== 4s
1 tasks failed
integration.test.testOnly 1 tests failed:
  scala.cli.integration.RunTests213.initializationError scala.cli.integration.RunTests213.initializationError

philwalk@d5 MSYS ~/workspace/scala-cli-july17

It seems to want cliKind() as jvm, but perhaps there's a better way to invoke it?

@philwalk
Copy link
Contributor Author

philwalk commented Jul 24, 2025

not a draft PR

The most recent changes need to be in a separate PR, I'll do that today.

@philwalk philwalk marked this pull request as draft July 24, 2025 14:38
@Gedochao
Copy link
Contributor

I'm still trying to figure out how to compose a command line to run an individual test. Is it documented somewhere? the JVM test runs for hours on my system ...

Oh, I see.
We have it documented right here: https://github.com/VirtusLab/scala-cli/blob/main/DEV.md#run-integration-tests-with-the-jvm-launcher

The test I mentioned earlier, you can run it like this:

./mill -i integration.test.jvm 'scala.cli.integration.RunTestsDefault.print*command'

@philwalk
Copy link
Contributor Author

Another thought - if you're adding this feature to .scala files, I'm thinking we should add it to .java & .md, as well, to be consistent.

The current implementation should work for anything jvm-based. Not sure how to extend it to '.js' or '.native', suggestions are welcome.

@philwalk
Copy link
Contributor Author

And - please add tests for the possible input configurations (including odd ones, like snippets and such).

Looking into it ...

@Gedochao
Copy link
Contributor

Another thought - if you're adding this feature to .scala files, I'm thinking we should add it to .java & .md, as well, to be consistent.

The current implementation should work for anything jvm-based. Not sure how to extend it to '.js' or '.native', suggestions are welcome.

jvm-based is enough.

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.

Make a version of scriptPath available to '.scala' source files
2 participants