Skip to content

Commit b1300b1

Browse files
authored
Merge pull request #94 from http4s/pr/native
Cross-build for Scala Native
2 parents 049da83 + ed97178 commit b1300b1

File tree

15 files changed

+390
-14
lines changed

15 files changed

+390
-14
lines changed

.github/workflows/ci.yml

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
os: [ubuntu-latest]
3232
scala: [3.1.3, 2.12.16, 2.13.8]
3333
java: [temurin@8, temurin@11, temurin@17]
34-
project: [rootJS, rootJVM]
34+
project: [rootJS, rootJVM, rootNative]
3535
jsenv: [NodeJS, Chrome, Firefox]
3636
exclude:
3737
- scala: 3.1.3
@@ -46,6 +46,10 @@ jobs:
4646
java: temurin@11
4747
- project: rootJS
4848
java: temurin@17
49+
- project: rootNative
50+
java: temurin@11
51+
- project: rootNative
52+
java: temurin@17
4953
- scala: 3.1.3
5054
jsenv: Chrome
5155
- scala: 3.1.3
@@ -56,8 +60,12 @@ jobs:
5660
jsenv: Firefox
5761
- project: rootJVM
5862
jsenv: Chrome
63+
- project: rootNative
64+
jsenv: Chrome
5965
- project: rootJVM
6066
jsenv: Firefox
67+
- project: rootNative
68+
jsenv: Firefox
6169
runs-on: ${{ matrix.os }}
6270
steps:
6371
- name: Checkout current branch (full)
@@ -142,6 +150,10 @@ jobs:
142150
if: matrix.project == 'rootJS'
143151
run: 'sbt ''project ${{ matrix.project }}'' ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' Test/scalaJSLinkerResult'
144152

153+
- name: nativeLink
154+
if: matrix.project == 'rootNative'
155+
run: 'sbt ''project ${{ matrix.project }}'' ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' Test/nativeLink'
156+
145157
- name: Test
146158
run: 'sbt ''project ${{ matrix.project }}'' ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' test'
147159

@@ -163,11 +175,11 @@ jobs:
163175

164176
- name: Make target directories
165177
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
166-
run: mkdir -p crypto/jvm/target target .js/target .jvm/target .native/target test-runtime/.jvm/target crypto/js/target test-runtime/.js/target project/target
178+
run: mkdir -p crypto/jvm/target target .js/target crypto/native/target .jvm/target .native/target test-runtime/.jvm/target crypto/js/target test-runtime/.js/target test-runtime/.native/target project/target
167179

168180
- name: Compress target directories
169181
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
170-
run: tar cf targets.tar crypto/jvm/target target .js/target .jvm/target .native/target test-runtime/.jvm/target crypto/js/target test-runtime/.js/target project/target
182+
run: tar cf targets.tar crypto/jvm/target target .js/target crypto/native/target .jvm/target .native/target test-runtime/.jvm/target crypto/js/target test-runtime/.js/target test-runtime/.native/target project/target
171183

172184
- name: Upload target directories
173185
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
@@ -272,6 +284,16 @@ jobs:
272284
tar xf targets.tar
273285
rm targets.tar
274286
287+
- name: Download target directories (3.1.3, NodeJS, rootNative)
288+
uses: actions/download-artifact@v2
289+
with:
290+
name: target-${{ matrix.os }}-${{ matrix.java }}-3.1.3-NodeJS-rootNative
291+
292+
- name: Inflate target directories (3.1.3, NodeJS, rootNative)
293+
run: |
294+
tar xf targets.tar
295+
rm targets.tar
296+
275297
- name: Download target directories (2.12.16, NodeJS, rootJS)
276298
uses: actions/download-artifact@v2
277299
with:
@@ -292,6 +314,16 @@ jobs:
292314
tar xf targets.tar
293315
rm targets.tar
294316
317+
- name: Download target directories (2.12.16, NodeJS, rootNative)
318+
uses: actions/download-artifact@v2
319+
with:
320+
name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.16-NodeJS-rootNative
321+
322+
- name: Inflate target directories (2.12.16, NodeJS, rootNative)
323+
run: |
324+
tar xf targets.tar
325+
rm targets.tar
326+
295327
- name: Download target directories (2.13.8, NodeJS, rootJS)
296328
uses: actions/download-artifact@v2
297329
with:
@@ -312,6 +344,16 @@ jobs:
312344
tar xf targets.tar
313345
rm targets.tar
314346
347+
- name: Download target directories (2.13.8, NodeJS, rootNative)
348+
uses: actions/download-artifact@v2
349+
with:
350+
name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.8-NodeJS-rootNative
351+
352+
- name: Inflate target directories (2.13.8, NodeJS, rootNative)
353+
run: |
354+
tar xf targets.tar
355+
rm targets.tar
356+
315357
- name: Import signing key
316358
if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE == ''
317359
run: echo $PGP_SECRET | base64 -di | gpg --import

