Skip to content

Commit aaf7bc0

Browse files
authored
Merge pull request scala#5190 from szeiger/wip/validate-test-use-sbt
Use sbt for PR validation [ci: last-only]
2 parents 91b6944 + c2c08a4 commit aaf7bc0

21 files changed

+382
-92
lines changed

build.sbt

Lines changed: 132 additions & 55 deletions
Large diffs are not rendered by default.

project/BuildSettings.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import sbt._
2+
3+
/** This object defines keys that should be visible with an unqualified name in all .sbt files and the command line */
4+
object BuildSettings extends AutoPlugin {
5+
object autoImport {
6+
lazy val antStyle = settingKey[Boolean]("Use ant-style incremental builds instead of name-hashing")
7+
lazy val baseVersion = settingKey[String]("The base version number from which all others are derived")
8+
lazy val baseVersionSuffix = settingKey[String]("Identifies the kind of version to build")
9+
lazy val mimaReferenceVersion = settingKey[Option[String]]("Scala version number to run MiMa against")
10+
}
11+
}

project/MiMa.scala

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// It would be nice to use sbt-mima-plugin here, but the plugin is missing
2+
// at least two features we need:
3+
// * ability to run MiMa twice, swapping `curr` and `prev`, to detect
4+
// both forwards and backwards incompatibilities (possibly fixed as of
5+
// https://github.com/typesafehub/migration-manager/commit/2844ffa48b6d2255aa64bd687703aec21dadd55e)
6+
// * ability to pass a filter file (https://github.com/typesafehub/migration-manager/issues/102)
7+
// So we invoke the MiMa CLI directly; it's also what the Ant build did.
8+
9+
import sbt._
10+
import sbt.Keys._
11+
import BuildSettings.autoImport._
12+
13+
object MiMa {
14+
lazy val mima =
15+
taskKey[Unit]("run Migration Manager to detect binary incompatibilities")
16+
17+
lazy val settings =
18+
Seq(
19+
mima := {
20+
val log = streams.value.log
21+
mimaReferenceVersion.value.fold {
22+
log.info(s"No reference version defined - skipping binary compatibility checks")
23+
} { refVersion =>
24+
def runOnce(prev: java.io.File, curr: java.io.File, isForward: Boolean): Unit = {
25+
val direction = if (isForward) "forward" else "backward"
26+
log.info(s"Checking $direction binary compatibility")
27+
log.debug(s"prev = $prev, curr = $curr")
28+
runMima(
29+
prev = if (isForward) curr else prev,
30+
curr = if (isForward) prev else curr,
31+
// TODO: it would be nicer if each subproject had its own whitelist, but for now
32+
// for compatibility with how Ant did things, there's just one at the root.
33+
// once Ant is gone we'd be free to split it up.
34+
filter = (baseDirectory in ThisBuild).value / s"bincompat-$direction.whitelist.conf",
35+
log)
36+
}
37+
val artifact =
38+
getPreviousArtifact(
39+
"org.scala-lang" % s"${name.value}" % refVersion,
40+
ivySbt.value, streams.value)
41+
for (isForward <- Seq(false, true))
42+
runOnce(artifact, (packageBin in Compile).value, isForward)
43+
}
44+
}
45+
)
46+
47+
def runMima(prev: java.io.File, curr: java.io.File, filter: java.io.File, log: Logger): Unit = {
48+
val args = Array(
49+
"--prev", prev.getAbsolutePath,
50+
"--curr", curr.getAbsolutePath,
51+
"--filters", filter.getAbsolutePath,
52+
"--generate-filters"
53+
)
54+
val exitCode = TrapExit(com.typesafe.tools.mima.cli.Main.main(args), log)
55+
if (exitCode != 0)
56+
throw new RuntimeException(s"MiMa failed with exit code $exitCode")
57+
}
58+
59+
// cribbed from https://github.com/typesafehub/migration-manager/blob/master/sbtplugin/src/main/scala/com/typesafe/tools/mima/plugin/SbtMima.scala
60+
def getPreviousArtifact(m: ModuleID, ivy: IvySbt, s: TaskStreams): File = {
61+
val moduleSettings = InlineConfiguration(
62+
"dummy" % "test" % "version",
63+
ModuleInfo("dummy-test-project-for-resolving"),
64+
dependencies = Seq(m))
65+
val module = new ivy.Module(moduleSettings)
66+
val report = Deprecated.Inner.ivyUpdate(ivy)(module, s)
67+
val optFile = (for {
68+
config <- report.configurations
69+
module <- config.modules
70+
(artifact, file) <- module.artifacts
71+
// TODO - Hardcode this?
72+
if artifact.name == m.name
73+
} yield file).headOption
74+
optFile getOrElse sys.error("Could not resolve previous artifact: " + m)
75+
}
76+
77+
}
78+
79+
// use the SI-7934 workaround to silence a deprecation warning on an sbt API
80+
// we have no choice but to call. on the lack of any suitable alternative,
81+
// see https://gitter.im/sbt/sbt-dev?at=5616e2681b0e279854bd74a4 :
82+
// "it's my intention to eventually come up with a public API" says Eugene Y
83+
object Deprecated {
84+
@deprecated("", "") class Inner {
85+
def ivyUpdate(ivy: IvySbt)(module: ivy.Module, s: TaskStreams) =
86+
IvyActions.update(
87+
module,
88+
new UpdateConfiguration(
89+
retrieve = None,
90+
missingOk = false,
91+
logging = UpdateLogging.DownloadOnly),
92+
s.log)
93+
}
94+
object Inner extends Inner
95+
}

