-
Notifications
You must be signed in to change notification settings - Fork 128
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Run native linker only if project was changed
- Loading branch information
Showing
5 changed files
with
282 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
modules/build/src/main/scala/scala/build/internal/NativeBuilderHelper.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package scala.build.internal | ||
|
||
import java.math.BigInteger | ||
import java.security.MessageDigest | ||
|
||
import scala.build.Build | ||
import scala.scalanative.{build => sn} | ||
|
||
case object NativeBuilderHelper { | ||
|
||
private def resolveProjectShaPath(nativeWorkDir: os.Path) = nativeWorkDir / ".project_sha" | ||
private def resolveOutputShaPath(nativeWorkDir: os.Path) = nativeWorkDir / ".output_sha" | ||
|
||
private def fileSha(filePath: os.Path): String = { | ||
val md = MessageDigest.getInstance("SHA-1") | ||
md.update(os.read.bytes(filePath)) | ||
|
||
val digest = md.digest() | ||
val calculatedSum = new BigInteger(1, digest) | ||
String.format(s"%040x", calculatedSum) | ||
} | ||
|
||
private def projectSha(build: Build.Successful, nativeConfig: sn.NativeConfig) = { | ||
val md = MessageDigest.getInstance("SHA-1") | ||
md.update(build.inputs.sourceHash().getBytes) | ||
md.update(nativeConfig.toString.getBytes) | ||
md.update(build.options.hash.getOrElse("").getBytes) | ||
|
||
val digest = md.digest() | ||
val calculatedSum = new BigInteger(1, digest) | ||
String.format(s"%040x", calculatedSum) | ||
} | ||
|
||
def updateOutputSha(dest: os.Path, nativeWorkDir: os.Path) = { | ||
val outputShaPath = resolveOutputShaPath(nativeWorkDir) | ||
val sha = fileSha(dest) | ||
os.write.over(outputShaPath, sha) | ||
} | ||
|
||
def shouldBuildIfChanged( | ||
build: Build.Successful, | ||
nativeConfig: sn.NativeConfig, | ||
dest: os.Path, | ||
nativeWorkDir: os.Path | ||
): Boolean = { | ||
val projectShaPath = resolveProjectShaPath(nativeWorkDir) | ||
val outputShaPath = resolveOutputShaPath(nativeWorkDir) | ||
|
||
val currentProjectSha = projectSha(build, nativeConfig) | ||
val currentOutputSha = if (os.exists(dest)) Some(fileSha(dest)) else None | ||
|
||
val previousProjectSha = if (os.exists(projectShaPath)) Some(os.read(projectShaPath)) else None | ||
val previousOutputSha = if (os.exists(outputShaPath)) Some(os.read(outputShaPath)) else None | ||
|
||
val changed = | ||
!previousProjectSha.contains(currentProjectSha) || | ||
previousOutputSha != currentOutputSha || | ||
!os.exists(dest) | ||
|
||
// update sha in .projectShaPath | ||
if (changed) os.write.over(projectShaPath, currentProjectSha, createFolders = true) | ||
|
||
changed | ||
} | ||
} |
178 changes: 178 additions & 0 deletions
178
modules/build/src/test/scala/scala/build/tests/NativeBuilderHelperTests.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
package scala.build.tests | ||
|
||
import com.eed3si9n.expecty.Expecty.{assert => expect} | ||
|
||
import scala.build.Ops.EitherThrowOps | ||
import scala.build.blooprifle.BloopRifleConfig | ||
import scala.build.internal.NativeBuilderHelper | ||
import scala.build.{Bloop, BuildThreads, Directories, LocalRepo, Logger} | ||
import scala.build.options.{BuildOptions, InternalOptions, ScalaOptions} | ||
import scala.util.{Properties, Random} | ||
|
||
class NativeBuilderHelperTests extends munit.FunSuite { | ||
|
||
val buildThreads = BuildThreads.create() | ||
val bloopConfig = BloopRifleConfig.default(v => Bloop.bloopClassPath(Logger.nop, v)) | ||
|
||
val helloFileName = "Hello.scala" | ||
|
||
val inputs = TestInputs( | ||
os.rel / helloFileName -> | ||
s"""object Hello extends App { | ||
| println("Hello") | ||
|} | ||
|""".stripMargin, | ||
os.rel / "main" / "Main.scala" -> | ||
s"""object Main extends App { | ||
| println("Hello") | ||
|} | ||
|""".stripMargin | ||
) | ||
|
||
val extraRepoTmpDir = os.temp.dir(prefix = "scala-cli-tests-extra-repo-") | ||
val directories = Directories.under(extraRepoTmpDir) | ||
|
||
val defaultOptions = BuildOptions( | ||
internal = InternalOptions( | ||
localRepository = LocalRepo.localRepo(directories.localRepoDir) | ||
) | ||
) | ||
|
||
test("should build native app at first time") { | ||
|
||
inputs.withBuild(defaultOptions, buildThreads, bloopConfig) { (root, _, maybeBuild) => | ||
val build = maybeBuild.toOption.get.successfulOpt.get | ||
|
||
val nativeConfig = build.options.scalaNativeOptions.config | ||
val nativeWorkDir = build.options.scalaNativeOptions.nativeWorkDir(root, "native-test") | ||
val destPath = nativeWorkDir / s"main${if (Properties.isWin) ".exe" else ""}" | ||
// generate dummy output | ||
os.write(destPath, Random.alphanumeric.take(10).mkString(""), createFolders = true) | ||
|
||
val changed = | ||
NativeBuilderHelper.shouldBuildIfChanged(build, nativeConfig, destPath, nativeWorkDir) | ||
expect(changed) | ||
} | ||
} | ||
|
||
test("should not rebuild the second time") { | ||
inputs.withBuild(defaultOptions, buildThreads, bloopConfig) { (root, _, maybeBuild) => | ||
val build = maybeBuild.toOption.get.successfulOpt.get | ||
|
||
val nativeConfig = build.options.scalaNativeOptions.config | ||
val nativeWorkDir = build.options.scalaNativeOptions.nativeWorkDir(root, "native-test") | ||
val destPath = nativeWorkDir / s"main${if (Properties.isWin) ".exe" else ""}" | ||
// generate dummy output | ||
os.write(destPath, Random.alphanumeric.take(10).mkString(""), createFolders = true) | ||
|
||
val changed = | ||
NativeBuilderHelper.shouldBuildIfChanged(build, nativeConfig, destPath, nativeWorkDir) | ||
NativeBuilderHelper.updateOutputSha(destPath, nativeWorkDir) | ||
expect(changed) | ||
|
||
val changedSameBuild = | ||
NativeBuilderHelper.shouldBuildIfChanged(build, nativeConfig, destPath, nativeWorkDir) | ||
expect(!changedSameBuild) | ||
} | ||
} | ||
|
||
test("should build native if output file was deleted") { | ||
inputs.withBuild(defaultOptions, buildThreads, bloopConfig) { (root, _, maybeBuild) => | ||
val build = maybeBuild.toOption.get.successfulOpt.get | ||
|
||
val nativeConfig = build.options.scalaNativeOptions.config | ||
val nativeWorkDir = build.options.scalaNativeOptions.nativeWorkDir(root, "native-test") | ||
val destPath = nativeWorkDir / s"main${if (Properties.isWin) ".exe" else ""}" | ||
// generate dummy output | ||
os.write(destPath, Random.alphanumeric.take(10).mkString(""), createFolders = true) | ||
|
||
val changed = | ||
NativeBuilderHelper.shouldBuildIfChanged(build, nativeConfig, destPath, nativeWorkDir) | ||
NativeBuilderHelper.updateOutputSha(destPath, nativeWorkDir) | ||
expect(changed) | ||
|
||
os.remove(destPath) | ||
val changedAfterDelete = | ||
NativeBuilderHelper.shouldBuildIfChanged(build, nativeConfig, destPath, nativeWorkDir) | ||
expect(changedAfterDelete) | ||
} | ||
} | ||
|
||
test("should build native if output file was changed") { | ||
inputs.withBuild(defaultOptions, buildThreads, bloopConfig) { (root, _, maybeBuild) => | ||
val build = maybeBuild.toOption.get.successfulOpt.get | ||
|
||
val nativeConfig = build.options.scalaNativeOptions.config | ||
val nativeWorkDir = build.options.scalaNativeOptions.nativeWorkDir(root, "native-test") | ||
val destPath = nativeWorkDir / s"main${if (Properties.isWin) ".exe" else ""}" | ||
// generate dummy output | ||
os.write(destPath, Random.alphanumeric.take(10).mkString(""), createFolders = true) | ||
|
||
val changed = | ||
NativeBuilderHelper.shouldBuildIfChanged(build, nativeConfig, destPath, nativeWorkDir) | ||
NativeBuilderHelper.updateOutputSha(destPath, nativeWorkDir) | ||
expect(changed) | ||
|
||
os.write.over(destPath, Random.alphanumeric.take(10).mkString("")) | ||
val changedAfterFileUpdate = | ||
NativeBuilderHelper.shouldBuildIfChanged(build, nativeConfig, destPath, nativeWorkDir) | ||
expect(changedAfterFileUpdate) | ||
} | ||
} | ||
|
||
test("should build native if input file was changed") { | ||
inputs.withBuild(defaultOptions, buildThreads, bloopConfig) { (root, _, maybeBuild) => | ||
val build = maybeBuild.toOption.get.successfulOpt.get | ||
|
||
val nativeConfig = build.options.scalaNativeOptions.config | ||
val nativeWorkDir = build.options.scalaNativeOptions.nativeWorkDir(root, "native-test") | ||
val destPath = nativeWorkDir / s"main${if (Properties.isWin) ".exe" else ""}" | ||
os.write(destPath, Random.alphanumeric.take(10).mkString(""), createFolders = true) | ||
|
||
val changed = | ||
NativeBuilderHelper.shouldBuildIfChanged(build, nativeConfig, destPath, nativeWorkDir) | ||
NativeBuilderHelper.updateOutputSha(destPath, nativeWorkDir) | ||
expect(changed) | ||
|
||
os.write.append(root / helloFileName, Random.alphanumeric.take(10).mkString("")) | ||
val changedAfterFileUpdate = | ||
NativeBuilderHelper.shouldBuildIfChanged(build, nativeConfig, destPath, nativeWorkDir) | ||
expect(changedAfterFileUpdate) | ||
} | ||
} | ||
|
||
test("should build native if native config was changed") { | ||
inputs.withBuild(defaultOptions, buildThreads, bloopConfig) { (root, _, maybeBuild) => | ||
val build = maybeBuild.toOption.get.successfulOpt.get | ||
|
||
val nativeConfig = build.options.scalaNativeOptions.config | ||
val nativeWorkDir = build.options.scalaNativeOptions.nativeWorkDir(root, "native-test") | ||
val destPath = nativeWorkDir / s"main${if (Properties.isWin) ".exe" else ""}" | ||
os.write(destPath, Random.alphanumeric.take(10).mkString(""), createFolders = true) | ||
|
||
val changed = | ||
NativeBuilderHelper.shouldBuildIfChanged(build, nativeConfig, destPath, nativeWorkDir) | ||
NativeBuilderHelper.updateOutputSha(destPath, nativeWorkDir) | ||
expect(changed) | ||
|
||
val updatedBuild = build.copy( | ||
options = build.options.copy( | ||
scalaNativeOptions = build.options.scalaNativeOptions.copy( | ||
clang = Some(Random.alphanumeric.take(10).mkString("")) | ||
) | ||
) | ||
) | ||
val updatedNativeConfig = updatedBuild.options.scalaNativeOptions.config | ||
|
||
val changedAfterConfigUpdate = | ||
NativeBuilderHelper.shouldBuildIfChanged( | ||
updatedBuild, | ||
updatedNativeConfig, | ||
destPath, | ||
nativeWorkDir | ||
) | ||
expect(changedAfterConfigUpdate) | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters