Skip to content

Commit 670be35

Browse files
authored
Cross build fs2.io for Scala.js (#2453)
* Extract JVM-specific io implementation * Add Node.js types dependency * Move resources to JVM directory * Fix build * Implement FileHandle for JS * Fix scala 3 compile errors * Formatting * Disable fail-fast CI * Regenerate workflow * Trigger CI * Implement Socket for Node.js * Formatting * Fix 2.12 compile * Renaming * More socket implementing * Renaming * Implement UDP for Node * Debugging, tcp now passing tests :) * Formatting * Improve ScalablyTyped build * Uncomment forgotten line * UdpSuite passing on Node.js * Formatting * Bump ST plugin. Scala 3 support! * Revert "Disable fail-fast CI" This reverts commit baa0f52. * Regenerate workflow * Fix scala 3 compile * Refactoring * TLS on Node.js * Delete duplicated resource * Fix scala 3 compile * Update yarn.lock * Add JS methods to Chunk[Byte] * Make ST facade internal, new public APIs * Formatting * Polish SecureContext API * Polish TLSParameters API * Fix Scala 2.12 compile * Fixup TLS suite * Refactor UnixSockets * Fix scala 3 compile * Annotate/simplify MiMa exclusions
1 parent b119f82 commit 670be35

File tree

101 files changed

+7104
-565
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+7104
-565
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767
run: sbt --client '++${{ matrix.scala }}; microsite/mdoc'
6868

6969
- name: Compress target directories
70-
run: tar cf targets.tar target core/js/target core/jvm/target io/target reactive-streams/target benchmark/target project/target
70+
run: tar cf targets.tar target core/js/target core/jvm/target io/js/target reactive-streams/target io/jvm/target benchmark/target project/target
7171

7272
- name: Upload target directories
7373
uses: actions/upload-artifact@v2

build.sbt

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ addCommandAlias(
66
"fmtCheck",
77
"; compile:scalafmtCheck; test:scalafmtCheck; it:scalafmtCheck; scalafmtSbtCheck"
88
)
9-
addCommandAlias("testJVM", ";coreJVM/test;io/test;reactiveStreams/test;benchmark/test")
10-
addCommandAlias("testJS", "coreJS/test")
9+
addCommandAlias("testJVM", ";rootJVM/test")
10+
addCommandAlias("testJS", "rootJS/test")
1111

1212
Global / onChangedBuildSource := ReloadOnSourceChanges
1313

@@ -104,13 +104,36 @@ ThisBuild / mimaBinaryIssueFilters ++= Seq(
104104
ProblemFilters.exclude[ReversedMissingMethodProblem](
105105
"fs2.io.net.tls.TLSContext.dtlsServerBuilder"
106106
),
107-
ProblemFilters.exclude[Problem]("fs2.io.net.tls.TLSEngine*")
107+
ProblemFilters.exclude[Problem]("fs2.io.net.tls.TLSEngine*"),
108+
// start #2453 cross-build fs2.io for scala.js
109+
// private implementation classes
110+
ProblemFilters.exclude[MissingClassProblem]("fs2.io.net.Socket$IntCallbackHandler"),
111+
ProblemFilters.exclude[MissingClassProblem]("fs2.io.net.Socket$BufferedReads"),
112+
ProblemFilters.exclude[MissingClassProblem]("fs2.io.net.SocketGroup$AsyncSocketGroup"),
113+
ProblemFilters.exclude[MissingClassProblem]("fs2.io.net.Socket$AsyncSocket"),
114+
ProblemFilters.exclude[MissingClassProblem](
115+
"fs2.io.net.DatagramSocketGroup$AsyncDatagramSocketGroup"
116+
),
117+
ProblemFilters.exclude[MissingClassProblem]("fs2.io.net.unixsocket.UnixSockets$AsyncSocket"),
118+
ProblemFilters.exclude[MissingClassProblem]("fs2.io.net.unixsocket.UnixSockets$AsyncUnixSockets"),
119+
ProblemFilters.exclude[MissingClassProblem]("fs2.io.net.tls.TLSContext$Builder$AsyncBuilder"),
120+
// sealed traits
121+
ProblemFilters.exclude[NewMixinForwarderProblem]("fs2.io.net.Network.*"),
122+
ProblemFilters.exclude[NewMixinForwarderProblem]("fs2.io.net.tls.TLSContext.*"),
123+
ProblemFilters.exclude[InheritedNewAbstractMethodProblem]("fs2.io.net.tls.TLSContext.*")
124+
// end #2453
108125
)
109126