project/Osgi.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ object Osgi {
2828
"Bundle-Name" -> bundleName.value,
2929
"Bundle-SymbolicName" -> bundleSymbolicName.value,
3030
"ver" -> v,
31-
"Export-Package" -> ("*;version=${ver}"),
32-
"Import-Package" -> ("scala.*;version=\"${range;[==,=+);${ver}}\",*"),
31+
"Export-Package" -> "*;version=${ver};-split-package:=merge-first",
32+
"Import-Package" -> "scala.*;version=\"${range;[==,=+);${ver}}\",*",
3333
"Bundle-Version" -> v,
3434
"Bundle-RequiredExecutionEnvironment" -> "JavaSE-1.6, JavaSE-1.7",
3535
"-eclipse" -> "false"

project/Quiet.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,4 @@ object Quiet {
2828
case x => x
2929
}
3030
}
31-
32-
def silenceIvyUpdateInfoLogging = logLevel in update := Level.Warn
3331
}

project/ScalaTool.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ case class ScalaTool(mainClass: String,
2727
} else classpath.mkString(":").replace('\\', '/').replaceAll(varRegex, """\${$1}""")
2828

2929
val variables = Map(
30-
("@@" -> "@"), // for backwards compatibility
31-
("@class@" -> mainClass),
32-
("@properties@" -> (properties map { case (k, v) => s"""-D$k="$v""""} mkString " ")),
33-
("@javaflags@" -> javaOpts),
34-
("@toolflags@" -> toolFlags),
35-
("@classpath@" -> platformClasspath)
30+
"@@" -> "@", // for backwards compatibility
31+
"@class@" -> mainClass,
32+
"@properties@" -> (properties map { case (k, v) => s"""-D$k="$v""""} mkString " "),
33+
"@javaflags@" -> javaOpts,
34+
"@toolflags@" -> toolFlags,
35+
"@classpath@" -> platformClasspath
3636
)
3737

3838
val (from, to) = variables.unzip

project/ScriptCommands.scala

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
11
import sbt._
22
import Keys._
3-
import complete.DefaultParsers._
3+
import BuildSettings.autoImport._
44

55
/** Custom commands for use by the Jenkins scripts. This keeps the surface area and call syntax small. */
66
object ScriptCommands {
7-
def all = Seq(setupPublishCore)
7+
def all = Seq(setupPublishCore, setupValidateTest)
88

99
/** Set up the environment for `validate/publish-core`. The argument is the Artifactory snapshot repository URL. */
1010
def setupPublishCore = Command.single("setupPublishCore") { case (state, url) =>
1111
Project.extract(state).append(Seq(
12-
VersionUtil.baseVersionSuffix in Global := "SHA-SNAPSHOT",
12+
baseVersionSuffix in Global := "SHA-SNAPSHOT",
1313
// Append build.timestamp to Artifactory URL to get consistent build numbers (see https://github.com/sbt/sbt/issues/2088):
1414
publishTo in Global := Some("scala-pr" at url.replaceAll("/$", "") + ";build.timestamp=" + System.currentTimeMillis),
1515
publishArtifact in (Compile, packageDoc) in ThisBuild := false,
16-
scalacOptions in Compile in ThisBuild += "-optimise"
16+
scalacOptions in Compile in ThisBuild += "-optimise",
17+
logLevel in ThisBuild := Level.Info,
18+
logLevel in update in ThisBuild := Level.Warn
1719
), state)
1820
}
21+
22+
/** Set up the environment for `validate/test`. The argument is the Artifactory snapshot repository URL. */
23+
def setupValidateTest = Command.single("setupValidateTest") { case (state, url) =>
24+
//TODO When ant is gone, pass starr version as an argument to this command instead of using version.properties
25+
Project.extract(state).append(Seq(
26+
resolvers in Global += "scala-pr" at url,
27+
scalacOptions in Compile in ThisBuild += "-optimise",
28+
logLevel in ThisBuild := Level.Info,
29+
logLevel in update in ThisBuild := Level.Warn
30+
), state)
31+
}
1932
}

project/VersionUtil.scala

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import sbt._
22
import Keys._
33
import java.util.Properties
4-
import java.io.FileInputStream
4+
import java.io.{File, FileInputStream}
55
import scala.collection.JavaConverters._
6+
import BuildSettings.autoImport._
67

78
object VersionUtil {
8-
lazy val baseVersion = settingKey[String]("The base version number from which all others are derived")
9-
lazy val baseVersionSuffix = settingKey[String]("Identifies the kind of version to build")
109
lazy val copyrightString = settingKey[String]("Copyright string.")
1110
lazy val versionProperties = settingKey[Versions]("Version properties.")
1211
lazy val generateVersionPropertiesFile = taskKey[File]("Generating version properties file.")
@@ -123,4 +122,33 @@ object VersionUtil {
123122
/** Get a subproject version number from `versionProps` */
124123
def versionNumber(name: String): String =
125124
versionProps(s"$name.version.number")
125+
126+
/** Build a dependency to a Scala module with the given group and artifact ID */
127+
def scalaDep(group: String, artifact: String, versionProp: String = null, scope: String = null, compatibility: String = "binary") = {
128+
val vp = if(versionProp eq null) artifact else versionProp
129+
val m = group % (artifact + "_" + versionProps(s"scala.$compatibility.version")) % versionNumber(vp)
130+
val m2 = if(scope eq null) m else m % scope
131+
// exclusion of the scala-library transitive dependency avoids eviction warnings during `update`:
132+
m2.exclude("org.scala-lang", "*")
133+
}
134+
135+
private def bootstrapOrganization(path: String) =
136+
"org.scala-lang.scala-sha-bootstrap." + path.replace('/', '.')
137+
138+
/** Build a dependency to a JAR file in the bootstrap repository */
139+
def bootstrapDep(baseDir: File, path: String, libName: String): ModuleID = {
140+
val sha = IO.read(baseDir / path / s"$libName.jar.desired.sha1").split(' ')(0)
141+
bootstrapOrganization(path) % libName % sha from
142+
s"https://dl.bintray.com/typesafe/scala-sha-bootstrap/org/scala-lang/bootstrap/$sha/$path/$libName.jar"
143+
}
144+
145+
/** Copy a boostrap dependency JAR that is on the classpath to a file */
146+
def copyBootstrapJar(cp: Seq[Attributed[File]], baseDir: File, path: String, libName: String): Unit = {
147+
val org = bootstrapOrganization(path)
148+
val resolved = cp.find { a =>
149+
val mod = a.get(moduleID.key)
150+
mod.map(_.organization) == Some(org) && mod.map(_.name) == Some(libName)
151+
}.map(_.data).get
152+
IO.copyFile(resolved, baseDir / path / s"$libName.jar")
153+
}
126154
}

project/plugins.sbt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
scalacOptions ++= Seq("-unchecked", "-feature", /*"-deprecation",*/
2+
"-Xlint" /*, "-Xfatal-warnings"*/)
3+
14
libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.3.2"
25

36
libraryDependencies += "org.pantsbuild" % "jarjar" % "1.6.3"
@@ -15,3 +18,5 @@ buildClasspath := (externalDependencyClasspath in Compile).value.map(_.data).mkS
1518
buildInfoKeys := Seq[BuildInfoKey](buildClasspath)
1619

1720
buildInfoPackage := "scalabuild"
21+
22+
libraryDependencies += "com.typesafe" %% "mima-reporter" % "0.1.8"

scripts/jobs/validate/publish-core

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ case $prDryRun in
1616
;;
1717
*)
1818
echo ">>> Getting Scala version number."
19-
$SBT_CMD "setupPublishCore $prRepoUrl" generateBuildCharacterPropertiesFile
19+
$SBT_CMD --warn "setupPublishCore $prRepoUrl" generateBuildCharacterPropertiesFile
2020
parseScalaProperties buildcharacter.properties # produce maven_version_number
2121

2222
echo ">>> Checking availability of Scala ${maven_version_number} in $prRepoUrl."
@@ -27,7 +27,7 @@ case $prDryRun in
2727
if $libraryAvailable && $reflectAvailable && $compilerAvailable; then
2828
echo "Scala core already built!"
2929
else
30-
$SBT_CMD "setupPublishCore $prRepoUrl" $antBuildArgs publish
30+
$SBT_CMD --warn "setupPublishCore $prRepoUrl" publish
3131
fi
3232

3333
mv buildcharacter.properties jenkins.properties # parsed by the jenkins job

scripts/jobs/validate/test

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,36 @@
1-
#!/bin/bash -e
1+
#!/bin/bash -e -v -x
2+
3+
baseDir=${WORKSPACE-`pwd`}
4+
scriptsDir="$baseDir/scripts"
5+
. $scriptsDir/common
26

37
case $prDryRun in
8+
49
yep)
510
echo "DRY RUN"
611
;;
12+
713
*)
8-
./pull-binary-libs.sh
914

1015
# build quick using STARR built upstream, as specified by scalaVersion
11-
# (in that sense it's locker, since it was built with starr by that upstream job)
12-
ant -Dstarr.version=$scalaVersion \
13-
-Dscalac.args.optimise=-optimise \
14-
-Dlocker.skip=1 -Dextra.repo.url=$prRepoUrl \
15-
$testExtraArgs ${testTarget-test.core docs.done}
16+
# (in that sense it's locker, since it was built with starr by that upstream job);
17+
# and run JUnit tests, partest, OSGi tests, MiMa and scaladoc
18+
$SBT_CMD \
19+
-Dstarr.version=$scalaVersion \
20+
--warn \
21+
"setupValidateTest $prRepoUrl" \
22+
$testExtraArgs \
23+
"test" \
24+
"partest run pos neg jvm" \
25+
"partest res scalap specialized scalacheck" \
26+
"partest instrumented presentation" \
27+
"partest --srcpath scaladoc" \
28+
osgiTestFelix/test \
29+
osgiTestEclipse/test \
30+
library/mima \
31+
reflect/mima \
32+
doc
33+
1634
;;
17-
esac
35+
36+
esac

src/scaladoc/scala/tools/nsc/doc/model/MemberLookup.scala

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,22 @@ trait MemberLookup extends base.MemberLookupBase {
4444
/* Get package object which has associatedFile ne null */
4545
sym.info.member(newTermName("package"))
4646
else sym
47-
Option(sym1.associatedFile) flatMap (_.underlyingSource) flatMap { src =>
48-
val path = src.canonicalPath
47+
def classpathEntryFor(s: Symbol): Option[String] = {
48+
Option(s.associatedFile).flatMap(_.underlyingSource).map { src =>
49+
val path = src.canonicalPath
50+
if(path.endsWith(".class")) { // Individual class file -> Classpath entry is root dir
51+
var nesting = s.ownerChain.count(_.hasPackageFlag)
52+
if(nesting > 0) {
53+
val p = 0.until(nesting).foldLeft(src) {
54+
case (null, _) => null
55+
case (f, _) => f.container
56+
}
57+
if(p eq null) path else p.canonicalPath
58+
} else path
59+
} else path // JAR file (and fallback option)
60+
}
61+
}
62+
classpathEntryFor(sym1) flatMap { path =>
4963
settings.extUrlMapping get path map { url =>
5064
LinkToExternal(name, url + "#" + name)
5165
}
File renamed without changes.
File renamed without changes.

test/osgi/src/logback.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<configuration>
2+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
3+
<encoder>
4+
<pattern>*** \(%logger{30}\)%green(%X{debugId}) %msg%n</pattern>
5+
</encoder>
6+
</appender>
7+
<root level="${log.root:-warn}">
8+
<appender-ref ref="STDOUT" />
9+
</root>
10+
</configuration>

test/scaladoc/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.log
2+
*.obj/

test/scaladoc/run/SI-191.scala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,14 @@ object Test extends ScaladocModelTest {
3333
def scalaURL = "http://bog.us"
3434

3535
override def scaladocSettings = {
36-
val scalaLibUri = getClass.getClassLoader.getResource("scala/Function1.class").getPath.split("!")(0)
37-
val scalaLibPath = new URI(scalaLibUri).getPath
38-
val externalArg = s"$scalaLibPath#$scalaURL"
39-
"-no-link-warnings -doc-external-doc " + externalArg
36+
val samplePath = getClass.getClassLoader.getResource("scala/Function1.class").getPath
37+
val scalaLibPath = if(samplePath.contains("!")) { // in scala-library.jar
38+
val scalaLibUri = samplePath.split("!")(0)
39+
new URI(scalaLibUri).getPath
40+
} else { // individual class files on disk
41+
samplePath.replace('\\', '/').dropRight("scala/Function1.class".length)
42+
}
43+
s"-no-link-warnings -doc-external-doc $scalaLibPath#$scalaURL"
4044
}
4145

4246
def testModel(rootPackage: Package) {

test/scaladoc/run/t8557.scala

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import java.net.URI
2+
13
import scala.tools.nsc.doc.base._
24
import scala.tools.nsc.doc.model._
35
import scala.tools.partest.ScaladocModelTest
@@ -15,10 +17,22 @@ object Test extends ScaladocModelTest {
1517
class A
1618
"""
1719

20+
def scalaURL = "http://www.scala-lang.org/api/current/"
21+
1822
// a non-canonical path to scala-library.jar should still work
19-
// this is a bit fragile (depends on the current directory being the root of the repo ;
20-
// ant & partest seem to do that properly)
21-
def scaladocSettings = "-doc-external-doc build/pack/bin/../lib/scala-library.jar#http://www.scala-lang.org/api/current/"
23+
override def scaladocSettings = {
24+
val samplePath = getClass.getClassLoader.getResource("scala/Function1.class").getPath.replace('\\', '/')
25+
val scalaLibPath = if(samplePath.contains("!")) { // in scala-library.jar
26+
val scalaLibUri = samplePath.split("!")(0)
27+
val p = new URI(scalaLibUri).getPath
28+
// this is a bit fragile (depends on the scala library being in build/pack as produced by ant)
29+
p.replace("/pack/lib/scala-library.jar", "/pack/bin/../lib/scala-library.jar")
30+
} else { // individual class files on disk
31+
val p = samplePath.dropRight("scala/Function1.class".length + 1)
32+
p + "/.." + p.takeRight(p.length - p.lastIndexOf('/'))
33+
}
34+
s"-doc-external-doc $scalaLibPath#$scalaURL"
35+
}
2236

2337
def testModel(rootPackage: Package) = {
2438
// get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s))

0 commit comments

Comments
 (0)