Skip to content

Commit 98af7bf

Browse files
authored
Merge pull request scala#9685 from retronym/topic/reproducible-osgi
Configure OSGI bnd tool for reproducible builds
2 parents c5620ef + 5ee8292 commit 98af7bf

11 files changed

+290
-264
lines changed

build.sbt

Lines changed: 205 additions & 203 deletions
Large diffs are not rendered by default.

project/AutomaticModuleName.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ object AutomaticModuleName {
1515
def settings(name: String): Seq[Def.Setting[_]] = {
1616
val pair = ("Automatic-Module-Name" -> name)
1717
Seq(
18-
packageOptions in (Compile, packageBin) += Package.ManifestAttributes(pair),
18+
(Compile / packageBin / packageOptions) += Package.ManifestAttributes(pair),
1919
Osgi.headers += pair
2020
)
2121
}

project/JitWatch.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ object JitWatchFilePlugin extends AutoPlugin {
3434

3535
// Transitive sources from the projects that contribute to this classpath.
3636
val projects: Seq[ProjectRef] = buildDependencies.value.classpathTransitiveRefs(thisProjectRef.value) :+ thisProjectRef.value
37-
val projectArtifacts: Map[ProjectRef, Seq[Artifact]] = projects.map(project => (project -> (Keys.artifacts in project get settingsData.value).getOrElse(Nil))).toMap
38-
val artifactNameToProject: Map[String, Seq[ProjectRef]] = projects.groupBy(project => (Keys.name in project get settingsData.value).getOrElse(""))
37+
val projectArtifacts: Map[ProjectRef, Seq[Artifact]] = projects.map(project => (project -> ((project / Keys.artifacts) get settingsData.value).getOrElse(Nil))).toMap
38+
val artifactNameToProject: Map[String, Seq[ProjectRef]] = projects.groupBy(project => ((project / Keys.name) get settingsData.value).getOrElse(""))
3939
val transitiveSourceDirectories = projects.flatMap { project =>
40-
val projectArtifacts: Seq[Artifact] = (Keys.artifacts in project get settingsData.value).getOrElse(Nil)
40+
val projectArtifacts: Seq[Artifact] = ((project / Keys.artifacts) get settingsData.value).getOrElse(Nil)
4141
val matching = projectArtifacts.filter(artifacts.contains(_))
4242
val configs = matching.flatMap(artifact => artifact.configurations).distinct
4343
val sourceDirectories: Seq[File] = configs.flatMap { configRef =>
44-
(Keys.sourceDirectories in project in sbt.Configuration.of(configRef.name.capitalize, configRef.name)).get(settingsData.value).toList.flatten
44+
(project / sbt.Configuration.of(configRef.name.capitalize, configRef.name) / Keys.sourceDirectories).get(settingsData.value).toList.flatten
4545
}
4646
sourceDirectories
4747
}.distinct
@@ -50,7 +50,7 @@ object JitWatchFilePlugin extends AutoPlugin {
5050
projects.flatMap { project: ProjectRef =>
5151
val configs = artifact.configurations
5252
val sourceDirectories: Seq[File] = configs.toList.flatMap { configRef =>
53-
(Keys.sourceDirectories in project in sbt.Configuration.of(configRef.name.capitalize, configRef.name)).get(settingsData.value).toList.flatten
53+
(project / sbt.Configuration.of(configRef.name.capitalize, configRef.name) / Keys.sourceDirectories).get(settingsData.value).toList.flatten
5454
}
5555
sourceDirectories
5656
}

project/License.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ object License extends AutoPlugin {
1010

1111
override def projectSettings: Seq[Def.Setting[_]] =
1212
List(packageSrc, packageBin, packageDoc)
13-
.map(pkg => mappings in (Compile, pkg) ++= licenseMapping.value)
13+
.map(pkg => (Compile / pkg / mappings) ++= licenseMapping.value)
1414

1515
override def buildSettings: Seq[Def.Setting[_]] = Seq(
1616
licenseMapping := List("LICENSE", "NOTICE").map(fn => (baseDirectory.value / fn) -> fn)

project/Osgi.scala

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import sbt.Keys._
88
import collection.JavaConverters._
99
import VersionUtil.versionProperties
1010

11-
/** OSGi packaging for the Scala build, distilled from sbt-osgi. We do not use sbt-osgi because it
12-
* depends on a newer version of BND which gives slightly different output (probably OK to upgrade
13-
* in the future, now that the Ant build has been removed) and does not allow a crucial bit of
11+
/** OSGi packaging for the Scala build, distilled from sbt-osgi.
12+
*
13+
* We don't use sbt-osgi (yet) because it does not allow a crucial bit of
1414
* configuration that we need: Setting the classpath for BND. In sbt-osgi this is always
15-
* `fullClasspath in Compile` whereas we want `products in Compile in packageBin`. */
15+
* `fullClasspath in Compile` whereas we want `products in Compile in packageBin`. */
1616
object Osgi {
1717
val bundle = TaskKey[File]("osgiBundle", "Create an OSGi bundle.")
1818
val bundleName = SettingKey[String]("osgiBundleName", "The Bundle-Name for the manifest.")
@@ -29,23 +29,42 @@ object Osgi {
2929
"Bundle-Name" -> bundleName.value,
3030
"Bundle-SymbolicName" -> bundleSymbolicName.value,
3131
"ver" -> v,
32-
"Export-Package" -> "*;version=${ver};-split-package:=merge-first",
32+
33+
// bnd 3.0 fixes for https://github.com/bndtools/bnd/issues/971. This changes our OSGi
34+
// metadata by adding Import-Package automatically for all of our exported packages.
35+
// Supposedly this is the right thing to do: https://blog.osgi.org/2007/04/importance-of-exporting-nd-importing.html
36+
// but I'm disabling the feature (`-noimport:=true`) to avoid changing this detail of
37+
// our little understood OSGi metadata for now.
38+
"Export-Package" -> "*;version=${ver};-noimport:=true;-split-package:=merge-first",
39+
3340
"Import-Package" -> "scala.*;version=\"${range;[==,=+);${ver}}\",*",
3441
"Bundle-Version" -> v,
3542
"Bundle-RequiredExecutionEnvironment" -> "JavaSE-1.8",
36-
"-eclipse" -> "false"
43+
"-eclipse" -> "false",
44+
45+
// Great new feature in modern bnd versions: reproducible builds.
46+
// Omits the Bundle-LastModified header and avoids using System.currentTimeMillis
47+
// for ZIP metadata.
48+
"-reproducible" -> "true",
49+
50+
// https://github.com/bndtools/bnd/commit/2f1d89428559d21857b87b6d5b465a18a300becc (bndlib 4.2.0)
51+
// seems to have fixed a bug in its detection class references in Class.forName("some.Class")
52+
// For our build, this adds an import on the package "com.cloudius.util" (referred to by an optional
53+
// part of JLine. This directive disables the Class.forName scanning. An alternative fix would be
54+
// direct this to be an optional dependency (as we do for jline itself with `"Import-Package" -> ("jline.*;resolution:=optional," + ... )`)
55+
"-noclassforname" -> "true" //
3756
)
3857
},
3958
jarlist := false,
4059
bundle := Def.task {
41-
val cp = (products in Compile in packageBin).value
60+
val cp = (Compile / packageBin / products).value
4261
val licenseFiles = License.licenseMapping.value.map(_._1)
4362
bundleTask(headers.value.toMap, jarlist.value, cp,
44-
(artifactPath in (Compile, packageBin)).value, cp ++ licenseFiles, streams.value)
63+
(Compile / packageBin / artifactPath).value, cp ++ licenseFiles, streams.value)
4564
}.value,
46-
packagedArtifact in (Compile, packageBin) := (((artifact in (Compile, packageBin)).value, bundle.value)),
65+
(Compile / packageBin / packagedArtifact) := (((Compile / packageBin / artifact).value, bundle.value)),
4766
// Also create OSGi source bundles:
48-
packageOptions in (Compile, packageSrc) += Package.ManifestAttributes(
67+
(Compile / packageSrc / packageOptions) += Package.ManifestAttributes(
4968
"Bundle-Name" -> (description.value + " Sources"),
5069
"Bundle-SymbolicName" -> (bundleSymbolicName.value + ".source"),
5170
"Bundle-Version" -> versionProperties.value.osgiVersion,

project/ScaladocSettings.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ object ScaladocSettings {
1515
s.get(artifact.key).isDefined && s.get(moduleID.key).exists(_.organization == "org.webjars")
1616
val dest = (resourceManaged.value / "webjars").getAbsoluteFile
1717
IO.createDirectory(dest)
18-
val classpathes = (dependencyClasspath in Compile).value
18+
val classpathes = (Compile / dependencyClasspath).value
1919
val files: Seq[File] = classpathes.filter(isWebjar).flatMap { classpathEntry =>
2020
val jarFile = classpathEntry.data
2121
IO.unzip(jarFile, dest)

project/ScriptCommands.scala

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ object ScriptCommands {
2626
* The optional argument is the Artifactory snapshot repository URL. */
2727
def setupPublishCoreNonOpt = setup("setupPublishCoreNonOpt") { args =>
2828
Seq(
29-
baseVersionSuffix in Global := "SHA-SNAPSHOT"
29+
(Global / baseVersionSuffix) := "SHA-SNAPSHOT"
3030
) ++ (args match {
3131
case Seq(url) => publishTarget(url)
3232
case Nil => Nil
@@ -37,7 +37,7 @@ object ScriptCommands {
3737
* The optional argument is the Artifactory snapshot repository URL. */
3838
def setupPublishCore = setup("setupPublishCore") { args =>
3939
Seq(
40-
baseVersionSuffix in Global := "SHA-SNAPSHOT"
40+
(Global / baseVersionSuffix) := "SHA-SNAPSHOT"
4141
) ++ (args match {
4242
case Seq(url) => publishTarget(url)
4343
case Nil => Nil
@@ -48,9 +48,9 @@ object ScriptCommands {
4848
* The optional argument is the Artifactory snapshot repository URL. */
4949
def setupValidateTest = setup("setupValidateTest") { args =>
5050
Seq(
51-
testOptions in IntegrationTest in LocalProject("test") ++= Seq(Tests.Argument("--show-log"), Tests.Argument("--show-diff"))
51+
LocalProject("test") / IntegrationTest / testOptions ++= Seq(Tests.Argument("--show-log"), Tests.Argument("--show-diff"))
5252
) ++ (args match {
53-
case Seq(url) => Seq(resolvers in Global += "scala-pr" at url)
53+
case Seq(url) => Seq((Global / resolvers) += "scala-pr" at url)
5454
case Nil => Nil
5555
}) ++ enableOptimizer
5656
}
@@ -61,8 +61,8 @@ object ScriptCommands {
6161
def setupBootstrapStarr = setup("setupBootstrapStarr") { case Seq(fileOrUrl, ver) =>
6262
val url = fileToUrl(fileOrUrl)
6363
Seq(
64-
baseVersion in Global := ver,
65-
baseVersionSuffix in Global := "SPLIT"
64+
(Global / baseVersion) := ver,
65+
(Global / baseVersionSuffix) := "SPLIT"
6666
) ++ publishTarget(url) ++ noDocs ++ enableOptimizer
6767
}
6868

@@ -72,9 +72,9 @@ object ScriptCommands {
7272
def setupBootstrapLocker = setup("setupBootstrapLocker") { case Seq(fileOrUrl, ver) =>
7373
val url = fileToUrl(fileOrUrl)
7474
Seq(
75-
baseVersion in Global := ver,
76-
baseVersionSuffix in Global := "SPLIT",
77-
resolvers in Global += "scala-pr" at url
75+
(Global / baseVersion) := ver,
76+
(Global / baseVersionSuffix) := "SPLIT",
77+
(Global / resolvers) += "scala-pr" at url
7878
) ++ publishTarget(url) ++ noDocs ++ enableOptimizer
7979
}
8080

@@ -88,10 +88,10 @@ object ScriptCommands {
8888
val targetUrl = fileToUrl(targetFileOrUrl)
8989
val resolverUrl = fileToUrl(resolverFileOrUrl)
9090
Seq(
91-
baseVersion in Global := ver,
92-
baseVersionSuffix in Global := "SPLIT",
93-
resolvers in Global += "scala-pr" at resolverUrl,
94-
testOptions in IntegrationTest in LocalProject("test") ++= Seq(Tests.Argument("--show-log"), Tests.Argument("--show-diff"))
91+
(Global / baseVersion) := ver,
92+
(Global / baseVersionSuffix) := "SPLIT",
93+
(Global / resolvers) += "scala-pr" at resolverUrl,
94+
LocalProject("test") / IntegrationTest / testOptions ++= Seq(Tests.Argument("--show-log"), Tests.Argument("--show-diff"))
9595
) ++ publishTarget(targetUrl) ++ enableOptimizer
9696
}
9797

@@ -102,11 +102,11 @@ object ScriptCommands {
102102
def setupBootstrapPublish = setup("setupBootstrapPublish") { case Seq(fileOrUrl, ver) =>
103103
val url = fileToUrl(fileOrUrl)
104104
Seq(
105-
baseVersion in Global := ver,
106-
baseVersionSuffix in Global := "SPLIT",
107-
resolvers in Global += "scala-pr" at url,
108-
publishTo in Global := Some("sonatype-releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2"),
109-
credentials in Global ++= {
105+
(Global / baseVersion) := ver,
106+
(Global / baseVersionSuffix) := "SPLIT",
107+
(Global / resolvers) += "scala-pr" at url,
108+
(Global / publishTo) := Some("sonatype-releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2"),
109+
(Global / credentials) ++= {
110110
val user = env("SONA_USER")
111111
val pass = env("SONA_PASS")
112112
if (user != "" && pass != "")
@@ -152,20 +152,20 @@ object ScriptCommands {
152152
}
153153

154154
val enableOptimizer = Seq(
155-
scalacOptions in Compile in ThisBuild ++= Seq("-opt:l:inline", "-opt-inline-from:scala/**")
155+
ThisBuild / Compile / scalacOptions ++= Seq("-opt:l:inline", "-opt-inline-from:scala/**")
156156
)
157157

158158
val noDocs = Seq(
159-
publishArtifact in (Compile, packageDoc) in ThisBuild := false
159+
ThisBuild / Compile / packageDoc / publishArtifact := false
160160
)
161161

162162
private[this] def publishTarget(url: String) = {
163163
// Append build.timestamp to Artifactory URL to get consistent build numbers (see https://github.com/sbt/sbt/issues/2088):
164164
val url2 = if(url.startsWith("file:")) url else url.replaceAll("/$", "") + ";build.timestamp=" + System.currentTimeMillis
165165

166166
Seq(
167-
publishTo in Global := Some("scala-pr-publish" at url2),
168-
credentials in Global ++= {
167+
(Global / publishTo) := Some("scala-pr-publish" at url2),
168+
(Global / credentials) ++= {
169169
val pass = env("PRIVATE_REPO_PASS")
170170
if (pass != "")
171171
List(Credentials("Artifactory Realm", "scala-ci.typesafe.com", "scala-ci", env("PRIVATE_REPO_PASS")))

project/VersionUtil.scala

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ package scala.build
22

33
import sbt._
44
import Keys._
5+
56
import java.util.{Date, Locale, Properties, TimeZone}
6-
import java.io.{File, FileInputStream}
7+
import java.io.{File, FileInputStream, StringWriter}
78
import java.text.SimpleDateFormat
89
import java.time.Instant
910
import java.time.format.DateTimeFormatter
1011
import java.time.temporal.{TemporalAccessor, TemporalQueries, TemporalQuery}
11-
1212
import scala.collection.JavaConverters._
1313
import BuildSettings.autoImport._
1414

@@ -24,9 +24,9 @@ object VersionUtil {
2424

2525
lazy val globalVersionSettings = Seq[Setting[_]](
2626
// Set the version properties globally (they are the same for all projects)
27-
versionProperties in Global := versionPropertiesImpl.value,
27+
(Global / versionProperties) := versionPropertiesImpl.value,
2828
gitProperties := gitPropertiesImpl.value,
29-
version in Global := versionProperties.value.mavenVersion
29+
(Global / version) := versionProperties.value.mavenVersion
3030
)
3131

3232
lazy val generatePropertiesFileSettings = Seq[Setting[_]](
@@ -37,12 +37,12 @@ object VersionUtil {
3737
| __\ \/ /__/ __ |/ /__/ __ |
3838
| /____/\___/_/ |_/____/_/ | |
3939
| |/ %s""".stripMargin.linesIterator.drop(1).map(s => s"${ "%n" }${ s }").mkString,
40-
resourceGenerators in Compile += generateVersionPropertiesFile.map(file => Seq(file)).taskValue,
40+
(Compile / resourceGenerators) += generateVersionPropertiesFile.map(file => Seq(file)).taskValue,
4141
generateVersionPropertiesFile := generateVersionPropertiesFileImpl.value
4242
)
4343

4444
lazy val generateBuildCharacterFileSettings = Seq[Setting[_]](
45-
buildCharacterPropertiesFile := ((baseDirectory in ThisBuild).value / "buildcharacter.properties"),
45+
buildCharacterPropertiesFile := ((ThisBuild / baseDirectory).value / "buildcharacter.properties"),
4646
generateBuildCharacterPropertiesFile := generateBuildCharacterPropertiesFileImpl.value
4747
)
4848

@@ -161,7 +161,7 @@ object VersionUtil {
161161
"copyright.string" -> copyrightString.value,
162162
"shell.welcome" -> shellWelcomeString.value
163163
),
164-
(resourceManaged in Compile).value / s"${thisProject.value.id}.properties")
164+
(Compile / resourceManaged).value / s"${thisProject.value.id}.properties")
165165
}
166166

167167
private lazy val generateBuildCharacterPropertiesFileImpl: Def.Initialize[Task[File]] = Def.task {
@@ -173,13 +173,18 @@ object VersionUtil {
173173
}
174174

175175
private def writeProps(m: Map[String, String], propFile: File): File = {
176-
val props = new Properties
177-
m.foreach { case (k, v) => props.put(k, v) }
178-
// unfortunately, this will write properties in arbitrary order
179-
// this makes it harder to test for stability of generated artifacts
180-
// consider using https://github.com/etiennestuder/java-ordered-properties
181-
// instead of java.util.Properties
182-
IO.write(props, null, propFile)
176+
// Like:
177+
// IO.write(props, null, propFile)
178+
// But with deterministic key ordering and no timestamp
179+
val fullWriter = new StringWriter()
180+
for (k <- m.keySet.toVector.sorted) {
181+
val writer = new StringWriter()
182+
val props = new Properties()
183+
props.put(k, m(k))
184+
props.store(writer, null)
185+
writer.toString.linesIterator.drop(1).foreach{line => fullWriter.write(line); fullWriter.write("\n")}
186+
}
187+
IO.write(propFile, fullWriter.toString)
183188
propFile
184189
}
185190

project/build.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sbt.version=1.3.13
1+
sbt.version=1.5.4

project/plugins.sbt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
scalacOptions ++= Seq("-unchecked", "-feature"/*, "-deprecation", "-Xlint" , "-Xfatal-warnings"*/)
22

3-
libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.3.2"
3+
libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.12.0"
44

5-
libraryDependencies += "org.pantsbuild" % "jarjar" % "1.6.5"
5+
libraryDependencies += "org.pantsbuild" % "jarjar" % "1.7.2"
66

7-
libraryDependencies += "biz.aQute.bnd" % "biz.aQute.bnd" % "2.4.1"
7+
libraryDependencies += "biz.aQute.bnd" % "biz.aQute.bndlib" % "5.3.0"
88

99
enablePlugins(BuildInfoPlugin)
1010

@@ -22,9 +22,9 @@ addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.8.1")
2222

2323
libraryDependencies ++= Seq(
2424
"org.eclipse.jgit" % "org.eclipse.jgit" % "4.6.0.201612231935-r",
25-
"org.slf4j" % "slf4j-nop" % "1.7.23",
25+
"org.slf4j" % "slf4j-nop" % "1.7.31",
2626
"com.googlecode.java-diff-utils" % "diffutils" % "1.3.0"
27-
)
27+
)
2828

2929
concurrentRestrictions in Global := Seq(
3030
Tags.limitAll(1) // workaround for https://github.com/sbt/sbt/issues/2970

scripts/common

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ rm -rf "$WORKSPACE/resolutionScratch_"
1515
mkdir -p "$WORKSPACE/resolutionScratch_"
1616

1717
SBT_CMD=${SBT_CMD-sbt}
18-
SBT_CMD="$SBT_CMD -sbt-version 1.3.13"
18+
SBT_CMD="$SBT_CMD -sbt-version 1.5.4"
1919

2020
# repo to publish builds
2121
integrationRepoUrl=${integrationRepoUrl-"https://scala-ci.typesafe.com/artifactory/scala-integration/"}

0 commit comments

Comments
 (0)