Skip to content

Commit eba1c66

Browse files
authored
Accept resources in Github Gists (#415)
* Accept resources in Github Gists
1 parent 29c0be6 commit eba1c66

File tree

12 files changed

+206
-18
lines changed

12 files changed

+206
-18
lines changed

modules/build/src/main/scala/scala/build/Build.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,9 @@ object Build {
159159

160160
val baseOptions = overrideOptions.orElse(sharedOptions)
161161

162-
val sources = value(crossSources.scopedSources(baseOptions))
162+
val crossSources0 = crossSources.withVirtualDir(inputs0, scope, baseOptions)
163+
164+
val sources = value(crossSources0.scopedSources(baseOptions))
163165
.sources(scope, baseOptions)
164166

165167
val generatedSources = sources.generateSources(inputs0.generatedSrcRoot(scope))

modules/build/src/main/scala/scala/build/CrossSources.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ final case class CrossSources(
1414
buildOptions: Seq[HasBuildRequirements[BuildOptions]]
1515
) {
1616

17+
def withVirtualDir(inputs: Inputs, scope: Scope, options: BuildOptions): CrossSources = {
18+
19+
val srcRootPath = inputs.generatedSrcRoot(scope)
20+
val resourceDirs0 = options.classPathOptions.resourceVirtualDir.map { path =>
21+
HasBuildRequirements(BuildRequirements(), srcRootPath / path)
22+
}
23+
24+
copy(
25+
resourceDirs = resourceDirs ++ resourceDirs0
26+
)
27+
}
28+
1729
def sharedOptions(baseOptions: BuildOptions): BuildOptions =
1830
buildOptions
1931
.filter(_.requirements.isEmpty)

modules/build/src/main/scala/scala/build/Inputs.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ object Inputs {
195195
extends Virtual with AnyScalaFile
196196
final case class VirtualJavaFile(content: Array[Byte], source: String)
197197
extends Virtual with Compiled
198+
final case class VirtualData(content: Array[Byte], source: String)
199+
extends Virtual
198200

199201
private def inputsHash(elements: Seq[Element]): String = {
200202
def bytes(s: String): Array[Byte] = s.getBytes(StandardCharsets.UTF_8)
@@ -226,7 +228,7 @@ object Inputs {
226228
}
227229

228230
private def forValidatedElems(
229-
validElems: Seq[Compiled],
231+
validElems: Seq[Element],
230232
baseProjectName: String,
231233
directories: Directories
232234
): Inputs = {
@@ -262,7 +264,7 @@ object Inputs {
262264
}
263265

264266
private val githubGistsArchiveRegex: Regex =
265-
s""":\\/\\/gist\\.github\\.com\\/[^\\/]*?\\/[^\\/]*$$""".r
267+
s"""gist\\.github\\.com\\/[^\\/]*?\\/[^\\/]*$$""".r
266268

267269
private def forNonEmptyArgs(
268270
args: Seq[String],
@@ -288,18 +290,19 @@ object Inputs {
288290
val url =
289291
if (githubGistsArchiveRegex.findFirstMatchIn(arg).nonEmpty) s"$arg/download" else arg
290292
download(url).map { content =>
291-
def resolve(path: String, content: Array[Byte]): Compiled = {
293+
def resolve(path: String, content: Array[Byte]): Element = {
292294
val wrapperPath =
293295
os.sub / path.split("/").last
294296

295297
if (path.endsWith(".scala")) VirtualScalaFile(content, path)
296298
else if (path.endsWith(".java")) VirtualJavaFile(content, path)
297-
else VirtualScript(content, path, wrapperPath)
299+
else if (path.endsWith(".sc")) VirtualScript(content, path, wrapperPath)
300+
else VirtualData(content, path)
298301
}
299302
if (githubGistsArchiveRegex.findFirstMatchIn(arg).nonEmpty) {
300303
val zipInputStream = new ZipInputStream(new ByteArrayInputStream(content))
301304
@tailrec
302-
def readArchive(acc: Seq[Compiled]): Seq[Compiled] =
305+
def readArchive(acc: Seq[Element]): Seq[Element] =
303306
zipInputStream.getNextEntry() match {
304307
case entry: ZipEntry if !entry.isDirectory =>
305308
val content = {

modules/build/src/main/scala/scala/build/Sources.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ object Sources {
3939
Seq(
4040
ScriptPreprocessor(codeWrapper),
4141
JavaPreprocessor,
42-
ScalaPreprocessor
42+
ScalaPreprocessor,
43+
DataPreprocessor
4344
)
4445
}

modules/build/src/main/scala/scala/build/options/ClassPathOptions.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ final case class ClassPathOptions(
1010
extraCompileOnlyJars: Seq[os.Path] = Nil,
1111
extraSourceJars: Seq[os.Path] = Nil,
1212
fetchSources: Option[Boolean] = None,
13-
extraDependencies: Seq[Positioned[AnyDependency]] = Nil
13+
extraDependencies: Seq[Positioned[AnyDependency]] = Nil,
14+
resourceVirtualDir: Seq[os.SubPath] = Nil
1415
)
1516

1617
object ClassPathOptions {

modules/build/src/main/scala/scala/build/options/HashedType.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ object HashedType {
1919
path => path.toString
2020
}
2121

22+
implicit val subPath: HashedType[os.SubPath] = {
23+
path => path.toString
24+
}
25+
2226
implicit val boolean: HashedType[Boolean] = {
2327
bool => bool.toString
2428
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package scala.build.preprocessing
2+
3+
import java.nio.charset.StandardCharsets
4+
5+
import scala.build.Inputs
6+
import scala.build.errors.BuildException
7+
8+
case object DataPreprocessor extends Preprocessor {
9+
def preprocess(input: Inputs.SingleElement)
10+
: Option[Either[BuildException, Seq[PreprocessedSource]]] =
11+
input match {
12+
case file: Inputs.VirtualData =>
13+
val content = new String(file.content, StandardCharsets.UTF_8)
14+
15+
val inMemory = Seq(
16+
PreprocessedSource.InMemory(
17+
Left(file.source),
18+
file.subPath,
19+
content,
20+
0,
21+
None,
22+
None,
23+
Nil,
24+
None,
25+
file.scopePath
26+
)
27+
)
28+
29+
Some(Right(inMemory))
30+
case _ =>
31+
None
32+
}
33+
}

modules/build/src/main/scala/scala/build/preprocessing/directives/Directive.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ object Directive {
1717
case object Using extends Type("using")
1818
case object Require extends Type("require")
1919

20+
def osRootResource(cwd: ScopePath): (Option[os.SubPath], Option[os.Path]) =
21+
cwd.root match {
22+
case Left(_) => (Some(cwd.path), None)
23+
case Right(root) => (None, Some(root / cwd.path))
24+
}
25+
2026
def osRoot(cwd: ScopePath, pos: Option[Position]): Either[BuildException, os.Path] =
2127
cwd.root match {
2228
case Left(virtualRoot) =>

modules/build/src/main/scala/scala/build/preprocessing/directives/UsingResourcesDirectiveHandler.scala

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package scala.build.preprocessing.directives
22

3-
import scala.build.EitherCps.{either, value}
3+
import scala.build.EitherCps.either
44
import scala.build.Position
55
import scala.build.errors.BuildException
66
import scala.build.options.{BuildOptions, ClassPathOptions}
@@ -24,11 +24,15 @@ case object UsingResourcesDirectiveHandler extends UsingDirectiveHandler {
2424
directive.values match {
2525
case Seq("resourceDir" | "resourceDirs", paths @ _*) =>
2626
val res = either {
27-
val root = value(Directive.osRoot(cwd, Some(directive.position)))
28-
val paths0 = paths.map(os.Path(_, root))
27+
val (virtualRootOpt, rootOpt) = Directive.osRootResource(cwd)
28+
val paths0 = rootOpt.map(root => paths.map(os.Path(_, root)))
29+
val virtualPaths = virtualRootOpt.map(virtualRoot =>
30+
paths.map(path => virtualRoot / os.SubPath(path))
31+
)
2932
BuildOptions(
3033
classPathOptions = ClassPathOptions(
31-
extraClassPath = paths0
34+
extraClassPath = paths0.toList.flatten,
35+
resourceVirtualDir = virtualPaths.toList.flatten
3236
)
3337
)
3438
}
@@ -43,12 +47,16 @@ case object UsingResourcesDirectiveHandler extends UsingDirectiveHandler {
4347
cwd: ScopePath,
4448
positionOpt: Option[Position]
4549
): Either[BuildException, BuildOptions] = either {
46-
val root = value(Directive.osRoot(cwd, positionOpt))
47-
val paths = DirectiveUtil.stringValues(values)
48-
val paths0 = paths.map(os.Path(_, root))
50+
val (virtualRootOpt, rootOpt) = Directive.osRootResource(cwd)
51+
val paths = DirectiveUtil.stringValues(values)
52+
val paths0 = rootOpt.map(root => paths.map(os.Path(_, root)))
53+
val virtualPaths = virtualRootOpt.map(virtualRoot =>
54+
paths.map(path => virtualRoot / os.SubPath(path))
55+
)
4956
BuildOptions(
5057
classPathOptions = ClassPathOptions(
51-
extraClassPath = paths0
58+
extraClassPath = paths0.toList.flatten,
59+
resourceVirtualDir = virtualPaths.toList.flatten
5260
)
5361
)
5462
}

modules/integration/src/main/scala/scala/cli/integration/TestInputs.scala

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package scala.cli.integration
22

3-
import java.io.IOException
3+
import java.io.{FileOutputStream, IOException}
44
import java.nio.charset.StandardCharsets
55
import java.security.SecureRandom
66
import java.util.concurrent.atomic.AtomicInteger
7+
import java.util.zip.{ZipEntry, ZipOutputStream}
78

9+
import scala.cli.integration.TestInputs.compress
810
import scala.util.control.NonFatal
911

1012
final case class TestInputs(
@@ -22,6 +24,12 @@ final case class TestInputs(
2224
writeIn(tmpDir)
2325
tmpDir
2426
}
27+
def asZip[T](f: (os.Path, os.Path) => T): T =
28+
TestInputs.withTmpDir { tmpDir =>
29+
val zipArchivePath = tmpDir / s"${tmpDir.last}.zip"
30+
compress(zipArchivePath, files)
31+
f(tmpDir, zipArchivePath)
32+
}
2533
def fromRoot[T](f: os.Path => T): T =
2634
TestInputs.withTmpDir { tmpDir =>
2735
writeIn(tmpDir)
@@ -31,6 +39,17 @@ final case class TestInputs(
3139

3240
object TestInputs {
3341

42+
def compress(zipFilepath: os.Path, files: Seq[(os.RelPath, String)]) = {
43+
val zip = new ZipOutputStream(new FileOutputStream(zipFilepath.toString()))
44+
try for ((relPath, content) <- files) {
45+
zip.putNextEntry(new ZipEntry(relPath.toString()))
46+
val in: Array[Byte] = content.getBytes
47+
zip.write(in)
48+
zip.closeEntry()
49+
}
50+
finally zip.close()
51+
}
52+
3453
private lazy val baseTmpDir = {
3554
Option(System.getenv("SCALA_CLI_TMP")).getOrElse {
3655
sys.error("SCALA_CLI_TMP not set")

0 commit comments

Comments
 (0)