mirror of https://github.com/sbt/sbt.git
Merge pull request #3909 from eed3si9n/wip/servertest
Starting server testing as a unit test
This commit is contained in:
commit
b228af3cf1
|
|
@ -4,9 +4,8 @@ init:
|
|||
- git config --global core.autocrlf input
|
||||
|
||||
install:
|
||||
- cinst jdk8 -params 'installdir=C:\\jdk8'
|
||||
- SET JAVA_HOME=C:\jdk8
|
||||
- SET PATH=C:\jdk8\bin;%PATH%
|
||||
- SET JAVA_HOME=C:\Program Files\Java\jdk1.8.0
|
||||
- SET PATH=%JAVA_HOME%\bin;%PATH%
|
||||
|
||||
- ps: |
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
|
|
@ -20,4 +19,8 @@ install:
|
|||
- SET PATH=C:\sbt\sbt\bin;%PATH%
|
||||
- SET SBT_OPTS=-XX:MaxPermSize=2g -Xmx4g -Dfile.encoding=UTF8
|
||||
test_script:
|
||||
- sbt "scripted actions/* server/*"
|
||||
- sbt "scripted actions/*" "testOnly sbt.ServerSpec"
|
||||
|
||||
cache:
|
||||
- '%USERPROFILE%\.ivy2\cache'
|
||||
- '%USERPROFILE%\.sbt'
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ env:
|
|||
- SBT_CMD="scripted dependency-management/*4of4"
|
||||
- SBT_CMD="scripted java/* package/* reporter/* run/* project-load/*"
|
||||
- SBT_CMD="scripted project/*1of2"
|
||||
- SBT_CMD="scripted project/*2of2 server/*"
|
||||
- SBT_CMD="scripted project/*2of2"
|
||||
- SBT_CMD="scripted source-dependencies/*1of3"
|
||||
- SBT_CMD="scripted source-dependencies/*2of3"
|
||||
- SBT_CMD="scripted source-dependencies/*3of3"
|
||||
|
|
|
|||
|
|
@ -318,12 +318,13 @@ lazy val actionsProj = (project in file("main-actions"))
|
|||
|
||||
lazy val protocolProj = (project in file("protocol"))
|
||||
.enablePlugins(ContrabandPlugin, JsonCodecPlugin)
|
||||
.dependsOn(collectionProj)
|
||||
.settings(
|
||||
testedBaseSettings,
|
||||
scalacOptions -= "-Ywarn-unused",
|
||||
scalacOptions += "-Xlint:-unused",
|
||||
name := "Protocol",
|
||||
libraryDependencies ++= Seq(sjsonNewScalaJson.value),
|
||||
libraryDependencies ++= Seq(sjsonNewScalaJson.value, ipcSocket),
|
||||
managedSourceDirectories in Compile +=
|
||||
baseDirectory.value / "src" / "main" / "contraband-scala",
|
||||
sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala",
|
||||
|
|
@ -355,6 +356,9 @@ lazy val commandProj = (project in file("main-command"))
|
|||
exclude[ReversedMissingMethodProblem]("sbt.internal.CommandChannel.*"),
|
||||
// Added an overload to reboot. The overload is private[sbt].
|
||||
exclude[ReversedMissingMethodProblem]("sbt.StateOps.reboot"),
|
||||
// Replace nailgun socket stuff
|
||||
exclude[MissingClassProblem]("sbt.internal.NG*"),
|
||||
exclude[MissingClassProblem]("sbt.internal.ReferenceCountedFileDescriptor"),
|
||||
),
|
||||
unmanagedSources in (Compile, headerCreate) := {
|
||||
val old = (unmanagedSources in (Compile, headerCreate)).value
|
||||
|
|
@ -463,7 +467,7 @@ lazy val mainProj = (project in file("main"))
|
|||
lazy val sbtProj = (project in file("sbt"))
|
||||
.dependsOn(mainProj, scriptedSbtProj % "test->test")
|
||||
.settings(
|
||||
baseSettings,
|
||||
testedBaseSettings,
|
||||
name := "sbt",
|
||||
normalizedName := "sbt",
|
||||
crossScalaVersions := Seq(baseScalaVersion),
|
||||
|
|
@ -477,6 +481,7 @@ lazy val sbtProj = (project in file("sbt"))
|
|||
buildInfoKeys in Test := Seq[BuildInfoKey](fullClasspath in Compile),
|
||||
connectInput in run in Test := true,
|
||||
outputStrategy in run in Test := Some(StdoutOutput),
|
||||
fork in Test := true,
|
||||
)
|
||||
.configure(addSbtCompilerBridge)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,178 +0,0 @@
|
|||
// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGUnixDomainServerSocket.java
|
||||
|
||||
/*
|
||||
|
||||
Copyright 2004-2015, Martian Software, 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.sun.jna.LastErrorException;
|
||||
import com.sun.jna.ptr.IntByReference;
|
||||
|
||||
/**
|
||||
* Implements a {@link ServerSocket} which binds to a local Unix domain socket
|
||||
* and returns instances of {@link NGUnixDomainSocket} from
|
||||
* {@link #accept()}.
|
||||
*/
|
||||
public class NGUnixDomainServerSocket extends ServerSocket {
|
||||
private static final int DEFAULT_BACKLOG = 50;
|
||||
|
||||
// We use an AtomicInteger to prevent a race in this situation which
|
||||
// could happen if fd were just an int:
|
||||
//
|
||||
// Thread 1 -> NGUnixDomainServerSocket.accept()
|
||||
// -> lock this
|
||||
// -> check isBound and isClosed
|
||||
// -> unlock this
|
||||
// -> descheduled while still in method
|
||||
// Thread 2 -> NGUnixDomainServerSocket.close()
|
||||
// -> lock this
|
||||
// -> check isClosed
|
||||
// -> NGUnixDomainSocketLibrary.close(fd)
|
||||
// -> now fd is invalid
|
||||
// -> unlock this
|
||||
// Thread 1 -> re-scheduled while still in method
|
||||
// -> NGUnixDomainSocketLibrary.accept(fd, which is invalid and maybe re-used)
|
||||
//
|
||||
// By using an AtomicInteger, we'll set this to -1 after it's closed, which
|
||||
// will cause the accept() call above to cleanly fail instead of possibly
|
||||
// being called on an unrelated fd (which may or may not fail).
|
||||
private final AtomicInteger fd;
|
||||
|
||||
private final int backlog;
|
||||
private boolean isBound;
|
||||
private boolean isClosed;
|
||||
|
||||
public static class NGUnixDomainServerSocketAddress extends SocketAddress {
|
||||
private final String path;
|
||||
|
||||
public NGUnixDomainServerSocketAddress(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an unbound Unix domain server socket.
|
||||
*/
|
||||
public NGUnixDomainServerSocket() throws IOException {
|
||||
this(DEFAULT_BACKLOG, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an unbound Unix domain server socket with the specified listen backlog.
|
||||
*/
|
||||
public NGUnixDomainServerSocket(int backlog) throws IOException {
|
||||
this(backlog, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs and binds a Unix domain server socket to the specified path.
|
||||
*/
|
||||
public NGUnixDomainServerSocket(String path) throws IOException {
|
||||
this(DEFAULT_BACKLOG, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs and binds a Unix domain server socket to the specified path
|
||||
* with the specified listen backlog.
|
||||
*/
|
||||
public NGUnixDomainServerSocket(int backlog, String path) throws IOException {
|
||||
try {
|
||||
fd = new AtomicInteger(
|
||||
NGUnixDomainSocketLibrary.socket(
|
||||
NGUnixDomainSocketLibrary.PF_LOCAL,
|
||||
NGUnixDomainSocketLibrary.SOCK_STREAM,
|
||||
0));
|
||||
this.backlog = backlog;
|
||||
if (path != null) {
|
||||
bind(new NGUnixDomainServerSocketAddress(path));
|
||||
}
|
||||
} catch (LastErrorException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void bind(SocketAddress endpoint) throws IOException {
|
||||
if (!(endpoint instanceof NGUnixDomainServerSocketAddress)) {
|
||||
throw new IllegalArgumentException(
|
||||
"endpoint must be an instance of NGUnixDomainServerSocketAddress");
|
||||
}
|
||||
if (isBound) {
|
||||
throw new IllegalStateException("Socket is already bound");
|
||||
}
|
||||
if (isClosed) {
|
||||
throw new IllegalStateException("Socket is already closed");
|
||||
}
|
||||
NGUnixDomainServerSocketAddress unEndpoint = (NGUnixDomainServerSocketAddress) endpoint;
|
||||
NGUnixDomainSocketLibrary.SockaddrUn address =
|
||||
new NGUnixDomainSocketLibrary.SockaddrUn(unEndpoint.getPath());
|
||||
try {
|
||||
int socketFd = fd.get();
|
||||
NGUnixDomainSocketLibrary.bind(socketFd, address, address.size());
|
||||
NGUnixDomainSocketLibrary.listen(socketFd, backlog);
|
||||
isBound = true;
|
||||
} catch (LastErrorException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Socket accept() throws IOException {
|
||||
// We explicitly do not make this method synchronized, since the
|
||||
// call to NGUnixDomainSocketLibrary.accept() will block
|
||||
// indefinitely, causing another thread's call to close() to deadlock.
|
||||
synchronized (this) {
|
||||
if (!isBound) {
|
||||
throw new IllegalStateException("Socket is not bound");
|
||||
}
|
||||
if (isClosed) {
|
||||
throw new IllegalStateException("Socket is already closed");
|
||||
}
|
||||
}
|
||||
try {
|
||||
NGUnixDomainSocketLibrary.SockaddrUn sockaddrUn =
|
||||
new NGUnixDomainSocketLibrary.SockaddrUn();
|
||||
IntByReference addressLen = new IntByReference();
|
||||
addressLen.setValue(sockaddrUn.size());
|
||||
int clientFd = NGUnixDomainSocketLibrary.accept(fd.get(), sockaddrUn, addressLen);
|
||||
return new NGUnixDomainSocket(clientFd);
|
||||
} catch (LastErrorException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void close() throws IOException {
|
||||
if (isClosed) {
|
||||
throw new IllegalStateException("Socket is already closed");
|
||||
}
|
||||
try {
|
||||
// Ensure any pending call to accept() fails.
|
||||
NGUnixDomainSocketLibrary.close(fd.getAndSet(-1));
|
||||
isClosed = true;
|
||||
} catch (LastErrorException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,192 +0,0 @@
|
|||
// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGUnixDomainSocket.java
|
||||
|
||||
/*
|
||||
|
||||
Copyright 2004-2015, Martian Software, 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;
|
||||
|
||||
import com.sun.jna.LastErrorException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Implements a {@link Socket} backed by a native Unix domain socket.
|
||||
*
|
||||
* Instances of this class always return {@code null} for
|
||||
* {@link Socket#getInetAddress()}, {@link Socket#getLocalAddress()},
|
||||
* {@link Socket#getLocalSocketAddress()}, {@link Socket#getRemoteSocketAddress()}.
|
||||
*/
|
||||
public class NGUnixDomainSocket extends Socket {
|
||||
private final ReferenceCountedFileDescriptor fd;
|
||||
private final InputStream is;
|
||||
private final OutputStream os;
|
||||
|
||||
public NGUnixDomainSocket(String path) throws IOException {
|
||||
try {
|
||||
AtomicInteger fd = new AtomicInteger(
|
||||
NGUnixDomainSocketLibrary.socket(
|
||||
NGUnixDomainSocketLibrary.PF_LOCAL,
|
||||
NGUnixDomainSocketLibrary.SOCK_STREAM,
|
||||
0));
|
||||
NGUnixDomainSocketLibrary.SockaddrUn address =
|
||||
new NGUnixDomainSocketLibrary.SockaddrUn(path);
|
||||
int socketFd = fd.get();
|
||||
NGUnixDomainSocketLibrary.connect(socketFd, address, address.size());
|
||||
this.fd = new ReferenceCountedFileDescriptor(socketFd);
|
||||
this.is = new NGUnixDomainSocketInputStream();
|
||||
this.os = new NGUnixDomainSocketOutputStream();
|
||||
} catch (LastErrorException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Unix domain socket backed by a native file descriptor.
|
||||
*/
|
||||
public NGUnixDomainSocket(int fd) {
|
||||
this.fd = new ReferenceCountedFileDescriptor(fd);
|
||||
this.is = new NGUnixDomainSocketInputStream();
|
||||
this.os = new NGUnixDomainSocketOutputStream();
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return is;
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() {
|
||||
return os;
|
||||
}
|
||||
|
||||
public void shutdownInput() throws IOException {
|
||||
doShutdown(NGUnixDomainSocketLibrary.SHUT_RD);
|
||||
}
|
||||
|
||||
public void shutdownOutput() throws IOException {
|
||||
doShutdown(NGUnixDomainSocketLibrary.SHUT_WR);
|
||||
}
|
||||
|
||||
private void doShutdown(int how) throws IOException {
|
||||
try {
|
||||
int socketFd = fd.acquire();
|
||||
if (socketFd != -1) {
|
||||
NGUnixDomainSocketLibrary.shutdown(socketFd, how);
|
||||
}
|
||||
} catch (LastErrorException e) {
|
||||
throw new IOException(e);
|
||||
} finally {
|
||||
fd.release();
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
try {
|
||||
// This might not close the FD right away. In case we are about
|
||||
// to read or write on another thread, it will delay the close
|
||||
// until the read or write completes, to prevent the FD from
|
||||
// being re-used for a different purpose and the other thread
|
||||
// reading from a different FD.
|
||||
fd.close();
|
||||
} catch (LastErrorException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private class NGUnixDomainSocketInputStream extends InputStream {
|
||||
public int read() throws IOException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(1);
|
||||
int result;
|
||||
if (doRead(buf) == 0) {
|
||||
result = -1;
|
||||
} else {
|
||||
// Make sure to & with 0xFF to avoid sign extension
|
||||
result = 0xFF & buf.get();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
ByteBuffer buf = ByteBuffer.wrap(b, off, len);
|
||||
int result = doRead(buf);
|
||||
if (result == 0) {
|
||||
result = -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int doRead(ByteBuffer buf) throws IOException {
|
||||
try {
|
||||
int fdToRead = fd.acquire();
|
||||
if (fdToRead == -1) {
|
||||
return -1;
|
||||
}
|
||||
return NGUnixDomainSocketLibrary.read(fdToRead, buf, buf.remaining());
|
||||
} catch (LastErrorException e) {
|
||||
throw new IOException(e);
|
||||
} finally {
|
||||
fd.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class NGUnixDomainSocketOutputStream extends OutputStream {
|
||||
|
||||
public void write(int b) throws IOException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(1);
|
||||
buf.put(0, (byte) (0xFF & b));
|
||||
doWrite(buf);
|
||||
}
|
||||
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
ByteBuffer buf = ByteBuffer.wrap(b, off, len);
|
||||
doWrite(buf);
|
||||
}
|
||||
|
||||
private void doWrite(ByteBuffer buf) throws IOException {
|
||||
try {
|
||||
int fdToWrite = fd.acquire();
|
||||
if (fdToWrite == -1) {
|
||||
return;
|
||||
}
|
||||
int ret = NGUnixDomainSocketLibrary.write(fdToWrite, buf, buf.remaining());
|
||||
if (ret != buf.remaining()) {
|
||||
// This shouldn't happen with standard blocking Unix domain sockets.
|
||||
throw new IOException("Could not write " + buf.remaining() + " bytes as requested " +
|
||||
"(wrote " + ret + " bytes instead)");
|
||||
}
|
||||
} catch (LastErrorException e) {
|
||||
throw new IOException(e);
|
||||
} finally {
|
||||
fd.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGUnixDomainSocketLibrary.java
|
||||
|
||||
/*
|
||||
|
||||
Copyright 2004-2015, Martian Software, 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;
|
||||
|
||||
import com.sun.jna.LastErrorException;
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Platform;
|
||||
import com.sun.jna.Structure;
|
||||
import com.sun.jna.Union;
|
||||
import com.sun.jna.ptr.IntByReference;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility class to bridge native Unix domain socket calls to Java using JNA.
|
||||
*/
|
||||
public class NGUnixDomainSocketLibrary {
|
||||
public static final int PF_LOCAL = 1;
|
||||
public static final int AF_LOCAL = 1;
|
||||
public static final int SOCK_STREAM = 1;
|
||||
|
||||
public static final int SHUT_RD = 0;
|
||||
public static final int SHUT_WR = 1;
|
||||
|
||||
// Utility class, do not instantiate.
|
||||
private NGUnixDomainSocketLibrary() { }
|
||||
|
||||
// BSD platforms write a length byte at the start of struct sockaddr_un.
|
||||
private static final boolean HAS_SUN_LEN =
|
||||
Platform.isMac() || Platform.isFreeBSD() || Platform.isNetBSD() ||
|
||||
Platform.isOpenBSD() || Platform.iskFreeBSD();
|
||||
|
||||
/**
|
||||
* Bridges {@code struct sockaddr_un} to and from native code.
|
||||
*/
|
||||
public static class SockaddrUn extends Structure implements Structure.ByReference {
|
||||
/**
|
||||
* On BSD platforms, the {@code sun_len} and {@code sun_family} values in
|
||||
* {@code struct sockaddr_un}.
|
||||
*/
|
||||
public static class SunLenAndFamily extends Structure {
|
||||
public byte sunLen;
|
||||
public byte sunFamily;
|
||||
|
||||
protected List getFieldOrder() {
|
||||
return Arrays.asList(new String[] { "sunLen", "sunFamily" });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On BSD platforms, {@code sunLenAndFamily} will be present.
|
||||
* On other platforms, only {@code sunFamily} will be present.
|
||||
*/
|
||||
public static class SunFamily extends Union {
|
||||
public SunLenAndFamily sunLenAndFamily;
|
||||
public short sunFamily;
|
||||
}
|
||||
|
||||
public SunFamily sunFamily = new SunFamily();
|
||||
public byte[] sunPath = new byte[104];
|
||||
|
||||
/**
|
||||
* Constructs an empty {@code struct sockaddr_un}.
|
||||
*/
|
||||
public SockaddrUn() {
|
||||
if (HAS_SUN_LEN) {
|
||||
sunFamily.sunLenAndFamily = new SunLenAndFamily();
|
||||
sunFamily.setType(SunLenAndFamily.class);
|
||||
} else {
|
||||
sunFamily.setType(Short.TYPE);
|
||||
}
|
||||
allocateMemory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code struct sockaddr_un} with a path whose bytes are encoded
|
||||
* using the default encoding of the platform.
|
||||
*/
|
||||
public SockaddrUn(String path) throws IOException {
|
||||
byte[] pathBytes = path.getBytes();
|
||||
if (pathBytes.length > sunPath.length - 1) {
|
||||
throw new IOException("Cannot fit name [" + path + "] in maximum unix domain socket length");
|
||||
}
|
||||
System.arraycopy(pathBytes, 0, sunPath, 0, pathBytes.length);
|
||||
sunPath[pathBytes.length] = (byte) 0;
|
||||
if (HAS_SUN_LEN) {
|
||||
int len = fieldOffset("sunPath") + pathBytes.length;
|
||||
sunFamily.sunLenAndFamily = new SunLenAndFamily();
|
||||
sunFamily.sunLenAndFamily.sunLen = (byte) len;
|
||||
sunFamily.sunLenAndFamily.sunFamily = AF_LOCAL;
|
||||
sunFamily.setType(SunLenAndFamily.class);
|
||||
} else {
|
||||
sunFamily.sunFamily = AF_LOCAL;
|
||||
sunFamily.setType(Short.TYPE);
|
||||
}
|
||||
allocateMemory();
|
||||
}
|
||||
|
||||
protected List getFieldOrder() {
|
||||
return Arrays.asList(new String[] { "sunFamily", "sunPath" });
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
Native.register(Platform.C_LIBRARY_NAME);
|
||||
}
|
||||
|
||||
public static native int socket(int domain, int type, int protocol) throws LastErrorException;
|
||||
public static native int bind(int fd, SockaddrUn address, int addressLen)
|
||||
throws LastErrorException;
|
||||
public static native int listen(int fd, int backlog) throws LastErrorException;
|
||||
public static native int accept(int fd, SockaddrUn address, IntByReference addressLen)
|
||||
throws LastErrorException;
|
||||
public static native int connect(int fd, SockaddrUn address, int addressLen)
|
||||
throws LastErrorException;
|
||||
public static native int read(int fd, ByteBuffer buffer, int count)
|
||||
throws LastErrorException;
|
||||
public static native int write(int fd, ByteBuffer buffer, int count)
|
||||
throws LastErrorException;
|
||||
public static native int close(int fd) throws LastErrorException;
|
||||
public static native int shutdown(int fd, int how) throws LastErrorException;
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGWin32NamedPipeLibrary.java
|
||||
|
||||
/*
|
||||
|
||||
Copyright 2004-2017, Martian Software, 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;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.sun.jna.*;
|
||||
import com.sun.jna.platform.win32.WinNT;
|
||||
import com.sun.jna.platform.win32.WinNT.*;
|
||||
import com.sun.jna.platform.win32.WinBase.*;
|
||||
import com.sun.jna.ptr.IntByReference;
|
||||
|
||||
import com.sun.jna.win32.W32APIOptions;
|
||||
|
||||
public interface NGWin32NamedPipeLibrary extends Library, WinNT {
|
||||
int PIPE_ACCESS_DUPLEX = 3;
|
||||
int PIPE_UNLIMITED_INSTANCES = 255;
|
||||
int FILE_FLAG_FIRST_PIPE_INSTANCE = 524288;
|
||||
|
||||
NGWin32NamedPipeLibrary INSTANCE =
|
||||
(NGWin32NamedPipeLibrary) Native.loadLibrary(
|
||||
"kernel32",
|
||||
NGWin32NamedPipeLibrary.class,
|
||||
W32APIOptions.UNICODE_OPTIONS);
|
||||
|
||||
HANDLE CreateNamedPipe(
|
||||
String lpName,
|
||||
int dwOpenMode,
|
||||
int dwPipeMode,
|
||||
int nMaxInstances,
|
||||
int nOutBufferSize,
|
||||
int nInBufferSize,
|
||||
int nDefaultTimeOut,
|
||||
SECURITY_ATTRIBUTES lpSecurityAttributes);
|
||||
boolean ConnectNamedPipe(
|
||||
HANDLE hNamedPipe,
|
||||
Pointer lpOverlapped);
|
||||
boolean DisconnectNamedPipe(
|
||||
HANDLE hObject);
|
||||
boolean ReadFile(
|
||||
HANDLE hFile,
|
||||
Memory lpBuffer,
|
||||
int nNumberOfBytesToRead,
|
||||
IntByReference lpNumberOfBytesRead,
|
||||
Pointer lpOverlapped);
|
||||
boolean WriteFile(
|
||||
HANDLE hFile,
|
||||
ByteBuffer lpBuffer,
|
||||
int nNumberOfBytesToWrite,
|
||||
IntByReference lpNumberOfBytesWritten,
|
||||
Pointer lpOverlapped);
|
||||
boolean CloseHandle(
|
||||
HANDLE hObject);
|
||||
boolean GetOverlappedResult(
|
||||
HANDLE hFile,
|
||||
Pointer lpOverlapped,
|
||||
IntByReference lpNumberOfBytesTransferred,
|
||||
boolean wait);
|
||||
boolean CancelIoEx(
|
||||
HANDLE hObject,
|
||||
Pointer lpOverlapped);
|
||||
HANDLE CreateEvent(
|
||||
SECURITY_ATTRIBUTES lpEventAttributes,
|
||||
boolean bManualReset,
|
||||
boolean bInitialState,
|
||||
String lpName);
|
||||
int WaitForSingleObject(
|
||||
HANDLE hHandle,
|
||||
int dwMilliseconds
|
||||
);
|
||||
|
||||
int GetLastError();
|
||||
}
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGWin32NamedPipeServerSocket.java
|
||||
|
||||
/*
|
||||
|
||||
Copyright 2004-2017, Martian Software, 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;
|
||||
|
||||
import com.sun.jna.platform.win32.WinBase;
|
||||
import com.sun.jna.platform.win32.WinError;
|
||||
import com.sun.jna.platform.win32.WinNT;
|
||||
import com.sun.jna.platform.win32.WinNT.HANDLE;
|
||||
import com.sun.jna.ptr.IntByReference;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
public class NGWin32NamedPipeServerSocket extends ServerSocket {
|
||||
private static final NGWin32NamedPipeLibrary API = NGWin32NamedPipeLibrary.INSTANCE;
|
||||
private static final String WIN32_PIPE_PREFIX = "\\\\.\\pipe\\";
|
||||
private static final int BUFFER_SIZE = 65535;
|
||||
private final LinkedBlockingQueue<HANDLE> openHandles;
|
||||
private final LinkedBlockingQueue<HANDLE> connectedHandles;
|
||||
private final NGWin32NamedPipeSocket.CloseCallback closeCallback;
|
||||
private final String path;
|
||||
private final int maxInstances;
|
||||
private final HANDLE lockHandle;
|
||||
|
||||
public NGWin32NamedPipeServerSocket(String path) throws IOException {
|
||||
this(NGWin32NamedPipeLibrary.PIPE_UNLIMITED_INSTANCES, path);
|
||||
}
|
||||
|
||||
public NGWin32NamedPipeServerSocket(int maxInstances, String path) throws IOException {
|
||||
this.openHandles = new LinkedBlockingQueue<>();
|
||||
this.connectedHandles = new LinkedBlockingQueue<>();
|
||||
this.closeCallback = handle -> {
|
||||
if (connectedHandles.remove(handle)) {
|
||||
closeConnectedPipe(handle, false);
|
||||
}
|
||||
if (openHandles.remove(handle)) {
|
||||
closeOpenPipe(handle);
|
||||
}
|
||||
};
|
||||
this.maxInstances = maxInstances;
|
||||
if (!path.startsWith(WIN32_PIPE_PREFIX)) {
|
||||
this.path = WIN32_PIPE_PREFIX + path;
|
||||
} else {
|
||||
this.path = path;
|
||||
}
|
||||
String lockPath = this.path + "_lock";
|
||||
lockHandle = API.CreateNamedPipe(
|
||||
lockPath,
|
||||
NGWin32NamedPipeLibrary.FILE_FLAG_FIRST_PIPE_INSTANCE | NGWin32NamedPipeLibrary.PIPE_ACCESS_DUPLEX,
|
||||
0,
|
||||
1,
|
||||
BUFFER_SIZE,
|
||||
BUFFER_SIZE,
|
||||
0,
|
||||
null);
|
||||
if (lockHandle == NGWin32NamedPipeLibrary.INVALID_HANDLE_VALUE) {
|
||||
throw new IOException(String.format("Could not create lock for %s, error %d", lockPath, API.GetLastError()));
|
||||
} else {
|
||||
if (!API.DisconnectNamedPipe(lockHandle)) {
|
||||
throw new IOException(String.format("Could not disconnect lock %d", API.GetLastError()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void bind(SocketAddress endpoint) throws IOException {
|
||||
throw new IOException("Win32 named pipes do not support bind(), pass path to constructor");
|
||||
}
|
||||
|
||||
public Socket accept() throws IOException {
|
||||
HANDLE handle = API.CreateNamedPipe(
|
||||
path,
|
||||
NGWin32NamedPipeLibrary.PIPE_ACCESS_DUPLEX | WinNT.FILE_FLAG_OVERLAPPED,
|
||||
0,
|
||||
maxInstances,
|
||||
BUFFER_SIZE,
|
||||
BUFFER_SIZE,
|
||||
0,
|
||||
null);
|
||||
if (handle == NGWin32NamedPipeLibrary.INVALID_HANDLE_VALUE) {
|
||||
throw new IOException(String.format("Could not create named pipe, error %d", API.GetLastError()));
|
||||
}
|
||||
openHandles.add(handle);
|
||||
|
||||
HANDLE connWaitable = API.CreateEvent(null, true, false, null);
|
||||
WinBase.OVERLAPPED olap = new WinBase.OVERLAPPED();
|
||||
olap.hEvent = connWaitable;
|
||||
olap.write();
|
||||
|
||||
boolean immediate = API.ConnectNamedPipe(handle, olap.getPointer());
|
||||
if (immediate) {
|
||||
openHandles.remove(handle);
|
||||
connectedHandles.add(handle);
|
||||
return new NGWin32NamedPipeSocket(handle, closeCallback);
|
||||
}
|
||||
|
||||
int connectError = API.GetLastError();
|
||||
if (connectError == WinError.ERROR_PIPE_CONNECTED) {
|
||||
openHandles.remove(handle);
|
||||
connectedHandles.add(handle);
|
||||
return new NGWin32NamedPipeSocket(handle, closeCallback);
|
||||
} else if (connectError == WinError.ERROR_NO_DATA) {
|
||||
// Client has connected and disconnected between CreateNamedPipe() and ConnectNamedPipe()
|
||||
// connection is broken, but it is returned it avoid loop here.
|
||||
// Actual error will happen for NGSession when it will try to read/write from/to pipe
|
||||
return new NGWin32NamedPipeSocket(handle, closeCallback);
|
||||
} else if (connectError == WinError.ERROR_IO_PENDING) {
|
||||
if (!API.GetOverlappedResult(handle, olap.getPointer(), new IntByReference(), true)) {
|
||||
openHandles.remove(handle);
|
||||
closeOpenPipe(handle);
|
||||
throw new IOException("GetOverlappedResult() failed for connect operation: " + API.GetLastError());
|
||||
}
|
||||
openHandles.remove(handle);
|
||||
connectedHandles.add(handle);
|
||||
return new NGWin32NamedPipeSocket(handle, closeCallback);
|
||||
} else {
|
||||
throw new IOException("ConnectNamedPipe() failed with: " + connectError);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
List<HANDLE> handlesToClose = new ArrayList<>();
|
||||
openHandles.drainTo(handlesToClose);
|
||||
for (HANDLE handle : handlesToClose) {
|
||||
closeOpenPipe(handle);
|
||||
}
|
||||
|
||||
List<HANDLE> handlesToDisconnect = new ArrayList<>();
|
||||
connectedHandles.drainTo(handlesToDisconnect);
|
||||
for (HANDLE handle : handlesToDisconnect) {
|
||||
closeConnectedPipe(handle, true);
|
||||
}
|
||||
} finally {
|
||||
API.CloseHandle(lockHandle);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeOpenPipe(HANDLE handle) throws IOException {
|
||||
API.CancelIoEx(handle, null);
|
||||
API.CloseHandle(handle);
|
||||
}
|
||||
|
||||
private void closeConnectedPipe(HANDLE handle, boolean shutdown) throws IOException {
|
||||
if (!shutdown) {
|
||||
API.WaitForSingleObject(handle, 10000);
|
||||
}
|
||||
API.DisconnectNamedPipe(handle);
|
||||
API.CloseHandle(handle);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/NGWin32NamedPipeSocket.java
|
||||
// Made change in `read` to read just the amount of bytes available.
|
||||
|
||||
/*
|
||||
|
||||
Copyright 2004-2017, Martian Software, 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;
|
||||
|
||||
import com.sun.jna.Memory;
|
||||
import com.sun.jna.platform.win32.WinBase;
|
||||
import com.sun.jna.platform.win32.WinError;
|
||||
import com.sun.jna.platform.win32.WinNT.HANDLE;
|
||||
import com.sun.jna.ptr.IntByReference;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class NGWin32NamedPipeSocket extends Socket {
|
||||
private static final NGWin32NamedPipeLibrary API = NGWin32NamedPipeLibrary.INSTANCE;
|
||||
private final HANDLE handle;
|
||||
private final CloseCallback closeCallback;
|
||||
private final InputStream is;
|
||||
private final OutputStream os;
|
||||
private final HANDLE readerWaitable;
|
||||
private final HANDLE writerWaitable;
|
||||
|
||||
interface CloseCallback {
|
||||
void onNamedPipeSocketClose(HANDLE handle) throws IOException;
|
||||
}
|
||||
|
||||
public NGWin32NamedPipeSocket(
|
||||
HANDLE handle,
|
||||
NGWin32NamedPipeSocket.CloseCallback closeCallback) throws IOException {
|
||||
this.handle = handle;
|
||||
this.closeCallback = closeCallback;
|
||||
this.readerWaitable = API.CreateEvent(null, true, false, null);
|
||||
if (readerWaitable == null) {
|
||||
throw new IOException("CreateEvent() failed ");
|
||||
}
|
||||
writerWaitable = API.CreateEvent(null, true, false, null);
|
||||
if (writerWaitable == null) {
|
||||
throw new IOException("CreateEvent() failed ");
|
||||
}
|
||||
this.is = new NGWin32NamedPipeSocketInputStream(handle);
|
||||
this.os = new NGWin32NamedPipeSocketOutputStream(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return is;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() {
|
||||
return os;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
closeCallback.onNamedPipeSocketClose(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdownInput() throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdownOutput() throws IOException {
|
||||
}
|
||||
|
||||
private class NGWin32NamedPipeSocketInputStream extends InputStream {
|
||||
private final HANDLE handle;
|
||||
|
||||
NGWin32NamedPipeSocketInputStream(HANDLE handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int result;
|
||||
byte[] b = new byte[1];
|
||||
if (read(b) == 0) {
|
||||
result = -1;
|
||||
} else {
|
||||
result = 0xFF & b[0];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
Memory readBuffer = new Memory(len);
|
||||
|
||||
WinBase.OVERLAPPED olap = new WinBase.OVERLAPPED();
|
||||
olap.hEvent = readerWaitable;
|
||||
olap.write();
|
||||
|
||||
boolean immediate = API.ReadFile(handle, readBuffer, len, null, olap.getPointer());
|
||||
if (!immediate) {
|
||||
int lastError = API.GetLastError();
|
||||
if (lastError != WinError.ERROR_IO_PENDING) {
|
||||
throw new IOException("ReadFile() failed: " + lastError);
|
||||
}
|
||||
}
|
||||
|
||||
IntByReference read = new IntByReference();
|
||||
if (!API.GetOverlappedResult(handle, olap.getPointer(), read, true)) {
|
||||
int lastError = API.GetLastError();
|
||||
throw new IOException("GetOverlappedResult() failed for read operation: " + lastError);
|
||||
}
|
||||
int actualLen = read.getValue();
|
||||
byte[] byteArray = readBuffer.getByteArray(0, actualLen);
|
||||
System.arraycopy(byteArray, 0, b, off, actualLen);
|
||||
return actualLen;
|
||||
}
|
||||
}
|
||||
|
||||
private class NGWin32NamedPipeSocketOutputStream extends OutputStream {
|
||||
private final HANDLE handle;
|
||||
|
||||
NGWin32NamedPipeSocketOutputStream(HANDLE handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
write(new byte[]{(byte) (0xFF & b)});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
ByteBuffer data = ByteBuffer.wrap(b, off, len);
|
||||
|
||||
WinBase.OVERLAPPED olap = new WinBase.OVERLAPPED();
|
||||
olap.hEvent = writerWaitable;
|
||||
olap.write();
|
||||
|
||||
boolean immediate = API.WriteFile(handle, data, len, null, olap.getPointer());
|
||||
if (!immediate) {
|
||||
int lastError = API.GetLastError();
|
||||
if (lastError != WinError.ERROR_IO_PENDING) {
|
||||
throw new IOException("WriteFile() failed: " + lastError);
|
||||
}
|
||||
}
|
||||
IntByReference written = new IntByReference();
|
||||
if (!API.GetOverlappedResult(handle, olap.getPointer(), written, true)) {
|
||||
int lastError = API.GetLastError();
|
||||
throw new IOException("GetOverlappedResult() failed for write operation: " + lastError);
|
||||
}
|
||||
if (written.getValue() != len) {
|
||||
throw new IOException("WriteFile() wrote less bytes than requested");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
// Copied from https://github.com/facebook/nailgun/blob/af623fddedfdca010df46302a0711ce0e2cc1ba6/nailgun-server/src/main/java/com/martiansoftware/nailgun/ReferenceCountedFileDescriptor.java
|
||||
|
||||
/*
|
||||
|
||||
Copyright 2004-2015, Martian Software, 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;
|
||||
|
||||
import com.sun.jna.LastErrorException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Encapsulates a file descriptor plus a reference count to ensure close requests
|
||||
* only close the file descriptor once the last reference to the file descriptor
|
||||
* is released.
|
||||
*
|
||||
* If not explicitly closed, the file descriptor will be closed when
|
||||
* this object is finalized.
|
||||
*/
|
||||
public class ReferenceCountedFileDescriptor {
|
||||
private int fd;
|
||||
private int fdRefCount;
|
||||
private boolean closePending;
|
||||
|
||||
public ReferenceCountedFileDescriptor(int fd) {
|
||||
this.fd = fd;
|
||||
this.fdRefCount = 0;
|
||||
this.closePending = false;
|
||||
}
|
||||
|
||||
protected void finalize() throws IOException {
|
||||
close();
|
||||
}
|
||||
|
||||
public synchronized int acquire() {
|
||||
fdRefCount++;
|
||||
return fd;
|
||||
}
|
||||
|
||||
public synchronized void release() throws IOException {
|
||||
fdRefCount--;
|
||||
if (fdRefCount == 0 && closePending && fd != -1) {
|
||||
doClose();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void close() throws IOException {
|
||||
if (fd == -1 || closePending) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fdRefCount == 0) {
|
||||
doClose();
|
||||
} else {
|
||||
// Another thread has the FD. We'll close it when they release the reference.
|
||||
closePending = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void doClose() throws IOException {
|
||||
try {
|
||||
NGUnixDomainSocketLibrary.close(fd);
|
||||
fd = -1;
|
||||
} catch (LastErrorException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ import sjsonnew.support.scalajson.unsafe.{ Converter, CompactPrinter }
|
|||
import sbt.internal.protocol.codec._
|
||||
import sbt.internal.util.ErrorHandling
|
||||
import sbt.internal.util.Util.isWindows
|
||||
import org.scalasbt.ipcsocket._
|
||||
|
||||
private[sbt] sealed trait ServerInstance {
|
||||
def shutdown(): Unit
|
||||
|
|
@ -57,11 +58,11 @@ private[sbt] object Server {
|
|||
connection.connectionType match {
|
||||
case ConnectionType.Local if isWindows =>
|
||||
// Named pipe already has an exclusive lock.
|
||||
addServerError(new NGWin32NamedPipeServerSocket(pipeName))
|
||||
addServerError(new Win32NamedPipeServerSocket(pipeName))
|
||||
case ConnectionType.Local =>
|
||||
tryClient(new NGUnixDomainSocket(socketfile.getAbsolutePath))
|
||||
tryClient(new UnixDomainSocket(socketfile.getAbsolutePath))
|
||||
prepareSocketfile()
|
||||
addServerError(new NGUnixDomainServerSocket(socketfile.getAbsolutePath))
|
||||
addServerError(new UnixDomainServerSocket(socketfile.getAbsolutePath))
|
||||
case ConnectionType.Tcp =>
|
||||
tryClient(new Socket(InetAddress.getByName(host), port))
|
||||
addServerError(new ServerSocket(port, 50, InetAddress.getByName(host)))
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ private[sbt] final class CommandExchange {
|
|||
subscribe(channel)
|
||||
}
|
||||
if (server.isEmpty && firstInstance.get) {
|
||||
val portfile = (new File(".")).getAbsoluteFile / "project" / "target" / "active.json"
|
||||
val portfile = s.baseDir / "project" / "target" / "active.json"
|
||||
val h = Hash.halfHashString(IO.toURI(portfile).toString)
|
||||
val tokenfile = BuildPaths.getGlobalBase(s) / "server" / h / "token.json"
|
||||
val socketfile = BuildPaths.getGlobalBase(s) / "server" / h / "sock"
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ object Dependencies {
|
|||
val launcherInterface = "org.scala-sbt" % "launcher-interface" % "1.0.2"
|
||||
val rawLauncher = "org.scala-sbt" % "launcher" % "1.0.2"
|
||||
val testInterface = "org.scala-sbt" % "test-interface" % "1.0"
|
||||
val ipcSocket = "org.scala-sbt.ipcsocket" % "ipcsocket" % "1.0.0"
|
||||
|
||||
private val compilerInterface = "org.scala-sbt" % "compiler-interface" % zincVersion
|
||||
private val compilerClasspath = "org.scala-sbt" %% "zinc-classpath" % zincVersion
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2017, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under BSD-3-Clause license (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt
|
||||
package protocol
|
||||
|
||||
import java.io.File
|
||||
import java.net.{ Socket, URI, InetAddress }
|
||||
import sjsonnew.BasicJsonProtocol
|
||||
import sjsonnew.support.scalajson.unsafe.{ Parser, Converter }
|
||||
import sjsonnew.shaded.scalajson.ast.unsafe.JValue
|
||||
import sbt.internal.protocol.{ PortFile, TokenFile }
|
||||
import sbt.internal.protocol.codec.{ PortFileFormats, TokenFileFormats }
|
||||
import sbt.internal.util.Util.isWindows
|
||||
import org.scalasbt.ipcsocket._
|
||||
|
||||
object ClientSocket {
|
||||
private lazy val fileFormats = new BasicJsonProtocol with PortFileFormats with TokenFileFormats {}
|
||||
|
||||
def socket(portfile: File): (Socket, Option[String]) = {
|
||||
import fileFormats._
|
||||
val json: JValue = Parser.parseFromFile(portfile).get
|
||||
val p = Converter.fromJson[PortFile](json).get
|
||||
val uri = new URI(p.uri)
|
||||
// println(uri)
|
||||
val token = p.tokenfilePath map { tp =>
|
||||
val tokeFile = new File(tp)
|
||||
val json: JValue = Parser.parseFromFile(tokeFile).get
|
||||
val t = Converter.fromJson[TokenFile](json).get
|
||||
t.token
|
||||
}
|
||||
val sk = uri.getScheme match {
|
||||
case "local" if isWindows =>
|
||||
new Win32NamedPipeSocket("""\\.\pipe\""" + uri.getSchemeSpecificPart)
|
||||
case "local" => new UnixDomainSocket(uri.getSchemeSpecificPart)
|
||||
case "tcp" => new Socket(InetAddress.getByName(uri.getHost), uri.getPort)
|
||||
case _ => sys.error(s"Unsupported uri: $uri")
|
||||
}
|
||||
(sk, token)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
package example
|
||||
|
||||
import java.net.{ URI, Socket, InetAddress, SocketException }
|
||||
import sbt.io._
|
||||
import sbt.io.syntax._
|
||||
import java.io.File
|
||||
import sjsonnew.support.scalajson.unsafe.{ Parser, Converter, CompactPrinter }
|
||||
import sjsonnew.shaded.scalajson.ast.unsafe.{ JValue, JObject, JString }
|
||||
|
||||
object Client extends App {
|
||||
val host = "127.0.0.1"
|
||||
val delimiter: Byte = '\n'.toByte
|
||||
|
||||
lazy val connection = getConnection
|
||||
lazy val out = connection.getOutputStream
|
||||
lazy val in = connection.getInputStream
|
||||
|
||||
val t = getToken
|
||||
val msg0 = s"""{ "type": "InitCommand", "token": "$t" }"""
|
||||
|
||||
writeLine(s"Content-Length: ${ msg0.size + 2 }")
|
||||
writeLine("Content-Type: application/sbt-x1")
|
||||
writeLine("")
|
||||
writeLine(msg0)
|
||||
out.flush
|
||||
|
||||
writeLine("Content-Length: 49")
|
||||
writeLine("Content-Type: application/sbt-x1")
|
||||
writeLine("")
|
||||
// 12345678901234567890123456789012345678901234567890
|
||||
writeLine("""{ "type": "ExecCommand", "commandLine": "exit" }""")
|
||||
writeLine("")
|
||||
out.flush
|
||||
|
||||
val baseDirectory = new File(args(0))
|
||||
IO.write(baseDirectory / "ok.txt", "ok")
|
||||
|
||||
def getToken: String = {
|
||||
val tokenfile = new File(getTokenFileUri)
|
||||
val json: JValue = Parser.parseFromFile(tokenfile).get
|
||||
json match {
|
||||
case JObject(fields) =>
|
||||
(fields find { _.field == "token" } map { _.value }) match {
|
||||
case Some(JString(value)) => value
|
||||
case _ =>
|
||||
sys.error("json doesn't token field that is JString")
|
||||
}
|
||||
case _ => sys.error("json doesn't have token field")
|
||||
}
|
||||
}
|
||||
|
||||
def getTokenFileUri: URI = {
|
||||
val portfile = baseDirectory / "project" / "target" / "active.json"
|
||||
val json: JValue = Parser.parseFromFile(portfile).get
|
||||
json match {
|
||||
case JObject(fields) =>
|
||||
(fields find { _.field == "tokenfileUri" } map { _.value }) match {
|
||||
case Some(JString(value)) => new URI(value)
|
||||
case _ =>
|
||||
sys.error("json doesn't tokenfile field that is JString")
|
||||
}
|
||||
case _ => sys.error("json doesn't have tokenfile field")
|
||||
}
|
||||
}
|
||||
|
||||
def getPort: Int = {
|
||||
val portfile = baseDirectory / "project" / "target" / "active.json"
|
||||
val json: JValue = Parser.parseFromFile(portfile).get
|
||||
json match {
|
||||
case JObject(fields) =>
|
||||
(fields find { _.field == "uri" } map { _.value }) match {
|
||||
case Some(JString(value)) =>
|
||||
val u = new URI(value)
|
||||
u.getPort
|
||||
case _ =>
|
||||
sys.error("json doesn't uri field that is JString")
|
||||
}
|
||||
case _ => sys.error("json doesn't have uri field")
|
||||
}
|
||||
}
|
||||
|
||||
def getConnection: Socket =
|
||||
try {
|
||||
new Socket(InetAddress.getByName(host), getPort)
|
||||
} catch {
|
||||
case _ =>
|
||||
Thread.sleep(1000)
|
||||
getConnection
|
||||
}
|
||||
|
||||
def writeLine(s: String): Unit = {
|
||||
if (s != "") {
|
||||
out.write(s.getBytes("UTF-8"))
|
||||
}
|
||||
writeEndLine
|
||||
}
|
||||
|
||||
def writeEndLine(): Unit = {
|
||||
val retByte: Byte = '\r'.toByte
|
||||
val delimiter: Byte = '\n'.toByte
|
||||
|
||||
out.write(retByte.toInt)
|
||||
out.write(delimiter.toInt)
|
||||
out.flush
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
lazy val runClient = taskKey[Unit]("")
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.settings(
|
||||
serverConnectionType in Global := ConnectionType.Tcp,
|
||||
scalaVersion := "2.12.3",
|
||||
serverPort in Global := 5123,
|
||||
libraryDependencies += "org.scala-sbt" %% "io" % "1.0.1",
|
||||
libraryDependencies += "com.eed3si9n" %% "sjson-new-scalajson" % "0.8.0",
|
||||
runClient := (Def.taskDyn {
|
||||
val b = baseDirectory.value
|
||||
(bgRun in Compile).toTask(s""" $b""")
|
||||
}).value
|
||||
)
|
||||
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
> show serverPort
|
||||
> runClient
|
||||
|
||||
-> shell
|
||||
|
||||
$ exists ok.txt
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
lazy val root = (project in file("."))
|
||||
.settings(
|
||||
Global / serverLog / logLevel := Level.Debug,
|
||||
name := "handshake",
|
||||
scalaVersion := "2.12.3",
|
||||
)
|
||||
|
|
@ -22,7 +22,7 @@ object RunFromSourceMain {
|
|||
|
||||
// this arrangement is because Scala does not always properly optimize away
|
||||
// the tail recursion in a catch statement
|
||||
@tailrec private def run(baseDir: File, args: Seq[String]): Unit =
|
||||
@tailrec private[sbt] def run(baseDir: File, args: Seq[String]): Unit =
|
||||
runImpl(baseDir, args) match {
|
||||
case Some((baseDir, args)) => run(baseDir, args)
|
||||
case None => ()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2017, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under BSD-3-Clause license (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt
|
||||
|
||||
import org.scalatest._
|
||||
import scala.concurrent._
|
||||
import java.io.{ InputStream, OutputStream }
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.{ ThreadFactory, ThreadPoolExecutor }
|
||||
import sbt.protocol.ClientSocket
|
||||
|
||||
class ServerSpec extends AsyncFlatSpec with Matchers {
|
||||
import ServerSpec._
|
||||
|
||||
"server" should "start" in {
|
||||
withBuildSocket("handshake") { (out, in, tkn) =>
|
||||
writeLine(
|
||||
"""{ "jsonrpc": "2.0", "id": 3, "method": "sbt/setting", "params": { "setting": "root/name" } }""",
|
||||
out)
|
||||
Thread.sleep(100)
|
||||
val l2 = contentLength(in)
|
||||
println(l2)
|
||||
readLine(in)
|
||||
readLine(in)
|
||||
val x2 = readContentLength(in, l2)
|
||||
println(x2)
|
||||
assert(1 == 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ServerSpec {
|
||||
private val serverTestBase: File = new File(".").getAbsoluteFile / "sbt" / "src" / "server-test"
|
||||
private val nextThreadId = new AtomicInteger(1)
|
||||
private val threadGroup = Thread.currentThread.getThreadGroup()
|
||||
val readBuffer = new Array[Byte](4096)
|
||||
var buffer: Vector[Byte] = Vector.empty
|
||||
var bytesRead = 0
|
||||
private val delimiter: Byte = '\n'.toByte
|
||||
private val RetByte = '\r'.toByte
|
||||
|
||||
private val threadFactory = new ThreadFactory() {
|
||||
override def newThread(runnable: Runnable): Thread = {
|
||||
val thread =
|
||||
new Thread(threadGroup,
|
||||
runnable,
|
||||
s"sbt-test-server-threads-${nextThreadId.getAndIncrement}")
|
||||
// Do NOT setDaemon because then the code in TaskExit.scala in sbt will insta-kill
|
||||
// the backgrounded process, at least for the case of the run task.
|
||||
thread
|
||||
}
|
||||
}
|
||||
|
||||
private val executor = new ThreadPoolExecutor(
|
||||
0, /* corePoolSize */
|
||||
1, /* maxPoolSize, max # of servers */
|
||||
2,
|
||||
java.util.concurrent.TimeUnit.SECONDS,
|
||||
/* keep alive unused threads this long (if corePoolSize < maxPoolSize) */
|
||||
new java.util.concurrent.SynchronousQueue[Runnable](),
|
||||
threadFactory
|
||||
)
|
||||
|
||||
def backgroundRun(baseDir: File, args: Seq[String]): Unit = {
|
||||
executor.execute(new Runnable {
|
||||
def run(): Unit = {
|
||||
RunFromSourceMain.run(baseDir, args)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
def shutdown(): Unit = executor.shutdown()
|
||||
|
||||
def withBuildSocket(testBuild: String)(
|
||||
f: (OutputStream, InputStream, Option[String]) => Future[Assertion]): Future[Assertion] = {
|
||||
IO.withTemporaryDirectory { temp =>
|
||||
IO.copyDirectory(serverTestBase / testBuild, temp / testBuild)
|
||||
withBuildSocket(temp / testBuild)(f)
|
||||
}
|
||||
}
|
||||
|
||||
def sendJsonRpc(message: String, out: OutputStream): Unit = {
|
||||
writeLine(s"""Content-Length: ${message.size + 2}""", out)
|
||||
writeLine("", out)
|
||||
writeLine(message, out)
|
||||
}
|
||||
|
||||
def contentLength(in: InputStream): Int = {
|
||||
readLine(in) map { line =>
|
||||
line.drop(16).toInt
|
||||
} getOrElse (0)
|
||||
}
|
||||
|
||||
def readLine(in: InputStream): Option[String] = {
|
||||
if (buffer.isEmpty) {
|
||||
val bytesRead = in.read(readBuffer)
|
||||
if (bytesRead > 0) {
|
||||
buffer = buffer ++ readBuffer.toVector.take(bytesRead)
|
||||
}
|
||||
}
|
||||
val delimPos = buffer.indexOf(delimiter)
|
||||
if (delimPos > 0) {
|
||||
val chunk0 = buffer.take(delimPos)
|
||||
buffer = buffer.drop(delimPos + 1)
|
||||
// remove \r at the end of line.
|
||||
if (chunk0.size > 0 && chunk0.indexOf(RetByte) == chunk0.size - 1)
|
||||
Some(new String(chunk0.dropRight(1).toArray, "utf-8"))
|
||||
else Some(new String(chunk0.toArray, "utf-8"))
|
||||
} else None // no EOL yet, so skip this turn.
|
||||
}
|
||||
|
||||
def readContentLength(in: InputStream, length: Int): Option[String] = {
|
||||
if (buffer.isEmpty) {
|
||||
val bytesRead = in.read(readBuffer)
|
||||
if (bytesRead > 0) {
|
||||
buffer = buffer ++ readBuffer.toVector.take(bytesRead)
|
||||
}
|
||||
}
|
||||
if (length <= buffer.size) {
|
||||
val chunk = buffer.take(length)
|
||||
buffer = buffer.drop(length)
|
||||
Some(new String(chunk.toArray, "utf-8"))
|
||||
} else None // have not read enough yet, so skip this turn.
|
||||
}
|
||||
|
||||
def writeLine(s: String, out: OutputStream): Unit = {
|
||||
def writeEndLine(): Unit = {
|
||||
val retByte: Byte = '\r'.toByte
|
||||
val delimiter: Byte = '\n'.toByte
|
||||
out.write(retByte.toInt)
|
||||
out.write(delimiter.toInt)
|
||||
out.flush
|
||||
}
|
||||
|
||||
if (s != "") {
|
||||
out.write(s.getBytes("UTF-8"))
|
||||
}
|
||||
writeEndLine
|
||||
}
|
||||
|
||||
def withBuildSocket(baseDirectory: File)(
|
||||
f: (OutputStream, InputStream, Option[String]) => Future[Assertion]): Future[Assertion] = {
|
||||
backgroundRun(baseDirectory, Nil)
|
||||
|
||||
val portfile = baseDirectory / "project" / "target" / "active.json"
|
||||
|
||||
def waitForPortfile(n: Int): Unit =
|
||||
if (portfile.exists) ()
|
||||
else {
|
||||
if (n <= 0) sys.error(s"Timeout. $portfile is not found.")
|
||||
else {
|
||||
Thread.sleep(1000)
|
||||
waitForPortfile(n - 1)
|
||||
}
|
||||
}
|
||||
waitForPortfile(10)
|
||||
val (sk, tkn) = ClientSocket.socket(portfile)
|
||||
val out = sk.getOutputStream
|
||||
val in = sk.getInputStream
|
||||
|
||||
sendJsonRpc(
|
||||
"""{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { } } }""",
|
||||
out)
|
||||
|
||||
try {
|
||||
f(out, in, tkn)
|
||||
} finally {
|
||||
sendJsonRpc(
|
||||
"""{ "jsonrpc": "2.0", "id": 9, "method": "sbt/exec", "params": { "commandLine": "exit" } }""",
|
||||
out)
|
||||
shutdown()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue