Merge pull request #399 from eed3si9n/wip/gigahorse

Drop OkHttp dependency
This commit is contained in:
eugene yokota 2022-06-12 22:39:37 -04:00 committed by GitHub
commit da80b6a72f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 28 additions and 484 deletions

View File

@ -37,7 +37,7 @@ jobs:
run: |
case ${{ matrix.jobtype }} in
1)
sbt -v -Dfile.encoding=UTF8 scalafmtCheckAll whitesourceCheckPolicies +test +packagedArtifacts
sbt -v -Dfile.encoding=UTF8 scalafmtCheckAll +test +packagedArtifacts
;;
*)
echo unknown jobtype

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ __pycache__
scripted-test/src/sbt-test/*/*/project/build.properties
scripted-test/src/sbt-test/*/*/project/plugins.sbt
metals.sbt

View File

@ -6,7 +6,8 @@ val _ = {
//https://github.com/sbt/contraband/issues/122
sys.props += ("line.separator" -> "\n")
}
Global / semanticdbEnabled := true
Global / semanticdbVersion := "4.5.9"
ThisBuild / version := {
val old = (ThisBuild / version).value
nightlyVersion match {
@ -88,6 +89,7 @@ val mimaSettings = Def settings (
"1.3.0",
"1.4.0",
"1.5.0",
"1.6.0",
) map (
version =>
organization.value %% moduleName.value % version
@ -118,8 +120,7 @@ lazy val lmCore = (project in file("core"))
scalaReflect.value,
scalaCompiler.value,
launcherInterface,
gigahorseOkhttp,
okhttpUrlconnection,
gigahorseApacheHttp,
sjsonnewScalaJson.value % Optional,
scalaTest % Test,
scalaCheck % Test,
@ -260,7 +261,9 @@ lazy val lmCore = (project in file("core"))
"sbt.librarymanagement.ResolverFunctions.validateArtifact"
),
exclude[IncompatibleResultTypeProblem]("sbt.librarymanagement.*.validateProtocol"),
exclude[DirectMissingMethodProblem]("sbt.internal.librarymanagement.cross.CrossVersionUtil.TransitionDottyVersion"),
exclude[DirectMissingMethodProblem](
"sbt.internal.librarymanagement.cross.CrossVersionUtil.TransitionDottyVersion"
),
exclude[DirectMissingMethodProblem]("sbt.librarymanagement.ScalaArtifacts.dottyID"),
exclude[DirectMissingMethodProblem]("sbt.librarymanagement.ScalaArtifacts.DottyIDPrefix"),
exclude[DirectMissingMethodProblem]("sbt.librarymanagement.ScalaArtifacts.toolDependencies*"),
@ -351,6 +354,15 @@ lazy val lmIvy = (project in file("ivy"))
"sbt.internal.librarymanagement.CustomPomParser.versionRangeFlag"
),
exclude[MissingClassProblem]("sbt.internal.librarymanagement.FixedParser*"),
exclude[MissingClassProblem]("sbt.internal.librarymanagement.ivyint.GigahorseUrlHandler*"),
exclude[MissingClassProblem]("sbt.internal.librarymanagement.JavaNetAuthenticator"),
exclude[MissingClassProblem]("sbt.internal.librarymanagement.CustomHttp*"),
exclude[DirectMissingMethodProblem]("sbt.internal.librarymanagement.IvySbt.http"),
exclude[DirectMissingMethodProblem]("sbt.internal.librarymanagement.IvySbt.this"),
exclude[DirectMissingMethodProblem]("sbt.librarymanagement.ivy.IvyPublisher.apply"),
exclude[DirectMissingMethodProblem](
"sbt.librarymanagement.ivy.IvyDependencyResolution.apply"
),
),
)
@ -387,16 +399,5 @@ def customCommands: Seq[Setting[_]] = Seq(
}
)
inThisBuild(
Seq(
whitesourceProduct := "Lightbend Reactive Platform",
whitesourceAggregateProjectName := "sbt-lm-master",
whitesourceAggregateProjectToken := "9bde4ccbaab7401a91f8cda337af84365d379e13abaf473b85cb16e3f5c65cb6",
whitesourceIgnoredScopes += "scalafmt",
whitesourceFailOnError := sys.env.contains("WHITESOURCE_PASSWORD"), // fail if pwd is present
whitesourceForceCheckAllDependencies := true,
)
)
def inCompileAndTest(ss: SettingsDefinition*): Seq[Setting[_]] =
Seq(Compile, Test) flatMap (inConfig(_)(Def.settings(ss: _*)))

View File

@ -1,6 +1,6 @@
package sbt.librarymanagement
import gigahorse._, support.okhttp.Gigahorse
import gigahorse._, support.apachehttp.Gigahorse
import scala.concurrent.duration.DurationInt
object Http {

View File

@ -1,82 +0,0 @@
/*
* Copyright (C) 2013 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sbt.internal.librarymanagement;
import java.io.IOException;
import java.net.Authenticator.RequestorType;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.util.List;
import okhttp3.Authenticator;
import okhttp3.Route;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.HttpUrl;
import okhttp3.Challenge;
import okhttp3.Credentials;
/**
* Adapts java.net.Authenticator to Authenticator. Configure OkHttp to use
* java.net.Authenticator with OkHttpClient.Builder#authenticator or
* OkHttpClient.Builder#proxyAuthenticator(Authenticator).
*/
public final class JavaNetAuthenticator implements Authenticator {
@Override public Request authenticate(Route route, Response response) throws IOException {
List<Challenge> challenges = response.challenges();
Request request = response.request();
HttpUrl url = request.url();
boolean proxyAuthorization = response.code() == 407;
Proxy proxy = null;
if (route != null) {
proxy = route.proxy();
}
for (int i = 0, size = challenges.size(); i < size; i++) {
Challenge challenge = challenges.get(i);
if (!"Basic".equalsIgnoreCase(challenge.scheme())) continue;
PasswordAuthentication auth;
if (proxyAuthorization) {
InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
auth = java.net.Authenticator.requestPasswordAuthentication(
proxyAddress.getHostName(), getConnectToInetAddress(proxy, url), proxyAddress.getPort(),
url.scheme(), challenge.realm(), challenge.scheme(), url.url(),
RequestorType.PROXY);
} else {
auth = java.net.Authenticator.requestPasswordAuthentication(
url.host(), getConnectToInetAddress(proxy, url), url.port(), url.scheme(),
challenge.realm(), challenge.scheme(), url.url(), RequestorType.SERVER);
}
if (auth != null) {
String credential = Credentials.basic(auth.getUserName(), new String(auth.getPassword()));
return request.newBuilder()
.header(proxyAuthorization ? "Proxy-Authorization" : "Authorization", credential)
.build();
}
}
return null; // No challenges were satisfied!
}
private InetAddress getConnectToInetAddress(Proxy proxy, HttpUrl url) throws IOException {
return (proxy != null && proxy.type() != Proxy.Type.DIRECT)
? ((InetSocketAddress) proxy.address()).getAddress()
: InetAddress.getByName(url.host());
}
}

View File

@ -1,21 +0,0 @@
package sbt.internal.librarymanagement
import gigahorse.HttpClient
import okhttp3.{ JavaNetAuthenticator => _, _ }
import sbt.librarymanagement.Http
object CustomHttp {
private[this] def http0: HttpClient = Http.http
private[sbt] def defaultHttpClientBuilder: OkHttpClient.Builder = {
http0
.underlying[OkHttpClient]
.newBuilder()
.authenticator(new sbt.internal.librarymanagement.JavaNetAuthenticator)
.followRedirects(true)
.followSslRedirects(true)
}
private[sbt] lazy val defaultHttpClient: OkHttpClient =
defaultHttpClientBuilder.build
}

View File

@ -7,7 +7,6 @@ import java.io.File
import java.net.URI
import java.util.concurrent.Callable
import okhttp3.OkHttpClient
import org.apache.ivy.Ivy
import org.apache.ivy.core.IvyPatternHelper
import org.apache.ivy.core.cache.{ CacheMetadataOptions, DefaultRepositoryCacheManager }
@ -50,17 +49,13 @@ import ivyint.{
CachedResolutionResolveEngine,
ParallelResolveEngine,
SbtDefaultDependencyDescriptor,
GigahorseUrlHandler
}
import sjsonnew.JsonFormat
import sjsonnew.support.murmurhash.Hasher
final class IvySbt(
val configuration: IvyConfiguration,
val http: OkHttpClient
) { self =>
def this(configuration: IvyConfiguration) = this(configuration, CustomHttp.defaultHttpClient)
/*
* ========== Configuration/Setup ============
* This part configures the Ivy instance by first creating the logger interface to ivy, then IvySettings, and then the Ivy instance.
@ -90,7 +85,6 @@ final class IvySbt(
}
private lazy val basicUrlHandler: URLHandler = new BasicURLHandler
private lazy val gigahorseUrlHandler: URLHandler = new GigahorseUrlHandler(http)
private lazy val settings: IvySettings = {
val dispatcher: URLHandlerDispatcher = URLHandlerRegistry.getDefault match {
@ -106,8 +100,8 @@ final class IvySbt(
disp
}
val urlHandler: URLHandler =
if (configuration.updateOptions.gigahorse) gigahorseUrlHandler else basicUrlHandler
// Ignore configuration.updateOptions.gigahorse due to sbt/sbt#6912
val urlHandler: URLHandler = basicUrlHandler
// Only set the urlHandler for the http/https protocols so we do not conflict with any other plugins
// that might register other protocol handlers.

View File

@ -112,7 +112,7 @@ class IvyCache(val ivyHome: Option[File]) {
.withResolvers(Vector(local))
.withLock(lock)
.withLog(log)
(new IvySbt(conf, CustomHttp.defaultHttpClient), local)
(new IvySbt(conf), local)
}
/** Creates a default jar artifact based on the given ID.*/

View File

@ -1,339 +0,0 @@
package sbt.internal.librarymanagement
package ivyint
import java.net.{ URL, UnknownHostException }
import java.io._
import scala.util.control.NonFatal
import okhttp3.{ MediaType, Request, RequestBody }
import okhttp3.internal.http.HttpDate
import okhttp3.{ JavaNetAuthenticator => _, _ }
import okio._
import org.apache.ivy.util.{ CopyProgressEvent, CopyProgressListener, Message }
import org.apache.ivy.util.url.{ AbstractURLHandler, BasicURLHandler, IvyAuthenticator, URLHandler }
import org.apache.ivy.util.url.URLHandler._
import sbt.io.IO
// Copied from Ivy's BasicURLHandler.
class GigahorseUrlHandler(http: OkHttpClient) extends AbstractURLHandler {
import GigahorseUrlHandler._
/**
* Returns the URLInfo of the given url or a #UNAVAILABLE instance,
* if the url is not reachable.
*/
def getURLInfo(url: URL): URLInfo = getURLInfo(url, 0)
/**
* Returns the URLInfo of the given url or a #UNAVAILABLE instance,
* if the url is not reachable.
*/
def getURLInfo(url0: URL, timeout: Int): URLInfo = {
// Install the ErrorMessageAuthenticator
if ("http" == url0.getProtocol || "https" == url0.getProtocol) {
IvyAuthenticator.install()
ErrorMessageAuthenticator.install()
}
val url = normalizeToURL(url0)
val request = new Request.Builder()
.url(url)
if (getRequestMethod == URLHandler.REQUEST_METHOD_HEAD) request.head() else request.get()
val response = http.newCall(request.build()).execute()
try {
val infoOption = try {
if (checkStatusCode(url, response)) {
val bodyCharset =
BasicURLHandler.getCharSetFromContentType(
Option(response.body().contentType()).map(_.toString).orNull
)
Some(
new SbtUrlInfo(
true,
response.body().contentLength(),
lastModifiedTimestamp(response),
bodyCharset
)
)
} else None
//
// Commented out for now - can potentially be used for non HTTP urls
//
// val contentLength: Long = con.getContentLengthLong
// if (contentLength <= 0) None
// else {
// // TODO: not HTTP... maybe we *don't* want to default to ISO-8559-1 here?
// val bodyCharset = BasicURLHandler.getCharSetFromContentType(con.getContentType)
// Some(new SbtUrlInfo(true, contentLength, con.getLastModified(), bodyCharset))
// }
} catch {
case e: UnknownHostException =>
Message.warn("Host " + e.getMessage + " not found. url=" + url)
Message.info(
"You probably access the destination server through "
+ "a proxy server that is not well configured."
)
None
case e: IOException =>
Message.error("Server access Error: " + e.getMessage + " url=" + url)
None
}
infoOption.getOrElse(UNAVAILABLE)
} finally {
response.close()
}
}
//The caller of this *MUST* call Response.close()
private def getUrl(url0: URL): okhttp3.Response = {
// Install the ErrorMessageAuthenticator
if ("http" == url0.getProtocol || "https" == url0.getProtocol) {
IvyAuthenticator.install()
ErrorMessageAuthenticator.install()
}
val url = normalizeToURL(url0)
val request = new Request.Builder()
.url(url)
.get()
.build()
val response = http.newCall(request).execute()
try {
if (!checkStatusCode(url, response)) {
throw new IOException(
"The HTTP response code for " + url + " did not indicate a success."
+ " See log for more detail."
)
}
response
} catch {
case NonFatal(e) =>
//ensure the response gets closed if there's an error
response.close()
throw e
}
}
def openStream(url: URL): InputStream = {
//It's assumed that the caller of this will call close() on the supplied inputstream,
// thus closing the OkHTTP request
getUrl(url).body().byteStream()
}
def download(url: URL, dest: File, l: CopyProgressListener): Unit = {
val response = getUrl(url)
try {
if (l != null) {
l.start(new CopyProgressEvent())
}
val sink = Okio.buffer(Okio.sink(dest))
try {
sink.writeAll(response.body().source())
sink.flush()
} finally {
sink.close()
}
val contentLength = response.body().contentLength()
if (contentLength != -1 && dest.length != contentLength) {
IO.delete(dest)
throw new IOException(
"Downloaded file size doesn't match expected Content Length for " + url
+ ". Please retry."
)
}
val lastModified = lastModifiedTimestamp(response)
if (lastModified > 0) {
IO.setModifiedTimeOrFalse(dest, lastModified)
}
if (l != null) {
l.end(new CopyProgressEvent(EmptyBuffer, contentLength))
}
} finally {
response.close()
}
}
def upload(source: File, dest0: URL, l: CopyProgressListener): Unit = {
if (("http" != dest0.getProtocol) && ("https" != dest0.getProtocol)) {
throw new UnsupportedOperationException("URL repository only support HTTP PUT at the moment")
}
IvyAuthenticator.install()
ErrorMessageAuthenticator.install()
val dest = normalizeToURL(dest0)
val body = RequestBody.create(MediaType.parse("application/octet-stream"), source)
val request = new Request.Builder()
.url(dest)
.put(body)
.build()
if (l != null) {
l.start(new CopyProgressEvent())
}
val response = http.newCall(request).execute()
try {
if (l != null) {
l.end(new CopyProgressEvent(EmptyBuffer, source.length()))
}
validatePutStatusCode(dest, response)
} finally {
response.close()
}
}
private val ErrorBodyTruncateLen = 512 // in case some bad service returns files rather than messages in error bodies
private val DefaultErrorCharset = java.nio.charset.StandardCharsets.UTF_8
// neurotic resource managemement...
// we could use this elsewhere in the class too
private def borrow[S <: AutoCloseable, T](rsrc: => S)(op: S => T): T = {
val r = rsrc
val out = {
try {
op(r)
} catch {
case NonFatal(t) => {
try {
r.close()
} catch {
case NonFatal(ct) => t.addSuppressed(ct)
}
throw t
}
}
}
r.close()
out
}
// this is perhaps overly cautious, but oh well
private def readTruncated(byteStream: InputStream): Option[(Array[Byte], Boolean)] = {
borrow(byteStream) { is =>
borrow(new ByteArrayOutputStream(ErrorBodyTruncateLen)) { os =>
var count = 0
var b = is.read()
var truncated = false
while (!truncated && b >= 0) {
if (count >= ErrorBodyTruncateLen) {
truncated = true
} else {
os.write(b)
count += 1
b = is.read()
}
}
if (count > 0) {
Some((os.toByteArray, truncated))
} else {
None
}
}
}
}
/*
* Supplements the IOException emitted on a bad status code by our inherited validatePutStatusCode(...)
* method with any message that might be present in an error response body.
*
* after calling this method, the object given as the response parameter must be reliably closed.
*/
private def validatePutStatusCode(dest: URL, response: Response): Unit = {
try {
validatePutStatusCode(dest, response.code(), response.message())
} catch {
case ioe: IOException => {
val mbBodyMessage = {
for {
body <- Option(response.body())
is <- Option(body.byteStream)
(bytes, truncated) <- readTruncated(is)
charset <- Option(body.contentType()).map(_.charset(DefaultErrorCharset)) orElse Some(
DefaultErrorCharset
)
} yield {
val raw = new String(bytes, charset)
if (truncated) raw + "..." else raw
}
}
mbBodyMessage match {
case Some(bodyMessage) => { // reconstruct the IOException
val newMessage = ioe.getMessage() + s"; Response Body: ${bodyMessage}"
val reconstructed = new IOException(newMessage, ioe.getCause())
reconstructed.setStackTrace(ioe.getStackTrace())
throw reconstructed
}
case None => {
throw ioe
}
}
}
}
}
}
object GigahorseUrlHandler {
// This is requires to access the constructor of URLInfo.
private[sbt] class SbtUrlInfo(
available: Boolean,
contentLength: Long,
lastModified: Long,
bodyCharset: String
) extends URLInfo(available, contentLength, lastModified, bodyCharset) {
def this(available: Boolean, contentLength: Long, lastModified: Long) = {
this(available, contentLength, lastModified, null)
}
}
private val EmptyBuffer: Array[Byte] = new Array[Byte](0)
private def checkStatusCode(url: URL, response: Response): Boolean =
response.code() match {
case 200 => true
case 204 if "HEAD" == response.request().method() => true
case status =>
Message.debug("HTTP response status: " + status + " url=" + url)
if (status == 407 /* PROXY_AUTHENTICATION_REQUIRED */ ) {
Message.warn("Your proxy requires authentication.")
} else if (status == 401) {
Message.warn(
"CLIENT ERROR: 401 Unauthorized. Check your resolvers username and password."
)
} else if (String.valueOf(status).startsWith("4")) {
Message.verbose("CLIENT ERROR: " + response.message() + " url=" + url)
} else if (String.valueOf(status).startsWith("5")) {
Message.error("SERVER ERROR: " + response.message() + " url=" + url)
}
false
}
private def lastModifiedTimestamp(response: Response): Long = {
val lastModifiedDate =
Option(response.headers().get("Last-Modified")).flatMap { headerValue =>
Option(HttpDate.parse(headerValue))
}
lastModifiedDate.map(_.getTime).getOrElse(0)
}
}

View File

@ -2,7 +2,6 @@ package sbt
package librarymanagement
package ivy
import okhttp3.OkHttpClient
import sbt.internal.librarymanagement._
import sbt.util.Logger
@ -30,8 +29,5 @@ class IvyDependencyResolution private[sbt] (val ivySbt: IvySbt)
object IvyDependencyResolution {
def apply(ivyConfiguration: IvyConfiguration): DependencyResolution =
apply(ivyConfiguration, CustomHttp.defaultHttpClient)
def apply(ivyConfiguration: IvyConfiguration, http: OkHttpClient): DependencyResolution =
DependencyResolution(new IvyDependencyResolution(new IvySbt(ivyConfiguration, http)))
DependencyResolution(new IvyDependencyResolution(new IvySbt(ivyConfiguration)))
}

View File

@ -2,7 +2,6 @@ package sbt
package librarymanagement
package ivy
import okhttp3.OkHttpClient
import sbt.internal.librarymanagement._
import sbt.util.Logger
import java.io.File
@ -36,8 +35,5 @@ class IvyPublisher private[sbt] (val ivySbt: IvySbt) extends PublisherInterface
object IvyPublisher {
def apply(ivyConfiguration: IvyConfiguration): Publisher =
apply(ivyConfiguration, CustomHttp.defaultHttpClient)
def apply(ivyConfiguration: IvyConfiguration, http: OkHttpClient): Publisher =
Publisher(new IvyPublisher(new IvySbt(ivyConfiguration, http)))
Publisher(new IvyPublisher(new IvySbt(ivyConfiguration)))
}

View File

@ -3,8 +3,8 @@ import Keys._
import sbt.contraband.ContrabandPlugin.autoImport._
object Dependencies {
val scala212 = "2.12.15"
val scala213 = "2.13.6"
val scala212 = "2.12.16"
val scala213 = "2.13.8"
def nightlyVersion: Option[String] =
sys.env.get("BUILD_VERSION") orElse sys.props.get("sbt.build.version")
@ -61,6 +61,5 @@ object Dependencies {
val sjsonnewScalaJson = Def.setting {
"com.eed3si9n" %% "sjson-new-scalajson" % contrabandSjsonNewVersion.value
}
val gigahorseOkhttp = "com.eed3si9n" %% "gigahorse-okhttp" % "0.5.0"
val okhttpUrlconnection = "com.squareup.okhttp3" % "okhttp-urlconnection" % "3.7.0"
val gigahorseApacheHttp = "com.eed3si9n" %% "gigahorse-apache-http" % "0.7.0"
}

View File

@ -1 +1 @@
sbt.version=1.5.1
sbt.version=1.6.2

View File

@ -3,6 +3,5 @@ addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.8.1")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.0.2")
addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.5.1")
addSbtPlugin("com.lightbend" % "sbt-whitesource" % "0.1.14")
scalacOptions += "-language:postfixOps"