summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean-Philippe Lesot <jplesot@google.com>2015-03-11 10:50:55 +0100
committerJean-Philippe Lesot <jplesot@google.com>2015-03-11 10:50:55 +0100
commita6ad00e946f9efa9fcaad7ebff51e125a224e4b3 (patch)
treeadc1860f2496d58b222d8008a6a83405ddafe472
parent14f66d21a64021d5492c56aff5055e5e7382dfd4 (diff)
downloadtoolchain_jack-a6ad00e946f9efa9fcaad7ebff51e125a224e4b3.tar.gz
toolchain_jack-a6ad00e946f9efa9fcaad7ebff51e125a224e4b3.tar.bz2
toolchain_jack-a6ad00e946f9efa9fcaad7ebff51e125a224e4b3.zip
Start support of a small server for Jack compilation
Change-Id: I38d410565da4f480c60cdd9405603eebaa2d9938
-rwxr-xr-xjack/etc/jack85
-rw-r--r--jack/src/com/android/jack/server/Server.java383
-rw-r--r--jack/src/com/android/jack/server/ServerTask.java32
-rw-r--r--sched/src/com/android/sched/util/file/AbstractStreamFile.java4
-rw-r--r--sched/src/com/android/sched/util/file/Directory.java2
-rw-r--r--sched/src/com/android/sched/util/file/FileOrDirectory.java38
-rw-r--r--sched/src/com/android/sched/util/file/OutputStreamFile.java23
7 files changed, 564 insertions, 3 deletions
diff --git a/jack/etc/jack b/jack/etc/jack
new file mode 100755
index 00000000..2409a09b
--- /dev/null
+++ b/jack/etc/jack
@@ -0,0 +1,85 @@
+#! /bin/bash
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# 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.
+#
+set -o nounset
+
+JACK_NB_COMPILE=4
+JACK_TIMEOUT=15
+
+TMPDIR="/tmp"
+
+JACK_PRG="java -cp /usr/local/google/home/jplesot/Android/ub-jack/toolchain/jack/jack/dist/jack.jar com.android.jack.server.Server"
+JACK_FIFO="$TMPDIR/jack-$USER.cmd"
+JACK_LOCK="$TMPDIR/jack-$USER.lock"
+JACK_LOG="$TMPDIR/jack-$USER.log"
+
+JACK_DIR="$TMPDIR/jack-$USER-$$/"
+JACK_OUT="$JACK_DIR/out"
+JACK_ERR="$JACK_DIR/err"
+JACK_EXIT="$JACK_DIR/exit"
+JACK_CLI="$JACK_DIR/cli"
+JACK_PWD="$PWD"
+
+#
+# Always create the FIFO
+#
+
+mkfifo -m 0000 $JACK_FIFO 2>/dev/null
+
+#
+# Prepare
+#
+
+# Cleanup
+trap "rm -f $JACK_OUT $JACK_ERR $JACK_EXIT $JACK_CLI $JACK_LOCK 2>/dev/null; rmdir $JACK_DIR 2>/dev/null" exit
+
+# Create fifo for a task
+mkdir $JACK_DIR
+mkfifo $JACK_OUT
+mkfifo $JACK_ERR
+mkfifo $JACK_EXIT
+
+# Redirect output and error
+cat <$JACK_OUT >&1 &
+PID_OUT=$!
+cat <$JACK_ERR >&2 &
+PID_ERR=$!
+
+echo \"$PWD\" $* >$JACK_CLI
+
+#
+# Launch compilation
+#
+
+# Try sending a command
+CMD="+ $JACK_OUT $JACK_ERR $JACK_EXIT $JACK_CLI"
+( echo $CMD >>$JACK_FIFO ) 2>/dev/null
+while [ $? -ne 0 ]; do
+ # If not possible, launch the server one time only
+ mkfifo $JACK_LOCK 2>/dev/null
+ if [ $? -eq 0 ]; then
+ echo "Launch background server" $JACK_PRG
+ nohup $JACK_PRG $JACK_NB_COMPILE $JACK_TIMEOUT $JACK_FIFO >$JACK_LOG 2>&1 &
+ fi
+ # Slow down, and retry sending the command
+ sleep 1
+ ( echo $CMD >>$JACK_FIFO ) 2>/dev/null
+done
+
+wait $PID_OUT
+wait $PID_ERR
+
+exit `cat $JACK_EXIT`
diff --git a/jack/src/com/android/jack/server/Server.java b/jack/src/com/android/jack/server/Server.java
new file mode 100644
index 00000000..65bd99a3
--- /dev/null
+++ b/jack/src/com/android/jack/server/Server.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.jack.server;
+
+import com.android.sched.util.config.cli.TokenIterator;
+import com.android.sched.util.file.AbstractStreamFile;
+import com.android.sched.util.file.CannotSetPermissionException;
+import com.android.sched.util.file.FileOrDirectory;
+import com.android.sched.util.file.FileOrDirectory.ChangePermission;
+import com.android.sched.util.file.FileOrDirectory.Permission;
+import com.android.sched.util.file.OutputStreamFile;
+import com.android.sched.util.location.FileLocation;
+import com.android.sched.util.location.NoLocation;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Random;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * XXX
+ */
+public class Server {
+ @Nonnull
+ private static ServerTask service = new ServerTask() {
+ @Nonnull
+ private final Random rnd = new Random();
+
+ @Override
+ public int run(@Nonnull PrintStream out, @Nonnull PrintStream err, @Nonnull File workingDir,
+ @Nonnull TokenIterator args) {
+ String cmd = null;
+ try {
+ args.hasNext();
+ cmd = args.next();
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+
+ out.println("Pre-test stdout for '" + workingDir.getPath() + "'");
+ err.println("Pre-test stderr for '" + cmd + "'");
+
+ try {
+ Thread.sleep(rnd.nextInt(30000));
+ } catch (InterruptedException e) {
+ // Doesn't matter
+ }
+
+ out.println("Post-test stdout for '" + workingDir.getPath() + "'");
+ err.println("Post-test stderr for '" + cmd + "'");
+
+ return rnd.nextInt(30);
+ }
+ };
+
+ @Nonnull
+ private static Logger logger = Logger.getLogger(Server.class.getSimpleName());
+
+ private static final int CMD_IDX_CMD = 0;
+ private static final int CMD_IDX_OUT = 1;
+ private static final int CMD_IDX_ERR = 2;
+ private static final int CMD_IDX_EXIT = 3;
+ private static final int CMD_IDX_CLI = 4;
+ private static final int CMD_IDX_END = 5;
+
+ private static final int CLI_IDX_MAX = 0;
+ private static final int CLI_IDX_TIEMOUT = 1;
+ private static final int CLI_IDX_FIFO = 2;
+ private static final int CLI_IDX_END = 3;
+
+ @Nonnull
+ private static File fifo;
+ @CheckForNull
+ private static LineNumberReader in;
+
+ private static int timeout;
+
+ @Nonnull
+ private static AtomicInteger nbMax = new AtomicInteger(0);
+ @Nonnull
+ private static AtomicLong nbCurrent = new AtomicLong(0);;
+
+ public static void main(String[] args) throws InterruptedException {
+ if (args.length != CLI_IDX_END) {
+ logger.log(Level.SEVERE, "Usage: <max-compile> <timeout-s> <path-fifo>");
+ abort();
+ }
+
+ int nbInstance = 0;
+ try {
+ nbInstance = Integer.parseInt(args[CLI_IDX_MAX]);
+ } catch (NumberFormatException e) {
+ logger.log(Level.SEVERE, "Cannot parse instance count '" + args[CLI_IDX_MAX] + "'");
+ abort();
+ }
+
+ try {
+ timeout = Integer.parseInt(args[CLI_IDX_TIEMOUT]) * 1000;
+ } catch (NumberFormatException e) {
+ logger.log(Level.SEVERE, "Cannot parse timeout '" + args[CLI_IDX_TIEMOUT] + "'");
+ abort();
+ }
+
+ fifo = new File(args[CLI_IDX_FIFO]);
+ if (fifo.canWrite()) {
+ logger.log(Level.WARNING, "Already running, aborting");
+ abort();
+ }
+
+ Runtime.getRuntime().addShutdownHook(new Thread(){
+ @Override
+ public void run() {
+ cancelTimer();
+ shutdownFifo();
+ }
+ });
+
+ try {
+ AbstractStreamFile.check(fifo, new FileLocation(fifo));
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e.getMessage(), e);
+ abort();
+ }
+
+ startFifo();
+ try {
+ in = new LineNumberReader(new FileReader(fifo));
+ } catch (FileNotFoundException e) {
+ throw new AssertionError(e);
+ }
+
+ logger.log(Level.INFO, "Starting server on '" + fifo.getPath() + "'");
+ ExecutorService executor = Executors.newFixedThreadPool(nbInstance);
+ for (int i = 0; i < nbInstance; i++) {
+ logger.log(Level.INFO, "Launching task #" + i);
+ executor.execute(new Task());
+ }
+
+ executor.shutdown();
+ executor.awaitTermination(1, TimeUnit.HOURS);
+
+ logger.log(Level.INFO, "Shutdown server");
+ logger.log(Level.INFO, "# service runs " + nbCurrent.get());
+ }
+
+ /**
+ * STOPSHIP
+ */
+ public static class Task implements Runnable {
+ @Override
+ public void run() {
+ while (true) {
+ String line;
+
+ try {
+ line = getLine();
+ logger.log(Level.INFO, "Read command '" + line + "'");
+ } catch (IOException e) {
+ logger.log(Level.INFO, "Shutdown task");
+ return;
+ }
+
+ String[] command = line.split(" ");
+
+ if (command[CMD_IDX_CMD].equals("-")) {
+ cancelTimer();
+ shutdownFifo();
+ continue;
+ }
+
+ if (!command[CMD_IDX_CMD].equals("+")) {
+ logger.log(Level.SEVERE, "Command error '" + line + "'");
+ continue;
+ }
+
+ if (command.length != CMD_IDX_END) {
+ logger.log(Level.SEVERE, "Command format error '" + line + "'");
+ continue;
+ }
+
+ logger.log(Level.INFO, "Open standard output '" + command[CMD_IDX_OUT] + "'");
+ PrintStream out = null;
+ try {
+ out = new OutputStreamFile(command[CMD_IDX_OUT]).getPrintStream();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e.getMessage(), e);
+ continue;
+ }
+
+ logger.log(Level.INFO, "Open standard error '" + command[CMD_IDX_ERR] + "'");
+ PrintStream err = null;
+ try {
+ err = new OutputStreamFile(command[CMD_IDX_ERR]).getPrintStream();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e.getMessage(), e);
+ continue;
+ }
+
+ logger.log(Level.INFO, "Parse command line");
+ TokenIterator args = new TokenIterator(new NoLocation(), "@" + command[CMD_IDX_CLI]);
+ args.allowFileReferenceInFile();
+
+ if (!args.hasNext()) {
+ logger.log(Level.WARNING, "Cli format error");
+ continue;
+ }
+ String workingDir;
+ try {
+ workingDir = args.next();
+ } catch (IOException e) {
+ logger.log(Level.WARNING, "Cli format error");
+ continue;
+ }
+
+ if (nbMax.getAndIncrement() == 0) {
+ cancelTimer();
+ }
+
+ int code;
+ try {
+ logger.log(Level.INFO, "Run service");
+ nbCurrent.incrementAndGet();
+ code = service.run(out, err, new File(workingDir), args);
+ } finally {
+ if (nbMax.decrementAndGet() == 0) {
+ startTimer();
+ }
+ }
+
+ assert out != null;
+ out.close();
+ assert err != null;
+ err.close();
+
+ try {
+ logger.log(Level.INFO, "Open exit fifo '" + command[CMD_IDX_EXIT] + "'");
+ PrintStream exit = new OutputStreamFile(command[CMD_IDX_EXIT]).getPrintStream();
+ logger.log(Level.INFO, "Write exit code '" + code + "'");
+ exit.println(code);
+ exit.close();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e.getMessage(), e);
+ continue;
+ }
+
+ }
+ }
+ }
+
+ @Nonnull
+ private static Object lockRead = new Object();
+
+ @Nonnull
+ public static String getLine() throws IOException {
+ synchronized (lockRead) {
+ String str = in.readLine();
+ while (str == null) {
+ try {
+ in.close();
+ } catch (IOException e1) {
+ // Best effort
+ }
+
+ in = new LineNumberReader(new FileReader(fifo));
+ str = in.readLine();
+ }
+
+ return str;
+ }
+ }
+
+ private static void startFifo() {
+ logger.log(Level.FINE, "Start FIFO");
+
+ try {
+ FileOrDirectory.setPermissions(fifo, new FileLocation(fifo),
+ Permission.READ | Permission.WRITE, ChangePermission.OWNER);
+ } catch (CannotSetPermissionException e) {
+ logger.log(Level.SEVERE, e.getMessage(), e);
+ abort();
+ }
+ }
+
+ private static void shutdownFifo() {
+ OutputStream out = null;
+
+ logger.log(Level.FINE, "Shutdown FIFO");
+
+ try {
+ out = new FileOutputStream(fifo);
+ } catch (FileNotFoundException e) {
+ // Best effort
+ }
+
+ try {
+ FileOrDirectory.unsetPermissions(fifo, new FileLocation(fifo), Permission.READ
+ | Permission.WRITE, ChangePermission.OWNER);
+ } catch (CannotSetPermissionException e) {
+ logger.log(Level.SEVERE, e.getMessage(), e);
+ abort();
+ }
+
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // Best effort
+ }
+ }
+ }
+
+ private static void abort() {
+ System.exit(1);
+ }
+
+ @CheckForNull
+ private static Timer timer;
+
+ @Nonnull
+ private static Object lockTimer = new Object();
+
+ private static void startTimer() {
+ synchronized (lockTimer) {
+ if (timer != null) {
+ cancelTimer();
+ }
+
+ logger.log(Level.INFO, "Start timer");
+
+ timer = new Timer("jack-server-timeout");
+ timer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ shutdownFifo();
+ cancelTimer();
+ }
+ }, timeout);
+ }
+ }
+
+ private static void cancelTimer() {
+ synchronized (lockTimer) {
+ if (timer != null) {
+ logger.log(Level.INFO, "Cancel timer");
+
+ timer.cancel();
+ timer.purge();
+ timer = null;
+ }
+ }
+ }
+}
diff --git a/jack/src/com/android/jack/server/ServerTask.java b/jack/src/com/android/jack/server/ServerTask.java
new file mode 100644
index 00000000..f7674b5e
--- /dev/null
+++ b/jack/src/com/android/jack/server/ServerTask.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.jack.server;
+
+import com.android.sched.util.config.cli.TokenIterator;
+
+import java.io.File;
+import java.io.PrintStream;
+
+import javax.annotation.Nonnull;
+
+/**
+ * STOPSHIP
+ */
+public interface ServerTask {
+ int run(@Nonnull PrintStream out, @Nonnull PrintStream err, @Nonnull File workingDir,
+ @Nonnull TokenIterator args);
+} \ No newline at end of file
diff --git a/sched/src/com/android/sched/util/file/AbstractStreamFile.java b/sched/src/com/android/sched/util/file/AbstractStreamFile.java
index 2a680d4b..6bd4b942 100644
--- a/sched/src/com/android/sched/util/file/AbstractStreamFile.java
+++ b/sched/src/com/android/sched/util/file/AbstractStreamFile.java
@@ -127,8 +127,8 @@ public abstract class AbstractStreamFile extends FileOrDirectory {
throw new NoSuchFileException(location);
}
- // Check it is a file
- if (!file.isFile()) {
+ // Check if it is not a Directory
+ if (file.isDirectory()) {
throw new NotFileException(location);
}
}
diff --git a/sched/src/com/android/sched/util/file/Directory.java b/sched/src/com/android/sched/util/file/Directory.java
index 55b30dac..2d7f3ae5 100644
--- a/sched/src/com/android/sched/util/file/Directory.java
+++ b/sched/src/com/android/sched/util/file/Directory.java
@@ -96,7 +96,7 @@ public class Directory extends FileOrDirectory {
throw new NoSuchFileException(location);
}
- // Check directory
+ // Check if it is a directory
if (!file.isDirectory()) {
throw new NotDirectoryException(location);
}
diff --git a/sched/src/com/android/sched/util/file/FileOrDirectory.java b/sched/src/com/android/sched/util/file/FileOrDirectory.java
index dde0146b..c7d7dcae 100644
--- a/sched/src/com/android/sched/util/file/FileOrDirectory.java
+++ b/sched/src/com/android/sched/util/file/FileOrDirectory.java
@@ -125,6 +125,44 @@ public abstract class FileOrDirectory implements HasLocation {
}
}
+
+ public static void unsetPermissions(@Nonnull File file, @Nonnull Location location,
+ int permissions, @Nonnull FileOrDirectory.ChangePermission change)
+ throws CannotSetPermissionException {
+ if (change != ChangePermission.NOCHANGE) {
+ // Set access
+ if ((permissions & Permission.READ) != 0) {
+ if (file.setReadable(false, change == ChangePermission.OWNER)) {
+ logger.log(Level.FINE, "Clear readable permission to {0} (''{1}'')",
+ new Object[] {location.getDescription(), file.getAbsoluteFile()});
+ } else {
+ throw new CannotSetPermissionException(location, Permission.READ,
+ change);
+ }
+ }
+
+ if ((permissions & Permission.WRITE) != 0) {
+ if (file.setWritable(false, change == ChangePermission.OWNER)) {
+ logger.log(Level.FINE, "Clear writable permission to {0} (''{1}'')",
+ new Object[] {location.getDescription(), file.getAbsoluteFile()});
+ } else {
+ throw new CannotSetPermissionException(location, Permission.WRITE,
+ change);
+ }
+ }
+
+ if ((permissions & Permission.EXECUTE) != 0) {
+ if (file.setExecutable(false, change == ChangePermission.OWNER)) {
+ logger.log(Level.FINE, "Clear executable permission to {0} (''{1}'')",
+ new Object[] {location.getDescription(), file.getAbsoluteFile()});
+ } else {
+ throw new CannotSetPermissionException(location, Permission.EXECUTE,
+ change);
+ }
+ }
+ }
+ }
+
public static void checkPermissions(@Nonnull File file, @Nonnull Location location,
int permissions) throws WrongPermissionException {
if ((permissions & Permission.READ) != 0) {
diff --git a/sched/src/com/android/sched/util/file/OutputStreamFile.java b/sched/src/com/android/sched/util/file/OutputStreamFile.java
index 055869b1..6956ba13 100644
--- a/sched/src/com/android/sched/util/file/OutputStreamFile.java
+++ b/sched/src/com/android/sched/util/file/OutputStreamFile.java
@@ -83,6 +83,29 @@ public class OutputStreamFile extends AbstractStreamFile {
this.append = false;
}
+ /**
+ * Creates a new instance of {@link OutputStreamFile} assuming the file must exist, without
+ * modifying its permissions. It will be overwritten.
+ */
+ public OutputStreamFile(@Nonnull String name)
+ throws WrongPermissionException, NotFileException {
+ super(name, null);
+
+ try {
+ performChecks(Existence.MUST_EXIST, Permission.WRITE, ChangePermission.NOCHANGE);
+ } catch (NoSuchFileException e) {
+ throw new AssertionError(e);
+ } catch (FileAlreadyExistsException e) {
+ throw new AssertionError(e);
+ } catch (CannotSetPermissionException e) {
+ throw new AssertionError(e);
+ } catch (CannotCreateFileException e) {
+ throw new AssertionError(e);
+ }
+
+ this.append = false;
+ }
+
@Nonnull
private static final Location STANDARD_OUTPUT_LOCATION = new StandardOutputLocation();
@Nonnull