build.sbt

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ ThisBuild / githubWorkflowBuildMatrixExclusions ++= {
5454
ThisBuild / githubWorkflowBuildMatrixExclusions ++= {
5555
for {
5656
jsenv <- jsenvs.tail
57-
} yield MatrixExclude(Map("project" -> "rootJVM", "jsenv" -> jsenv))
57+
project <- List("rootJVM", "rootNative")
58+
} yield MatrixExclude(Map("project" -> project, "jsenv" -> jsenv))
5859
}
5960

6061
lazy val useJSEnv =
@@ -80,13 +81,13 @@ ThisBuild / Test / jsEnv := {
8081
val catsVersion = "2.8.0"
8182
val catsEffectVersion = "3.3.14"
8283
val scodecBitsVersion = "1.1.34"
83-
val munitVersion = "0.7.29"
84-
val munitCEVersion = "1.0.7"
85-
val disciplineMUnitVersion = "1.0.9"
84+
val munitVersion = "1.0.0-M6"
85+
val munitCEVersion = "2.0.0-M3"
86+
val disciplineMUnitVersion = "2.0.0-M3"
8687

8788
lazy val root = tlCrossRootProject.aggregate(crypto, testRuntime)
8889

89-
lazy val crypto = crossProject(JSPlatform, JVMPlatform)
90+
lazy val crypto = crossProject(JSPlatform, JVMPlatform, NativePlatform)
9091
.in(file("crypto"))
9192
.settings(
9293
name := "http4s-crypto",
@@ -98,12 +99,16 @@ lazy val crypto = crossProject(JSPlatform, JVMPlatform)
9899
"org.typelevel" %%% "cats-laws" % catsVersion % Test,
99100
"org.typelevel" %%% "cats-effect" % catsEffectVersion % Test,
100101
"org.typelevel" %%% "discipline-munit" % disciplineMUnitVersion % Test,
101-
"org.typelevel" %%% "munit-cats-effect-3" % munitCEVersion % Test
102+
"org.typelevel" %%% "munit-cats-effect" % munitCEVersion % Test
102103
)
103104
)
105+
.nativeSettings(
106+
tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "0.2.4").toMap,
107+
unusedCompileDependenciesTest := {}
108+
)
104109
.dependsOn(testRuntime % Test)
105110

