-
Notifications
You must be signed in to change notification settings - Fork 278
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
Allow to compile with Scala Native #4325
Conversation
build.sbt
Outdated
// .jsSettings( | ||
// libraryDependencies ++= List( | ||
// scalatest.value % Test // must be here for coreJS/test to run anything | ||
// ) | ||
// ) | ||
.nativeSettings(libraryDependencies += "com.lihaoyi" %%% "fastparse" % "3.1.0") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably the weirdest part. scalameta-trees seems to use fastparse in its implementation, however I think it does not point to it in the jar metadata? (I cannot find any mention of fastparse there on scaladex either). Either way, without this we get errors about missing symbols when linking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The annoying part here is keeping the version set to the same one scalameta uses
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's shaded... why is it used by scalafmt?
https://github.com/scalameta/scalameta/blob/main/build.sbt#L783
https://github.com/scalameta/scalameta/blob/main/project/ShadedDependency.scala
also, the version is 3.1.1, i think.
/* Before text matching (?=re), after text matching (?<=re) | ||
* and more are incompatible in Scala Native, so custom functions | ||
* have to be used. | ||
* Scala Native uses an implementation of: | ||
* https://github.com/google/re2/wiki/Syntax | ||
*/ | ||
|
||
object RegexCompat { | ||
|
||
/* Replaces '\\h', which is incompatible in Scala Native. | ||
* Does not check correctness of the input regex string. | ||
*/ | ||
private def fixHorizontalSpaceInRegex(reg: String) = { | ||
|
||
@inline | ||
val replacingInsideClass = | ||
"\t \u00A0\u1680\u180E\u2000-\u200A\u202F\u205F\u3000" | ||
|
||
@inline | ||
val replacingOutsideClass = s"[$replacingInsideClass]" | ||
|
||
val sb = new StringBuilder() | ||
var isInClass = false | ||
var isEscaped = false | ||
|
||
for (char <- reg) char match { | ||
case '\\' if !isEscaped => isEscaped = true | ||
case 'h' if isEscaped => | ||
sb.append(if (isInClass) replacingInsideClass else replacingOutsideClass) | ||
isEscaped = false | ||
case '[' if !isEscaped => | ||
sb.append('[') | ||
isInClass = true | ||
case ']' if !isEscaped => | ||
sb.append(']') | ||
isInClass = false | ||
case other => | ||
if (isEscaped) { | ||
isEscaped = false | ||
sb.append('\\') | ||
} | ||
sb.append(other) | ||
} | ||
sb.toString() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is mostly a reply to a related comment from a previous PR. This method was written mostly because I found \\h
replacements specifically very annoying, unintuitive and ugly. But the regexes here have many more differences unfortunately, so just supplying this method to JVM style regexes would not work unfortunately.
1f9775e
to
b2864f4
Compare
build.sbt
Outdated
// .jsSettings( | ||
// libraryDependencies ++= List( | ||
// scalatest.value % Test // must be here for coreJS/test to run anything | ||
// ) | ||
// ) | ||
.nativeSettings(libraryDependencies += "com.lihaoyi" %%% "fastparse" % "3.1.0") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's shaded... why is it used by scalafmt?
https://github.com/scalameta/scalameta/blob/main/build.sbt#L783
https://github.com/scalameta/scalameta/blob/main/project/ShadedDependency.scala
also, the version is 3.1.1, i think.
build.sbt
Outdated
@@ -284,3 +298,7 @@ lazy val buildInfoSettings: Seq[Def.Setting[_]] = Seq( | |||
buildInfoPackage := "org.scalafmt", | |||
buildInfoObject := "Versions", | |||
) | |||
|
|||
lazy val scalaNativeNativeConfig = nativeConfig ~= { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why "native" twice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a nativeConfig setting for Scala Native... but I guess singular one still makes more sense, I'll change it to that
project/plugins.sbt
Outdated
@@ -15,3 +15,5 @@ addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") | |||
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2") | |||
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.4") | |||
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.4") | |||
addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@should we put this a few lines above, next to scalajs
variant? and, perhaps, add a variable for "1.3.2".
protected val isNative: Boolean = true | ||
|
||
protected def returnDynamicRunner(): Either[String, ScalafmtRunner] = { | ||
assert(false, "Code path should be unreachable.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't it redundant, given ???
below?
@@ -125,7 +100,7 @@ object Cli { | |||
case Right(`stableVersion`) => | |||
options.common.debug.println(s"Using core runner [$stableVersion]") | |||
Right(ScalafmtCoreRunner) | |||
case Right(v) if isNativeImage => | |||
case Right(v) if isNativeImage || isNative => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not move isNative
directly into isNativeImage
? line 64 above:
private val isNativeImage: Boolean = isNative || ...
scalafmt-core/native/src/main/scala/org/scalafmt/internal/RegexCompat.scala
Show resolved
Hide resolved
scalafmt-interfaces/native/src/main/java/org/scalafmt/interfaces/Scalafmt.scala
Show resolved
Hide resolved
// Scala Native does not include ParallelConverters | ||
object ParConverters { | ||
implicit class XIterable[T](val col: Iterable[T]) extends AnyVal { | ||
def compatPar = col |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm... this will make native version of scalafmt substantially slower than jvm (since it's no longer in parallel), which might generally defeat the purpose....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So back in the previous iteration 3 years ago SN did not have multithreading yet, but even then the single threaded scala native version was usually faster (I imagine this would change at scale). However this comment made me retest performance and I also realized that parallel collections were built into stdlib in scala 2.12, and indeed, they work on Scala Native now. So now, after that change, that would make scala 2.12 on Scala Native the best. Example test:
vl-d-1450:main jchyb$ time java --class-path scalafmt.jar org.scalafmt.cli.Cli
Reformatting...
100,0% [##########] 700 source files formatted
real 0m2,787s
user 0m17,050s
sys 0m0,961s
vl-d-1450:main jchyb$ time ./scalafmt-cli2
Reformatting...
100.0% [##########] 700 source files formatted
real 0m1,024s
user 0m2,016s
sys 0m0,365s
vl-d-1450:main jchyb$ time ./scalafmt-cli3
Reformatting...
100.0% [##########] 700 source files formatted
real 0m2,056s
user 0m1,611s
sys 0m0,207s
So that makes even the Scala Native version without parallelization better (again, I imagine this would change with a bigger project). Since the 2.12 built-in version works, I can't imagine porting scala-parallel-collections being difficult, so once that is done, the CollectionCompat implementations could be replaced and merged (I added a related comment).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very interesting, thank you for running this experiment. i have added a CommunitySuiteb test which is going to do a lot and is a bit slow, any way you could suggest a method to run it in scala native instead, as part of the GitHub build?
Try(new URL(filename)) match { | ||
if (PlatformCompat.isNative) readFile(getFile(filename)) | ||
else Try(new URL(filename)) match { | ||
case Success(url) => readFile(url) | ||
case _ => readFile(getFile(filename)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how's this:
val urlOpt = if (PlatformCompat.isNative) None else Try(new URL(filename)).toOption
urlOpt.fold(readFile(getFile(filename))(readFile)
@@ -19,12 +19,12 @@ class ScalafmtProps extends FunSuite with FormatAssertions { | |||
def getBugs( | |||
config: ScalafmtConfig = ScalafmtConfig.default, | |||
count: Int = Int.MaxValue, | |||
): mutable.Seq[(CorpusFile, Observation[Bug])] = { | |||
): mutable.Seq[(scala.meta.testkit.CorpusFile, Observation[Bug])] = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this package qualification necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, sorry, leftover I have missed, removing now
26b5729
to
247e582
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm probably reviewing too early since it's marked a draft.
System.getProperty("scalafmt.native-image", "false") | ||
private val isNative: Boolean = | ||
("true" == System.getProperty("scalafmt.native-image", "false")) || | ||
isScalaNative |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: isScalaNative
seems cheaper to compute, could put it first 🙂
if (parsed.noStdErr || !usesOut) parsed.common.out | ||
else parsed.common.err, | ||
) | ||
val writerMaybe = if (usesOut) getConsoleWriter() else None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please change to consWriter
or consWriterOpt
, or, if you really prefer, maybeCons[ole]Writer
. other than a few standard geeky suffixes like Opt a variable or class name should be a correct English noun phrase (and methods a verb phrase).
val start = matcher.start() | ||
val end = matcher.end() | ||
|
||
sb.append(baseText.substring(currPosition, start)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you had used a java StringBuilder, you could have used the append method which takes offset and length so doesn't need to create a copy of the substring.
scalafmt-interfaces/native/src/main/java/org/scalafmt/interfaces/Scalafmt.scala
Show resolved
Hide resolved
private[scalafmt] object CompatCollections { | ||
val JavaConverters = scala.collection.JavaConverters | ||
object ParConverters { | ||
implicit class XIterable[T](val col: Iterable[T]) extends AnyVal { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think in scalameta code there's a convention of what to call these implicit extension classes. i forgot but you can easily find existing examples. I thought it was a bit longer than just X
, with all due respect to Shmelon Shmusk.
247e582
to
1e10509
Compare
@jchyb windows native git test fails, looks like argument |
9123873
to
ae6514e
Compare
@jchyb what does this mean:
(https://github.com/scalameta/scalafmt/actions/runs/11036880214/job/30656386467?pr=4325) |
This relates to how Scala Native implements java.util.ServiceLoader service providers, where it requires them to be preconfigured (in the META-INF files, or nativeLink settings). As I understand it, during linking it checks for existence of |
ae6514e
to
d294347
Compare
|newlines.topLevelBodyIfMinStatements = [before,after] | ||
|fileOverride { | ||
| "glob:${PlatformCompat | ||
.fixPathOnNativeWindows("**/src/test/scala/**")}" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can users still specify paths using forward slash? the config file is shared between platforms, and they can't have one for windows and another for unix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out the problem I was trying to fix here was found elsewhere. I had to remove 2 uses of .asFilename compatibility tool (one used on fileOverride, one for include/excludePaths), as it messed with scala native's glob implementation. That asFilename method replaced unix file separator with Windows one, but it seems to work even on JVM just as well without it
@@ -859,14 +868,16 @@ class CliTest extends AbstractCliTest with CliTestBehavior { | |||
|/.scalafmt.conf | |||
|version = $stableVersion | |||
|maxColumn = 2 | |||
|project { includePaths = ["glob:**/bar.scala"] } | |||
|project { includePaths = ["glob:${PlatformCompat |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same concern about using slashes.
@@ -295,8 +298,14 @@ trait CliTestBehavior { | |||
"--config-str", | |||
s"""{version="$version",style=IntelliJ}""", | |||
) | |||
intercept[IOException] { | |||
Cli.exceptionThrowingMainWithOptions(args, baseCliOptions) | |||
if(PlatformCompat.isNativeOnWindows()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
formatting issue (needs a space). you should run ./scalafmt
in your local checkout directory before amending the commit.
@@ -76,8 +76,8 @@ object InputMethod { | |||
file.writeFile(text)(options.encoding) | |||
|
|||
override protected def list(options: CliOptions): Unit = { | |||
val cwd = options.cwd.toUri | |||
options.common.out.println(cwd.relativize(file.toUri)) | |||
val cwd = options.cwd.jfile.toPath |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jfile.toPath
is path.toFile.toPath
. why not just use path
?
b87a491
to
9b9699a
Compare
scalafmt-core/shared/src/main/scala/org/scalafmt/config/ProjectFiles.scala
Show resolved
Hide resolved
9b9699a
to
c624520
Compare
c624520
to
1b488f1
Compare
Adds Regexes for scala native, splits up uses of certain Collections, System.console, and metaconfig. Also moves some tests to a JVM only directory.
1b488f1
to
28fb625
Compare
@kitbellew Thank you so much for all of the reviews! I'll try to look into running communitytest stuff on native next week |
@jchyb and thank you both for your submission and for the patience. |
Unless we find a specific issue in the future that we wouldn't discover otherwise I think is good keeping as is. Great work @jchyb ! |
Apologies for a big PR once again.
Vast majority of the additions here consist of the rewritten interfaces to Scala, and it's difficult to test them without the rest of the scala native implementation, so instead of a different PR I decided to put them into a different commit. My only other idea was to split the TermUtils, CliUtils, etc. version dependent implementations to another PR, but I think in those cases it's helpful to see what I was trying to accomplish by having both native and jvm implementations side by side (and they are all very small).
Part of the Scala Native cross compilation effort (#4279)