Skip to content

Commit

Permalink
packageTimestamp setting
Browse files Browse the repository at this point in the history
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 a global setting called `packageTimestamp`, which is
initialized as follows:

```scala
packageTimestamp :== Package.defaultTimestamp,
```

Here the `Package.defaultTimestamp` would pick either the value from the
`SOURCE_DATE_EPOCH` environment variable or 2010-01-01.

To opt out of this default, the user can use:

```scala
ThisBuild / packageTimestamp := Package.keepTimestamps

// or

ThisBuild / packageTimestamp := Package.gitCommitDateTimestamp
```

Before (sbt 1.4.6)
------------------

```
$ 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 (using Package.gitCommitDateTimestamp)
--------------------------------------------

```
$ unzip -v target/scala-2.13/root_2.13-0.1.0-SNAPSHOT.jar
Archive:  target/scala-2.13/root_2.13-0.1.0-SNAPSHOT.jar
 Length   Method    Size  Cmpr    Date    Time   CRC-32   Name
--------  ------  ------- ---- ---------- ----- --------  ----
     288  Defl:N      136  53% 01-25-2021 03:09 888682a9  META-INF/MANIFEST.MF
       0  Stored        0   0% 01-25-2021 03:09 00000000  example/
     901  Defl:N      601  33% 01-25-2021 03:09 3543f377  example/Greeting.class
    3079  Defl:N     1279  59% 01-25-2021 03:09 848b4386  example/Hello$.class
     738  Defl:N      464  37% 01-25-2021 03:09 571f4288  example/Hello$delayedInit$body.class
     875  Defl:N      594  32% 01-25-2021 03:09 ad295259  example/Hello.class
--------          -------  ---                            -------
    5881             3074  48%                            6 files
```
  • Loading branch information
eed3si9n committed Jan 25, 2021
1 parent 01b5cb1 commit 628d908
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 12 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
val keepTimestamps: Option[Long] = None
val fixed2010Timestamp: Option[Long] = Some(default2010Timestamp)
def gitCommitDateTimestamp: Option[Long] =
try {
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"
)
}
def setFixedTimestamp(value: Option[Long]): PackageOption =
FixedTimestamp(value)

/** 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
23 changes: 14 additions & 9 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,
packageTimestamp :== Package.defaultTimestamp,
) ++ BuildServerProtocol.globalSettings

private[sbt] lazy val globalIvyCore: Seq[Setting[_]] =
Expand Down Expand Up @@ -1599,20 +1600,27 @@ object Defaults extends BuildCommon {
val org = organization.value
val orgName = organizationName.value
val main = mainClass.value
val ts = packageTimestamp.value
val old = packageOptions.value

Package.addSpecManifestAttributes(n, ver, orgName) +:
Package.addImplManifestAttributes(n, ver, homepage.value, org, orgName) +:
Package.setFixedTimestamp(ts) +:
main.map(Package.MainClass.apply) ++: old
}
)
) ++
inTask(packageSrc)(
Seq(
packageOptions := Package.addSpecManifestAttributes(
name.value,
version.value,
organizationName.value
) +: packageOptions.value
packageOptions := {
val old = packageOptions.value
val ts = packageTimestamp.value
Package.addSpecManifestAttributes(
name.value,
version.value,
organizationName.value
) +: Package.setFixedTimestamp(ts) +: old
}
)
) ++
packageTaskSettings(packageBin, packageBinMappings) ++
Expand Down Expand Up @@ -1778,10 +1786,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
1 change: 1 addition & 0 deletions main/src/main/scala/sbt/Keys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ object Keys {
val packageCache = taskKey[File]("Produces the main artifact for caching.")

val packageOptions = taskKey[Seq[PackageOption]]("Options for packaging.").withRank(BTask)
val packageTimestamp = settingKey[Option[Long]]("Overwrites timestamps in JAR file to make the build reproducible; None keeps the existing timestamps (useful for web resources)").withRank(CSetting)
val packageConfiguration = taskKey[Package.Configuration]("Collects all inputs needed for packaging.").withRank(DTask)
val artifactPath = settingKey[File]("The location of a generated artifact.").withRank(BPlusSetting)
val artifact = settingKey[Artifact]("Describes an artifact.").withRank(BMinusSetting)
Expand Down

0 comments on commit 628d908

Please sign in to comment.