106-
lazy val testRuntime = crossProject(JSPlatform, JVMPlatform)
111+
lazy val testRuntime = crossProject(JSPlatform, JVMPlatform, NativePlatform)
107112
.crossType(CrossType.Pure)
108113
.in(file("test-runtime"))
109114
.enablePlugins(BuildInfoPlugin, NoPublishPlugin)
@@ -120,3 +125,9 @@ lazy val testRuntime = crossProject(JSPlatform, JVMPlatform)
120125
BuildInfoKey("runtime" -> useJSEnv.value.toString)
121126
)
122127
)
128+
.nativeSettings(
129+
buildInfoKeys := Seq(
130+
BuildInfoKey("runtime" -> "Native")
131+
),
132+
unusedCompileDependenciesTest := {}
133+
)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2021 http4s.org
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.http4s.crypto
18+
19+
import cats.effect.kernel.Sync
20+
21+
private[crypto] trait CryptoCompanionPlatform {
22+
implicit def forSync[F[_]: Sync]: Crypto[F] =
23+
new UnsealedCrypto[F] {
24+
override def hash: Hash[F] = Hash[F]
25+
override def hmac: Hmac[F] = Hmac[F]
26+
override def hmacKeyGen: HmacKeyGen[F] = HmacKeyGen[F]
27+
}
28+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2021 http4s.org
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.http4s.crypto
18+
19+
import cats.ApplicativeThrow
20+
import scodec.bits.ByteVector
21+
22+
import scala.scalanative.unsafe._
23+
import scala.scalanative.unsigned._
24+
25+
private[crypto] trait HashCompanionPlatform {
26+
implicit def forApplicativeThrow[F[_]](implicit F: ApplicativeThrow[F]): Hash[F] =
27+
new UnsealedHash[F] {
28+
def digest(algorithm: HashAlgorithm, data: ByteVector): F[ByteVector] =
29+
Zone { implicit z =>
30+
import HashAlgorithm._
31+
32+
val name = algorithm match {
33+
case MD5 => c"MD5"
34+
case SHA1 => c"SHA1"
35+
case SHA256 => c"SHA256"
36+
case SHA512 => c"SHA512"
37+
}
38+
39+
val `type` = openssl.evp.EVP_get_digestbyname(name)
40+
if (`type` == null)
41+
F.raiseError(new GeneralSecurityException("EVP_get_digestbyname"))
42+
else {
43+
val md = stackalloc[CUnsignedChar](openssl.evp.EVP_MAX_MD_SIZE)
44+
val size = stackalloc[CUnsignedInt]()
45+
46+
if (openssl
47+
.evp
48+
.EVP_Digest(data.toPtr, data.size.toULong, md, size, `type`, null) == 1)
49+
F.pure(ByteVector.fromPtr(md.asInstanceOf[Ptr[Byte]], (!size).toLong))
50+
else
51+
F.raiseError(new GeneralSecurityException("EVP_DIGEST"))
52+
}
53+
}
54+
55+
}
56+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2021 http4s.org
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.http4s.crypto
18+
19+
import cats.effect.kernel.Sync
20+
import scodec.bits.ByteVector
21+
22+
import scala.scalanative.unsafe._
23+
24+
private[crypto] trait HmacKeyGenCompanionPlatform {
25+
implicit def forSync[F[_]](implicit F: Sync[F]): HmacKeyGen[F] =
26+
new UnsealedHmacKeyGen[F] {
27+
def generateKey[A <: HmacAlgorithm](algorithm: A): F[SecretKey[A]] =
28+
F.delay {
29+
val len = algorithm.minimumKeyLength
30+
val buf = stackalloc[Byte](len.toLong)
31+
if (openssl.rand.RAND_bytes(buf, len) != 1)
32+
throw new GeneralSecurityException("RAND_bytes")
33+
SecretKeySpec(ByteVector.fromPtr(buf, len.toLong), algorithm)
34+
}
35+
}
36+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2021 http4s.org
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.http4s.crypto
18+
19+
import cats.ApplicativeThrow
20+
import scodec.bits.ByteVector
21+
22+
import scala.scalanative.unsafe._
23+
import scala.scalanative.unsigned._
24+
25+
private[crypto] trait HmacPlatform[F[_]]
26+
27+
private[crypto] trait HmacCompanionPlatform {
28+
implicit def forApplicativeThrow[F[_]](implicit F: ApplicativeThrow[F]): Hmac[F] =
29+
new UnsealedHmac[F] {
30+
31+
def digest(key: SecretKey[HmacAlgorithm], data: ByteVector): F[ByteVector] =
32+
Zone { implicit z =>
33+
import HmacAlgorithm._
34+
35+
val SecretKeySpec(keyBytes, algorithm) = key
36+
37+
val name = algorithm match {
38+
case SHA1 => c"SHA1"
39+
case SHA256 => c"SHA256"
40+
case SHA512 => c"SHA512"
41+
}
42+
43+
val evpMd = openssl.evp.EVP_get_digestbyname(name)
44+
if (evpMd == null)
45+
F.raiseError(new GeneralSecurityException("EVP_get_digestbyname"))
46+
else {
47+
val md = stackalloc[CUnsignedChar](openssl.evp.EVP_MAX_MD_SIZE)
48+
val mdLen = stackalloc[CUnsignedInt]()
49+
50+
if (openssl
51+
.hmac
52+
.HMAC(
53+
evpMd,
54+
keyBytes.toPtr,
55+
keyBytes.size.toInt,
56+
data.toPtr.asInstanceOf[Ptr[CUnsignedChar]],
57+
data.size.toULong,
58+
md,
59+
mdLen) != null)
60+
F.pure(ByteVector.fromPtr(md.asInstanceOf[Ptr[Byte]], (!mdLen).toLong))
61+
else
62+
F.raiseError(new GeneralSecurityException("HMAC"))
63+
}
64+
}
65+
66+
def importKey[A <: HmacAlgorithm](key: ByteVector, algorithm: A): F[SecretKey[A]] =
67+
F.pure(SecretKeySpec(key, algorithm))
68+
}
69+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2021 http4s.org
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.http4s.crypto
18+
19+
private[crypto] trait KeyPlatform
20+
private[crypto] trait PublicKeyPlatform
21+
private[crypto] trait PrivateKeyPlatform
22+
private[crypto] trait SecretKeyPlatform
23+
private[crypto] trait SecretKeySpecPlatform[+A <: Algorithm]

0 commit comments

Comments
 (0)