Skip to content

Commit

Permalink
Package.keepTimestamps, Package.fixed2010Timestamp, and Package.gitCo…
Browse files Browse the repository at this point in the history
…mmitDate

Fixes sbt#6235

In sbt 1.4.0 (sbt#5344) we started wiping out the timestamps in JAR to make the builds more repeatable. This had an unintended consequence of breaking Play's last-modified response header (playframework/playframework#10572).

This adds an opt-out from the default:

```scala
ThisBuild / packageOptions += Package.keepTimestamps
```

Before
------

```
$ ll example
total 32
-rw-r--r--  1 eed3si9n  wheel   901 Jan  1  1970 Greeting.class
-rw-r--r--  1 eed3si9n  wheel  3079 Jan  1  1970 Hello$.class
-rw-r--r--  1 eed3si9n  wheel   738 Jan  1  1970 Hello$delayedInit$body.class
-rw-r--r--  1 eed3si9n  wheel   875 Jan  1  1970 Hello.class
```

After
-----

```
$ ll target/scala-2.13/hello_2.13-0.1.0-SNAPSHOT/example
total 32
-rwxr-xr-x  1 eed3si9n  wheel   901 Jan  3 12:20 Greeting.class*
-rwxr-xr-x  1 eed3si9n  wheel  3079 Jan  3 12:20 Hello$.class*
-rwxr-xr-x  1 eed3si9n  wheel   738 Jan  3 12:20 Hello$delayedInit$body.class*
-rwxr-xr-x  1 eed3si9n  wheel   875 Jan  3 12:20 Hello.class*
```
  • Loading branch information
eed3si9n committed Jan 25, 2021
1 parent 01b5cb1 commit 4840f42
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 8 deletions.
49 changes: 46 additions & 3 deletions main-actions/src/main/scala/sbt/Package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package sbt

import java.io.File
import java.time.OffsetDateTime
import java.util.jar.{ Attributes, Manifest }
import scala.collection.JavaConverters._
import sbt.internal.util.Types.:+:
Expand All @@ -23,6 +24,7 @@ import sbt.internal.util.HListFormats._
import sbt.util.FileInfo.{ exists, lastModified }
import sbt.util.CacheImplicits._
import sbt.util.Tracked.{ inputChanged, outputChanged }
import scala.sys.process.Process

sealed trait PackageOption

Expand All @@ -43,6 +45,40 @@ object Package {
val converted = for ((name, value) <- attributes) yield (new Attributes.Name(name), value)
new ManifestAttributes(converted: _*)
}
// 2010-01-01
private val default2010Timestamp: Long = 1262304000000L
final case class FixedTimestamp(value: Option[Long]) extends PackageOption
def keepTimestamps: FixedTimestamp = FixedTimestamp(None)
def fixed2010Timestamp: FixedTimestamp = FixedTimestamp(Some(default2010Timestamp))
def gitCommitDate: FixedTimestamp =
try {
FixedTimestamp(
Some(
OffsetDateTime
.parse(Process("git show -s --format=%cI").!!.trim)
.toInstant()
.toEpochMilli()
)
)
} catch {
case e: Exception if e.getMessage.startsWith("Nonzero") =>
sys.error(
s"git repository was expected for package timestamp; use Package.fixed2010Timestamp or Package.keepTimestamps instead"
)
}

/** by default we overwrite all timestamps in JAR to epoch time 2010-01-01 for repeatable build */
lazy val defaultTimestamp: Option[Long] =
sys.env
.get("SOURCE_DATE_EPOCH")
.map(_.toLong * 1000)
.orElse(Some(default2010Timestamp))

def timeFromConfiguration(config: Configuration): Option[Long] =
(config.options.collect { case t: FixedTimestamp => t }).headOption match {
case Some(FixedTimestamp(value)) => value
case _ => defaultTimestamp
}

def mergeAttributes(a1: Attributes, a2: Attributes) = a1.asScala ++= a2.asScala
// merges `mergeManifest` into `manifest` (mutating `manifest` in the process)
Expand Down Expand Up @@ -70,9 +106,14 @@ object Package {
val options: Seq[PackageOption]
)

@deprecated("Please specify whether to use a static timestamp", "1.4.0")
/**
*
* @param conf the package configuration that should be build
* @param cacheStoreFactory used for jar caching. We try to avoid rebuilds as much as possible
* @param log feedback for the user
*/
def apply(conf: Configuration, cacheStoreFactory: CacheStoreFactory, log: Logger): Unit =
apply(conf, cacheStoreFactory, log, None)
apply(conf, cacheStoreFactory, log, timeFromConfiguration(conf))

/**
*
Expand All @@ -94,6 +135,7 @@ object Package {
case JarManifest(mergeManifest) => mergeManifests(manifest, mergeManifest); ()
case MainClass(mainClassName) => main.put(Attributes.Name.MAIN_CLASS, mainClassName); ()
case ManifestAttributes(attributes @ _*) => main.asScala ++= attributes; ()
case FixedTimestamp(value) => ()
case _ => log.warn("Ignored unknown package option " + option)
}
}
Expand Down Expand Up @@ -163,7 +205,8 @@ object Package {
homepage map (h => (IMPLEMENTATION_URL, h.toString))
}: _*)
}
@deprecated("Please specify whether to use a static timestamp", "1.4.0")

@deprecated("Specify whether to use a static timestamp", "1.4.0")
def makeJar(sources: Seq[(File, String)], jar: File, manifest: Manifest, log: Logger): Unit =
makeJar(sources, jar, manifest, log, None)

Expand Down
9 changes: 4 additions & 5 deletions main/src/main/scala/sbt/Defaults.scala
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ object Defaults extends BuildCommon {
bgCopyClasspath :== true,
closeClassLoaders :== SysProp.closeClassLoaders,
allowZombieClassLoaders :== true,
packageOptions :== Nil,
) ++ BuildServerProtocol.globalSettings

private[sbt] lazy val globalIvyCore: Seq[Setting[_]] =
Expand Down Expand Up @@ -1585,7 +1586,8 @@ object Defaults extends BuildCommon {
artifact := Artifact(moduleName.value)
) ++ Defaults.globalDefaults(
Seq(
packageOptions :== Nil,
// This is to refer to ThisBuild / packageOptions, if the user provides it
packageOptions := packageOptions.value,
artifactName :== (Artifact.artifactName _)
)
)
Expand Down Expand Up @@ -1778,10 +1780,7 @@ object Defaults extends BuildCommon {
config,
s.cacheStoreFactory,
s.log,
sys.env
.get("SOURCE_DATE_EPOCH")
.map(_.toLong * 1000)
.orElse(Some(1262304000000L)) // 2010-01-01
Package.timeFromConfiguration(config)
)
config.jar
}
Expand Down

0 comments on commit 4840f42

Please sign in to comment.