110127
lazy val root = project
111128
.in(file("."))
112129
.enablePlugins(NoPublishPlugin, SonatypeCiReleasePlugin)
113-
.aggregate(coreJVM, coreJS, io, reactiveStreams, benchmark)
130+
.aggregate(coreJVM, coreJS, io.jvm, io.js, reactiveStreams, benchmark)
131+
132+
lazy val rootJVM = project
133+
.in(file("."))
134+
.enablePlugins(NoPublishPlugin)
135+
.aggregate(coreJVM, io.jvm, reactiveStreams, benchmark)
136+
lazy val rootJS = project.in(file(".")).enablePlugins(NoPublishPlugin).aggregate(coreJS, io.js)
114137

115138
lazy val IntegrationTest = config("it").extend(Test)
116139

@@ -177,16 +200,13 @@ lazy val coreJS = core.js
177200
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule))
178201
)
179202

180-
lazy val io = project
203+
lazy val io = crossProject(JVMPlatform, JSPlatform)
181204
.in(file("io"))
182205
.enablePlugins(SbtOsgi)
206+
.jsConfigure(_.enablePlugins(ScalablyTypedConverterGenSourcePlugin))
183207
.settings(
184208
name := "fs2-io",
185-
libraryDependencies ++= Seq(
186-
"com.comcast" %% "ip4s-core" % "3.0.3",
187-
"com.github.jnr" % "jnr-unixsocket" % "0.38.8" % Optional
188-
),
189-
Test / fork := true,
209+
libraryDependencies += "com.comcast" %%% "ip4s-core" % "3.0.3",
190210
OsgiKeys.exportPackage := Seq("fs2.io.*"),
191211
OsgiKeys.privatePackage := Seq(),
192212
OsgiKeys.importPackage := {
@@ -200,7 +220,20 @@ lazy val io = project
200220
OsgiKeys.additionalHeaders := Map("-removeheaders" -> "Include-Resource,Private-Package"),
201221
osgiSettings
202222
)
203-
.dependsOn(coreJVM % "compile->compile;test->test")
223+
.jvmSettings(
224+
Test / fork := true,
225+
libraryDependencies += "com.github.jnr" % "jnr-unixsocket" % "0.38.8" % Optional
226+
)
227+
.jsSettings(
228+
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)),
229+
Compile / npmDependencies += "@types/node" -> "16.0.0",
230+
Test / npmDependencies += "jks-js" -> "1.0.1",
231+
useYarn := true,
232+
stOutputPackage := "fs2.internal.jsdeps",
233+
stStdlib := List("es2020"),
234+
stIgnore += "jks-js"
235+
)
236+
.dependsOn(core % "compile->compile;test->test")
204237

205238
lazy val reactiveStreams = project
206239
.in(file("reactive-streams"))
@@ -236,7 +269,7 @@ lazy val benchmark = project
236269
Test / run / javaOptions := (Test / run / javaOptions).value
237270
.filterNot(o => o.startsWith("-Xmx") || o.startsWith("-Xms")) ++ Seq("-Xms256m", "-Xmx256m")
238271
)
239-
.dependsOn(io)
272+
.dependsOn(io.jvm)
240273

241274
lazy val microsite = project
242275
.in(file("mdoc"))
@@ -252,7 +285,7 @@ lazy val microsite = project
252285
githubWorkflowArtifactUpload := false,
253286
fatalWarningsInCI := false
254287
)
255-
.dependsOn(coreJVM, io, reactiveStreams)
288+
.dependsOn(coreJVM, io.jvm, reactiveStreams)
256289
.enablePlugins(MdocPlugin, NoPublishPlugin)
257290

258291
ThisBuild / githubWorkflowBuildPostamble ++= List(
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2013 Functional Streams for Scala
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
5+
* this software and associated documentation files (the "Software"), to deal in
6+
* the Software without restriction, including without limitation the rights to
7+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8+
* the Software, and to permit persons to whom the Software is furnished to do so,
9+
* subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
package fs2
23+
24+
import scala.scalajs.js.typedarray.ArrayBuffer
25+
import scala.scalajs.js.typedarray.TypedArrayBuffer
26+
import scala.scalajs.js.typedarray.Uint8Array
27+
import scala.scalajs.js.typedarray.TypedArrayBufferOps._
28+
29+
trait ChunkRuntimePlatform[+O] { self: Chunk[O] =>
30+
31+
def toJSArrayBuffer[B >: O](implicit ev: B =:= Byte): ArrayBuffer = {
32+
val bb = toByteBuffer[B]
33+
if (bb.hasArrayBuffer())
34+
bb.arrayBuffer()
35+
else {
36+
val ab = new ArrayBuffer(bb.remaining())
37+
TypedArrayBuffer.wrap(ab).put(bb)
38+
ab
39+
}
40+
}
41+
42+
def toUint8Array[B >: O](implicit ev: B =:= Byte): Uint8Array = {
43+
val ab = toJSArrayBuffer[B]
44+
new Uint8Array(ab, 0, ab.byteLength)
45+
}
46+
47+
}
48+
49+
trait ChunkCompanionRuntimePlatform { self: Chunk.type =>
50+
51+
def jsArrayBuffer(buffer: ArrayBuffer): Chunk[Byte] =
52+
byteBuffer(TypedArrayBuffer.wrap(buffer))
53+
54+
def uint8Array(array: Uint8Array): Chunk[Byte] =
55+
jsArrayBuffer(array.buffer)
56+
57+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (c) 2013 Functional Streams for Scala
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
5+
* this software and associated documentation files (the "Software"), to deal in
6+
* the Software without restriction, including without limitation the rights to
7+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8+
* the Software, and to permit persons to whom the Software is furnished to do so,
9+
* subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
package fs2
23+
24+
import org.scalacheck.Arbitrary
25+
import org.scalacheck.Gen
26+
import org.scalacheck.Prop.forAll
27+
28+
class ChunkRuntimePlatformSuite extends Fs2Suite {
29+
override def scalaCheckTestParameters =
30+
super.scalaCheckTestParameters
31+
.withMinSuccessfulTests(if (isJVM) 100 else 25)
32+
.withWorkers(1)
33+
34+
// Override to remove from implicit scope
35+
override val byteChunkArbitrary = Arbitrary(???)
36+
37+
def testByteChunk(
38+
genChunk: Gen[Chunk[Byte]],
39+
name: String
40+
): Unit =
41+
group(s"$name") {
42+
implicit val implicitChunkArb: Arbitrary[Chunk[Byte]] = Arbitrary(genChunk)
43+
property("JSArrayBuffer conversion is idempotent") {
44+
forAll { (c: Chunk[Byte]) =>
45+
assertEquals(Chunk.jsArrayBuffer(c.toJSArrayBuffer), c)
46+
}
47+
}
48+
property("Uint8Array conversion is idempotent") {
49+
forAll { (c: Chunk[Byte]) =>
50+
assertEquals(Chunk.uint8Array(c.toUint8Array), c)
51+
}
52+
}
53+
}
54+
55+
testByteChunk(byteBufferChunkGenerator, "ByteBuffer")
56+
testByteChunk(byteVectorChunkGenerator, "ByteVector")
57+
58+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) 2013 Functional Streams for Scala
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
5+
* this software and associated documentation files (the "Software"), to deal in
6+
* the Software without restriction, including without limitation the rights to
7+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8+
* the Software, and to permit persons to whom the Software is furnished to do so,
9+
* subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
package fs2
23+
24+
trait ChunkRuntimePlatform[+O]
25+
26+
trait ChunkCompanionRuntimePlatform

core/shared/src/main/scala/fs2/Chunk.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ import cats.syntax.all._
5656
* access to the underlying backing array, along with an offset and length, referring to a slice
5757
* of that array.
5858
*/
59-
abstract class Chunk[+O] extends Serializable with ChunkPlatform[O] { self =>
59+
abstract class Chunk[+O] extends Serializable with ChunkPlatform[O] with ChunkRuntimePlatform[O] {
60+
self =>
6061

6162
/** Returns the number of elements in this chunk. */
6263
def size: Int
@@ -543,7 +544,10 @@ abstract class Chunk[+O] extends Serializable with ChunkPlatform[O] { self =>
543544
iterator.mkString("Chunk(", ", ", ")")
544545
}
545546

546-
object Chunk extends CollectorK[Chunk] with ChunkCompanionPlatform {
547+
object Chunk
548+
extends CollectorK[Chunk]
549+
with ChunkCompanionPlatform
550+
with ChunkCompanionRuntimePlatform {
547551

548552
private val empty_ : Chunk[Nothing] = new EmptyChunk
549553
private final class EmptyChunk extends Chunk[Nothing] {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright (c) 2013 Functional Streams for Scala
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
5+
* this software and associated documentation files (the "Software"), to deal in
6+
* the Software without restriction, including without limitation the rights to
7+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8+
* the Software, and to permit persons to whom the Software is furnished to do so,
9+
* subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
package fs2
23+
package io
24+
package file
25+
26+
import fs2.internal.jsdeps.node.fsPromisesMod
27+
import cats.effect.kernel.Async
28+
import cats.syntax.all._
29+
import scala.scalajs.js.typedarray.ArrayBuffer
30+
import scala.scalajs.js.typedarray.TypedArrayBuffer
31+
import scala.scalajs.js.typedarray.TypedArrayBufferOps._
32+
33+
private[file] trait FileHandlePlatform[F[_]]
34+
35+
private[file] trait FileHandleCompanionPlatform {
36+
private[file] def make[F[_]](
37+
fd: fsPromisesMod.FileHandle
38+
)(implicit F: Async[F]): FileHandle[F] =
39+
new FileHandle[F] {
40+
41+
override def force(metaData: Boolean): F[Unit] =
42+
F.fromPromise(F.delay(fsPromisesMod.fdatasync(fd)))
43+
44+
override def read(numBytes: Int, offset: Long): F[Option[Chunk[Byte]]] =
45+
F.fromPromise(F.delay(fd.read(new ArrayBuffer(numBytes), offset.toDouble, numBytes))).map {
46+
res =>
47+
if (res.bytesRead < 0) None
48+
else if (res.bytesRead == 0) Some(Chunk.empty)
49+
else
50+
Some(Chunk.byteBuffer(TypedArrayBuffer.wrap(res.buffer).limit(res.bytesRead.toInt)))
51+
}
52+
53+
override def size: F[Long] =
54+
F.fromPromise(F.delay(fd.stat())).map(_.size.toLong)
55+
56+
override def truncate(size: Long): F[Unit] =
57+
F.fromPromise(F.delay(fd.truncate(size.toDouble)))
58+
59+
override def write(bytes: Chunk[Byte], offset: Long): F[Int] =
60+
F.fromPromise(F.delay(fd.write(bytes.toByteBuffer.arrayBuffer(), offset.toDouble)))
61+
.map(_.bytesWritten.toInt)
62+
}
63+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2013 Functional Streams for Scala
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
5+
* this software and associated documentation files (the "Software"), to deal in
6+
* the Software without restriction, including without limitation the rights to
7+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8+
* the Software, and to permit persons to whom the Software is furnished to do so,
9+
* subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
package fs2
23+
package io.file
24+
25+
import fs2.internal.jsdeps.node.fsMod.PathLike
26+
import fs2.io.internal.ByteChunkOps._
27+
28+
sealed abstract class Path {
29+
private[file] def toPathLike: PathLike
30+
}
31+
32+
object Path {
33+
34+
def apply(path: String): Path = StringPath(path)
35+
def apply(path: Chunk[Byte]): Path = BufferPath(path)
36+
37+
private final case class StringPath(path: String) extends Path {
38+
override private[file] def toPathLike: PathLike = path.asInstanceOf[PathLike]
39+
}
40+
41+
private final case class BufferPath(path: Chunk[Byte]) extends Path {
42+
override private[file] def toPathLike: PathLike = path.toBuffer.asInstanceOf[PathLike]
43+
}
44+
45+
}

0 commit comments

Comments
 (0)