Enable upload of custom builds of the SDK.

Change-Id: I3bc35ff970f0b5338ce2e3e278de926926375126
diff --git a/compileserver/pom.xml b/compileserver/pom.xml
index 84a16f7..a733fb5 100644
--- a/compileserver/pom.xml
+++ b/compileserver/pom.xml
@@ -78,6 +78,16 @@
       <version>1.1.3</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>commons-fileupload</groupId>
+      <artifactId>commons-fileupload</artifactId>
+      <version>1.3.1</version>
+    </dependency>
+    <dependency>
+      <groupId>net.lingala.zip4j</groupId>
+      <artifactId>zip4j</artifactId>
+      <version>1.3.1</version>
+    </dependency>
   </dependencies>
   <build>
     <plugins>
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/CompileServerEntryPoint.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/CompileServerEntryPoint.java
index af3e126..e5dcdc0 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/CompileServerEntryPoint.java
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/CompileServerEntryPoint.java
@@ -20,10 +20,13 @@
 import com.google.gwt.inject.client.AbstractGinModule;
 import com.google.gwt.inject.client.GinModules;
 import com.google.gwt.inject.client.Ginjector;
+import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.RootPanel;
 import com.google.inject.Provides;
 
+import javax.inject.Named;
+
 
 /**
  * EntryPoint for compile server UI.
@@ -45,6 +48,12 @@
     protected Label providesLabel() {
       return new Label();
     }
+
+    @Provides
+    @Named("fileUpload")
+    protected boolean showFileUpload () {
+      return Window.Location.getPath().contains("single.html");
+    }
   }
 
   @GinModules(CompileServerClientModule.class)
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusComposite.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusComposite.java
index 89a4bad..1d5ff65 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusComposite.java
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusComposite.java
@@ -19,6 +19,7 @@
 import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkRunDTO;
 import com.google.gwt.benchmark.compileserver.shared.dto.BenchmarkRunDTO.State;
 import com.google.gwt.core.shared.GWT;
+import com.google.gwt.event.dom.client.ChangeEvent;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.event.shared.HandlerRegistration;
@@ -32,6 +33,10 @@
 import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FileUpload;
+import com.google.gwt.user.client.ui.FormPanel;
+import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent;
+import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
 import com.google.gwt.user.client.ui.Grid;
 import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.Widget;
@@ -41,6 +46,7 @@
 import java.util.List;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 /**
  * A view that displays the status of the compile server.
@@ -98,14 +104,34 @@
   @UiField
   Button startStopButton;
 
+  @UiField
+  FormPanel formPanel;
+
+  @UiField
+  FileUpload fileUpload;
+
+  @UiField
+  Button uploadButton;
+
+  private boolean showFileUpload;
 
   @Inject
   public BenchmarkStatusComposite(ServiceAsync service, NumberFormat format,
-      Provider<Label> labelProvider) {
+      Provider<Label> labelProvider, @Named("fileUpload") boolean showFileUpload) {
     this.service = service;
     this.labelProvider = labelProvider;
     this.format = format;
+    this.showFileUpload = showFileUpload;
     initWidget(uiBinder.createAndBindUi(this));
+    formPanel.setVisible(this.showFileUpload);
+    formPanel.setAction("/compileserver/upload");
+    formPanel.setEncoding(FormPanel.ENCODING_MULTIPART);
+    formPanel.setMethod(FormPanel.METHOD_POST);
+    if (showFileUpload) {
+      startStopButton.setEnabled(false);
+    }
+
+    uploadButton.setEnabled(false);
     bundle.css().ensureInjected();
   }
 
@@ -113,6 +139,28 @@
     loadBenchmarks();
   }
 
+  @UiHandler("fileUpload")
+  protected void onUpLoadChanged(@SuppressWarnings("unused") ChangeEvent event) {
+    uploadButton.setEnabled(!"".equals(fileUpload.getFilename()));
+  }
+
+  @UiHandler("uploadButton")
+  protected void onUploadButtonClicked(@SuppressWarnings("unused") ClickEvent event) {
+    formPanel.submit();
+  }
+
+  @UiHandler("formPanel")
+  public void onSubmitComplete(@SuppressWarnings("unused") SubmitCompleteEvent event) {
+    Window.alert("File: " + event.getResults());
+    uploadButton.setEnabled(true);
+    startStopButton.setEnabled(true);
+  }
+
+  @UiHandler("formPanel")
+  public void onSubmit(@SuppressWarnings("unused") SubmitEvent event) {
+    uploadButton.setEnabled(false);
+  }
+
   @UiHandler("startStopButton")
   protected void onStartButtonPressed(@SuppressWarnings("unused") ClickEvent e) {
 
@@ -130,9 +178,9 @@
     };
 
     if (running) {
-      service.stopServer(callback);
+      service.stopServer(showFileUpload, callback);
     } else {
-      service.startServer(callback);
+      service.startServer(showFileUpload, callback);
     }
   }
 
@@ -154,7 +202,7 @@
     statusText.setText("");
     startStopButton.setVisible(false);
 
-    service.loadBenchmarkOverview(new AsyncCallback<BenchmarkOverviewResponseDTO>() {
+    service.loadBenchmarkOverview(showFileUpload, new AsyncCallback<BenchmarkOverviewResponseDTO>() {
 
         @Override
       public void onFailure(Throwable caught) {
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusComposite.ui.xml b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusComposite.ui.xml
index 4423d5c..5bafebf 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusComposite.ui.xml
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusComposite.ui.xml
@@ -8,5 +8,11 @@
     </g:FlowPanel>
     <g:Label ui:field="errorLabel"/>
     <g:Label ui:field="loadingLabel">Loading...</g:Label>
+    <g:FormPanel ui:field="formPanel">
+      <g:FlowPanel>
+        <g:FileUpload ui:field="fileUpload" name="file"></g:FileUpload>
+        <g:Button ui:field="uploadButton">Upload SDK</g:Button>
+      </g:FlowPanel>
+    </g:FormPanel>
   </g:HTMLPanel>
 </ui:UiBinder>
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/CompileServerGuiceModule.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/CompileServerGuiceModule.java
index 07bcf30..1db527a 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/CompileServerGuiceModule.java
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/guice/CompileServerGuiceModule.java
@@ -14,6 +14,7 @@
 package com.google.gwt.benchmark.compileserver.server.guice;
 
 import com.google.gwt.benchmark.compileserver.server.service.BenchmarkServiceImpl;
+import com.google.gwt.benchmark.compileserver.server.service.FileUploadServlet;
 import com.google.inject.Singleton;
 import com.google.inject.servlet.ServletModule;
 
@@ -50,5 +51,8 @@
 
     bind(BenchmarkServiceImpl.class).in(Singleton.class);
     serve("/compileserver/data/service").with(BenchmarkServiceImpl.class);
+
+    bind(FileUploadServlet.class).in(Singleton.class);
+    serve("/compileserver/upload").with(FileUploadServlet.class);
   }
 }
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkCompiler.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkCompiler.java
index e583088..4d5e02e 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkCompiler.java
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkCompiler.java
@@ -22,5 +22,5 @@
   /**
    * Invokes the GWT compiler for the specified module.
    */
-  public void compile(String moduleName, File outputDir) throws BenchmarkCompilerException;
+  public void compile(String moduleName, File outputDir, File devJar, File userJar) throws BenchmarkCompilerException;
 }
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManager.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManager.java
index 8ba8e00..42aff0d 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManager.java
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManager.java
@@ -21,6 +21,7 @@
 import com.google.inject.Singleton;
 import com.google.inject.name.Named;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -150,7 +151,7 @@
     EXIT, CHECK_FOR_UPDATES, RUN_BENCHMARKS, SUCCESSFUL_RUN, FAILED_RUN
   }
 
-  private enum State {
+  protected enum State {
     IDLE, RUNNING_BENCHMARKS
   }
 
@@ -164,7 +165,7 @@
 
   private long currentCommitDateMsEpoch;
 
-  private boolean currentlyRunning = false;
+  protected boolean currentlyRunning = false;
 
   private BlockingQueue<Command> commands = new LinkedBlockingQueue<>();
 
@@ -182,9 +183,9 @@
 
   private Factory reporterFactory;
 
-  private CliInteractor cliInteractor;
+  protected CliInteractor cliInteractor;
 
-  private State state = State.IDLE;
+  protected State state = State.IDLE;
 
   private Timer timer;
 
@@ -194,6 +195,10 @@
 
   private AtomicInteger workCount = new AtomicInteger();
 
+  protected File devJar;
+
+  protected File userJar;
+
   @Inject
   public BenchmarkManager(BenchmarkFinder collector,
       BenchmarkWorker.Factory benchmarkWorkerFactory,
@@ -202,7 +207,8 @@
       @Named("useReporter") boolean useReporter,
       CliInteractor commitReader,
       Provider<Timer> timerProvider,
-      MailReporter errorReporter) {
+      MailReporter errorReporter,
+      @Named("gwtSourceLocation") File gwtSourceLocation) {
     this.benchmarkFinder = collector;
     this.benchmarkWorkerFactory = benchmarkWorkerFactory;
     this.poolProvider = poolProvider;
@@ -211,6 +217,8 @@
     this.cliInteractor = commitReader;
     this.errorReporter = errorReporter;
     this.timerProvider = timerProvider;
+    devJar = new File(gwtSourceLocation, "build/staging/gwt-0.0.0/gwt-dev.jar");
+    userJar = new File(gwtSourceLocation, "build/staging/gwt-0.0.0/gwt-user.jar");
   }
 
   public String getLastCommitId() {
@@ -292,7 +300,7 @@
     return br;
   }
 
-  private void maybeReportResults(String commitId, long commitMsEpoch) {
+  protected void maybeReportResults(String commitId, long commitMsEpoch) {
     Map<String, BenchmarkRun> results;
     synchronized (benchmarkRunsByNameLock) {
       results = deepClone(benchmarkRunsByName);
@@ -343,7 +351,7 @@
     }
   }
 
-  private void runEventLoop() {
+  protected void runEventLoop() {
     state = State.IDLE;
     // check out last successful commit
     try {
@@ -456,7 +464,7 @@
     }
   }
 
-  private void reportError(String message) {
+  protected void reportError(String message) {
     errorReporter.sendEmail(message);
   }
 
@@ -466,7 +474,7 @@
     }
   }
 
-  private void startBenchmarkingAllForCommit(String commitId, long currentCommitDateMsEpoch) {
+  protected void startBenchmarkingAllForCommit(String commitId, long currentCommitDateMsEpoch) {
 
     pool = poolProvider.get();
 
@@ -491,8 +499,8 @@
 
       ProgressHandler progressHandler = new ThreadSafeProgressHandler(br);
 
-      BenchmarkWorker worker =
-          benchmarkWorkerFactory.create(BenchmarkWorkerConfig.from(br), progressHandler);
+      BenchmarkWorker worker = benchmarkWorkerFactory.create(
+          BenchmarkWorkerConfig.from(br, devJar, userJar), progressHandler);
       workCount.incrementAndGet();
 
       pool.execute(worker);
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorker.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorker.java
index 1c94546..e60b35b 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorker.java
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorker.java
@@ -96,7 +96,8 @@
     }
 
     try {
-      compiler.compile(benchmarkData.getModuleName(), outputDir);
+      compiler.compile(benchmarkData.getModuleName(), outputDir, benchmarkData.getDevJar(),
+          benchmarkData.getUserJar());
     } catch (BenchmarkCompilerException e) {
       cleanupDirectory(outputDir);
       progressHandler.onCompilationFailed(e.getMessage() + " " + e.getOutput());
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorkerConfig.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorkerConfig.java
index debafcd..c8aa9fc 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorkerConfig.java
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorkerConfig.java
@@ -13,6 +13,7 @@
  */
 package com.google.gwt.benchmark.compileserver.server.manager;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -22,15 +23,20 @@
  * runners that the worker should launch.
  */
 public class BenchmarkWorkerConfig {
-  public static BenchmarkWorkerConfig from(BenchmarkRun run) {
-    return new BenchmarkWorkerConfig(run.getModuleName(), run.getRunConfigs());
+  public static BenchmarkWorkerConfig from(BenchmarkRun run, File devJar, File userJar) {
+    return new BenchmarkWorkerConfig(run.getModuleName(), run.getRunConfigs(), devJar, userJar);
   }
 
   private final String moduleName;
   private final List<RunnerConfig> runnerConfigs;
+  private final File devJar;
+  private final File userJar;
 
-  public BenchmarkWorkerConfig(String moduleName, List<RunnerConfig> runners) {
+  public BenchmarkWorkerConfig(String moduleName, List<RunnerConfig> runners, File devJar,
+      File userJar) {
     this.moduleName = moduleName;
+    this.devJar = devJar;
+    this.userJar = userJar;
     this.runnerConfigs = new ArrayList<>(runners);
   }
 
@@ -41,4 +47,12 @@
   public List<RunnerConfig> getRunners() {
     return Collections.unmodifiableList(runnerConfigs);
   }
+
+  public File getDevJar() {
+    return devJar;
+  }
+
+  public File getUserJar() {
+    return userJar;
+  }
 }
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/CliInteractor.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/CliInteractor.java
index 07df0ff..72bfe7d 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/CliInteractor.java
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/CliInteractor.java
@@ -67,14 +67,11 @@
   }
 
   @Override
-  public void compile(String moduleName, File compilerOutputDir) throws BenchmarkCompilerException {
+  public void compile(String moduleName, File compilerOutputDir, File devJar, File userJar)
+      throws BenchmarkCompilerException {
     logger.info("compiling: " + moduleName);
     File compileScript = new File(scriptDirectory, "compileModule");
 
-    String devjar =
-        new File(gwtSourceLocation, "build/staging/gwt-0.0.0/gwt-dev.jar").getAbsolutePath();
-    String userjar =
-        new File(gwtSourceLocation, "build/staging/gwt-0.0.0/gwt-user.jar").getAbsolutePath();
     String bsl = benchmarkSourceLocation.getAbsolutePath();
     if (!bsl.endsWith("/")) {
       bsl += "/";
@@ -82,8 +79,8 @@
 
     String outputDir = compilerOutputDir.getAbsolutePath();
     try {
-      runCommand(compileScript.getAbsolutePath() + " " + moduleName + " " + devjar + " " + userjar
-          + " " + bsl + " " + outputDir, false);
+      runCommand(compileScript.getAbsolutePath() + " " + moduleName + " " + devJar.getAbsolutePath()
+          + " " + userJar.getAbsolutePath() + " " + bsl + " " + outputDir, false);
     } catch (BenchmarkManagerException e) {
       throw new BenchmarkCompilerException("failed compile", e);
     }
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/SingleRunBenchmarkManager.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/SingleRunBenchmarkManager.java
new file mode 100644
index 0000000..9551b5f
--- /dev/null
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/manager/SingleRunBenchmarkManager.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2015 Google 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 com.google.gwt.benchmark.compileserver.server.manager;
+
+import com.google.inject.Provider;
+import com.google.inject.name.Named;
+
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.util.Timer;
+import java.util.concurrent.ExecutorService;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+/**
+ * SingleRunBenchmarkManager allows for a single (updloaded) versino of the SDK to be benchmarked.
+ */
+public class SingleRunBenchmarkManager extends BenchmarkManager {
+
+  private static final Logger logger = Logger.getLogger(BenchmarkManager.class.getName());
+  private File sdkFolder;
+
+  @Inject
+  public SingleRunBenchmarkManager(BenchmarkFinder collector,
+      BenchmarkWorker.Factory benchmarkWorkerFactory,
+      @Named("managerPoolSize") Provider<ExecutorService> poolProvider,
+      BenchmarkReporter.Factory reporterFactory,
+      @Named("useReporter") boolean useReporter,
+      CliInteractor commitReader,
+      Provider<Timer> timerProvider,
+      MailReporter errorReporter,
+      @Named("gwtSourceLocation") File gwtSourceLocation) {
+    super(collector, benchmarkWorkerFactory, poolProvider, reporterFactory, useReporter,
+        commitReader, timerProvider, errorReporter, gwtSourceLocation);
+  }
+
+  @Override
+  protected void runEventLoop() {
+    logger.info("Starting benchmark runners");
+    startBenchmarkingAllForCommit("no_commit", System.currentTimeMillis());
+  }
+
+  public void setSDKDir(File sdkFolder) {
+    // This is a dirty hack to get rid of files of an old run
+    // but for now this is good enough...
+    if (this.sdkFolder != null) {
+      FileUtils.deleteQuietly(this.sdkFolder);
+    }
+
+    this.sdkFolder = sdkFolder;
+    if (!sdkFolder.isDirectory()) {
+      throw new IllegalArgumentException(
+          "sdkFolder does not point to a folder: " + sdkFolder.getAbsolutePath());
+    }
+
+    devJar = new File(sdkFolder, "gwt-dev.jar");
+    if (!devJar.exists()) {
+      throw new IllegalArgumentException("Can not find dev jar");
+    }
+    userJar = new File(sdkFolder, "gwt-user.jar");
+    if (!userJar.exists()) {
+      throw new IllegalArgumentException("Can not find user jar");
+    }
+  }
+
+  @Override
+  protected void maybeReportResults(String commitId, long commitMsEpoch) {
+    currentlyRunning = false;
+  }
+}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/service/BenchmarkServiceImpl.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/service/BenchmarkServiceImpl.java
index 4b7fbac..3d844e6 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/service/BenchmarkServiceImpl.java
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/service/BenchmarkServiceImpl.java
@@ -16,6 +16,7 @@
 import com.google.gwt.benchmark.common.shared.service.ServiceException;
 import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkManager;
 import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkRun;
+import com.google.gwt.benchmark.compileserver.server.manager.SingleRunBenchmarkManager;
 import com.google.gwt.benchmark.compileserver.server.manager.BenchmarkRun.Result;
 import com.google.gwt.benchmark.compileserver.server.manager.RunnerConfig;
 import com.google.gwt.benchmark.compileserver.shared.Service;
@@ -106,19 +107,26 @@
 
   private BenchmarkManager benchmarkManager;
 
+  private SingleRunBenchmarkManager singleRunBenchmarkManager;
+
   @Inject
-  public BenchmarkServiceImpl(BenchmarkManager benchmarkManager) {
+  public BenchmarkServiceImpl(BenchmarkManager benchmarkManager,
+      SingleRunBenchmarkManager singleRunBenchmarkManager) {
     this.benchmarkManager = benchmarkManager;
+    this.singleRunBenchmarkManager = singleRunBenchmarkManager;
   }
 
   @Override
-  public BenchmarkOverviewResponseDTO loadBenchmarkOverview() throws ServiceException {
+  public BenchmarkOverviewResponseDTO loadBenchmarkOverview(boolean single) throws ServiceException {
+
+    BenchmarkManager manager = single ? singleRunBenchmarkManager : benchmarkManager;
+
     try {
       BenchmarkOverviewResponseDTO response = new BenchmarkOverviewResponseDTO();
-      response.setRunnerNames(createRunnerDTOs(benchmarkManager.getAllRunners()));
+      response.setRunnerNames(createRunnerDTOs(manager.getAllRunners()));
 
-      Map<String, BenchmarkRun> latestRun = benchmarkManager.getLatestRun();
-      response.setExecutingBenchmarks(benchmarkManager.isRunning());
+      Map<String, BenchmarkRun> latestRun = manager.getLatestRun();
+      response.setExecutingBenchmarks(manager.isRunning());
 
       if (latestRun == null || latestRun.isEmpty()) {
         response.setHasLatestRun(false);
@@ -140,12 +148,31 @@
   }
 
   @Override
-  public void startServer() {
-    benchmarkManager.start();
+  public void startServer(boolean single) throws ServiceException {
+    try {
+      if (single) {
+        singleRunBenchmarkManager.setSDKDir(FileUploadServlet.getSdkFolder());
+        singleRunBenchmarkManager.start();
+      } else {
+        benchmarkManager.start();
+      }
+    } catch (Exception e) {
+      logger.log(Level.WARNING, "failed to start server", e);
+      throw new ServiceException("Failed to start server");
+    }
   }
 
   @Override
-  public void stopServer() {
-    benchmarkManager.stop();
+  public void stopServer(boolean single) throws ServiceException {
+    try {
+      if (single) {
+        singleRunBenchmarkManager.stop();
+      } else {
+        benchmarkManager.stop();
+      }
+    } catch (Exception e) {
+      logger.log(Level.WARNING, "failed to start server", e);
+      throw new ServiceException("Failed to start server");
+    }
   }
 }
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/service/FileUploadServlet.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/service/FileUploadServlet.java
new file mode 100644
index 0000000..8399aaf
--- /dev/null
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/server/service/FileUploadServlet.java
@@ -0,0 +1,79 @@
+package com.google.gwt.benchmark.compileserver.server.service;
+
+import com.google.inject.Provider;
+
+import net.lingala.zip4j.core.ZipFile;
+import net.lingala.zip4j.exception.ZipException;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+import org.apache.commons.io.IOUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet for SDK uploads.
+ */
+public class FileUploadServlet extends HttpServlet {
+
+  private final Provider<String> randomStringProvider;
+
+  private static File sdkFolder;
+
+  public static File getSdkFolder() {
+    return sdkFolder;
+  }
+
+  @Inject
+  public FileUploadServlet(@Named("randomStringProvider") Provider<String> randomStringProvider) {
+    this.randomStringProvider = randomStringProvider;
+  }
+
+  @Override
+  protected void doPost(HttpServletRequest request, HttpServletResponse response)
+      throws ServletException, IOException {
+    try {
+      List<FileItem> items = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);
+      for (FileItem item : items) {
+        if (!item.isFormField()) {
+          InputStream fileContent = item.getInputStream();
+          String randomString = randomStringProvider.get();
+          File tempFile = File.createTempFile(randomString + "-gwt-sdk", ".zip");
+          FileOutputStream tempOutputStream = new FileOutputStream(tempFile);
+          File folder = new File(randomString + "-gwt-sdk/");
+          folder.mkdirs();
+
+          try {
+            IOUtils.copy(fileContent, tempOutputStream);
+            tempOutputStream.close();
+
+            ZipFile zipFile = new ZipFile(tempFile.getAbsolutePath());
+            zipFile.extractAll(folder.getAbsolutePath());
+          } catch (ZipException | IOException e ) {
+           throw new ServletException(e);
+          } finally {
+            tempFile.delete();
+          }
+
+          sdkFolder = new File(folder, "gwt-0.0.0");
+          response.getWriter().write(folder.getAbsolutePath());
+        }
+      }
+    } catch (FileUploadException e) {
+      throw new ServletException("Upload failed.", e);
+    }
+  }
+}
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/Service.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/Service.java
index e6ff809..da7402e 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/Service.java
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/Service.java
@@ -24,9 +24,9 @@
 @RemoteServiceRelativePath("data/service")
 public interface Service extends RemoteService {
 
-  BenchmarkOverviewResponseDTO loadBenchmarkOverview() throws ServiceException;
+  BenchmarkOverviewResponseDTO loadBenchmarkOverview(boolean single) throws ServiceException;
 
-  void startServer();
+  void startServer(boolean single) throws ServiceException;
 
-  void stopServer();
+  void stopServer(boolean single) throws ServiceException;
 }
diff --git a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/ServiceAsync.java b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/ServiceAsync.java
index 1789575..a7cec5f 100644
--- a/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/ServiceAsync.java
+++ b/compileserver/src/main/java/com/google/gwt/benchmark/compileserver/shared/ServiceAsync.java
@@ -20,9 +20,9 @@
  * Service interface to be injected into every object that needs data from the server.
  */
 public interface ServiceAsync {
-  void loadBenchmarkOverview(AsyncCallback<BenchmarkOverviewResponseDTO> callback);
+  void loadBenchmarkOverview(boolean single, AsyncCallback<BenchmarkOverviewResponseDTO> callback);
 
-  void startServer(AsyncCallback<Void> callback);
+  void startServer(boolean single, AsyncCallback<Void> callback);
 
-  void stopServer(AsyncCallback<Void> callback);
+  void stopServer(boolean single, AsyncCallback<Void> callback);
 }
diff --git a/compileserver/src/main/webapp/single.html b/compileserver/src/main/webapp/single.html
new file mode 100644
index 0000000..82f0f21
--- /dev/null
+++ b/compileserver/src/main/webapp/single.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html>
+  <head>
+    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+    <title>CompileServer</title>
+    <script type="text/javascript" src="compileserver/compileserver.nocache.js"></script>
+  </head>
+  <body>
+  </body>
+</html>
diff --git a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusCompositeTest.java b/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusCompositeTest.java
index 191df6c..ea3ef78 100644
--- a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusCompositeTest.java
+++ b/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/client/status/BenchmarkStatusCompositeTest.java
@@ -13,6 +13,7 @@
  */
 package com.google.gwt.benchmark.compileserver.client.status;
 
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -63,7 +64,7 @@
   @Before
   public void setup() {
     messages.clear();
-    composite = new BenchmarkStatusComposite(service, numberFormat, labelProvider) {
+    composite = new BenchmarkStatusComposite(service, numberFormat, labelProvider, false) {
       @Override
       void alert(String message) {
         messages.add(message);
@@ -94,7 +95,7 @@
     verify(composite.statusText).setText("");
     verify(composite.startStopButton).setVisible(false);
 
-    verify(service).loadBenchmarkOverview(asyncCaptor.capture());
+    verify(service).loadBenchmarkOverview(eq(false), asyncCaptor.capture());
 
     AsyncCallback<BenchmarkOverviewResponseDTO> asyncCallback = asyncCaptor.getValue();
 
@@ -126,7 +127,7 @@
     verify(composite.statusText).setText("");
     verify(composite.startStopButton).setVisible(false);
 
-    verify(service).loadBenchmarkOverview(asyncCaptor.capture());
+    verify(service).loadBenchmarkOverview(eq(false), asyncCaptor.capture());
 
     AsyncCallback<BenchmarkOverviewResponseDTO> asyncCallback = asyncCaptor.getValue();
 
diff --git a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManagerTest.java b/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManagerTest.java
index 866350e..0a1100f 100644
--- a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManagerTest.java
+++ b/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkManagerTest.java
@@ -25,6 +25,7 @@
 import org.mockito.Mockito;
 import org.mockito.verification.VerificationWithTimeout;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -51,7 +52,8 @@
         boolean useReporter,
         CliInteractor commitReader,
         Provider<Timer> timerProvider,
-        MailReporter errorReporter) {
+        MailReporter errorReporter,
+        File gwtSourceLocation) {
       super(collector,
           benchmarkWorkerFactory,
           poolProvider,
@@ -59,7 +61,8 @@
           useReporter,
           commitReader,
           timerProvider,
-          errorReporter);
+          errorReporter,
+          gwtSourceLocation);
     }
 
     @Override
@@ -85,6 +88,7 @@
   private BenchmarkReporter benchmarkReporter;
   private MailReporter errorReporter;
   private Timer timer;
+  private File gwtSourceLocation;
 
   @Before
   public void setup() {
@@ -99,6 +103,7 @@
     benchmarkReporter = Mockito.mock(BenchmarkReporter.class);
     errorReporter = Mockito.mock(MailReporter.class);
     timer = Mockito.mock(Timer.class);
+    gwtSourceLocation = new File("");
   }
 
   @Test
@@ -113,7 +118,8 @@
         true,
         commitReader,
         timerProvider,
-        errorReporter);
+        errorReporter,
+        gwtSourceLocation);
 
     Mockito.when(commitReader.getLastCommitId()).thenReturn("commit1");
     Mockito.when(commitReader.getCurrentCommitId()).thenReturn("commit2");
@@ -260,7 +266,8 @@
         true,
         commitReader,
         timerProvider,
-        errorReporter);
+        errorReporter,
+        gwtSourceLocation);
 
     Mockito.when(commitReader.getLastCommitId()).thenThrow(new BenchmarkManagerException(""));
 
@@ -290,7 +297,8 @@
         true,
         commitReader,
         timerProvider,
-        errorReporter);
+        errorReporter,
+        gwtSourceLocation);
 
     Mockito.when(commitReader.getDateForCommitInMsEpoch(Mockito.anyString())).thenThrow(
         new BenchmarkManagerException(""));
@@ -321,7 +329,8 @@
         true,
         commitReader,
         timerProvider,
-        errorReporter);
+        errorReporter,
+        gwtSourceLocation);
 
     Mockito.doThrow(new BenchmarkManagerException("")).when(commitReader)
         .checkout(Mockito.anyString());
@@ -354,7 +363,8 @@
         true,
         commitReader,
         timerProvider,
-        errorReporter);
+        errorReporter,
+        gwtSourceLocation);
 
     Mockito.when(commitReader.getLastCommitId()).thenReturn("commit1");
     Mockito.when(commitReader.getCurrentCommitId()).thenReturn("commit2");
@@ -453,7 +463,8 @@
         true,
         commitReader,
         timerProvider,
-        errorReporter);
+        errorReporter,
+        gwtSourceLocation);
 
     Mockito.when(commitReader.getLastCommitId()).thenReturn("commit1");
     Mockito.when(commitReader.getCurrentCommitId()).thenReturn("commit2");
@@ -553,7 +564,8 @@
         true,
         commitReader,
         timerProvider,
-        errorReporter) {
+        errorReporter,
+        gwtSourceLocation) {
 
       @Override
       void addBenchmarkRunSynchronized(Map<String, BenchmarkRun> runsByName, BenchmarkRun br) {
diff --git a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorkerTest.java b/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorkerTest.java
index 7b01ff4..6b18b14 100644
--- a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorkerTest.java
+++ b/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/BenchmarkWorkerTest.java
@@ -17,6 +17,8 @@
 import com.google.gwt.benchmark.compileserver.server.manager.Runner.Factory;
 import com.google.inject.Provider;
 
+import static org.mockito.Mockito.mock;
+
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 import org.junit.After;
@@ -49,6 +51,8 @@
   private Provider<String> randomStringProvider;
   private String moduleTemplate;
   private BenchmarkWorkerConfig benchmarkData;
+  private File devJar;
+  private File userJar;
 
   @Before
   public void setup() {
@@ -76,7 +80,12 @@
 
     moduleTemplate = "{module_nocache}";
 
-    benchmarkData = new BenchmarkWorkerConfig(moduleName, Arrays.asList(runnerConfig));
+
+    devJar = mock(File.class);
+    userJar = mock(File.class);
+
+    benchmarkData =
+        new BenchmarkWorkerConfig(moduleName, Arrays.asList(runnerConfig), devJar, userJar);
 
     progressHandler = Mockito.mock(ProgressHandler.class);
 
@@ -101,8 +110,8 @@
 
     String moduleTemplate = "mytemplate [{module_nocache}]";
     String moduleName = "moduleName1";
-    BenchmarkWorkerConfig benchmarkData =
-        new BenchmarkWorkerConfig(moduleName, Arrays.asList(RunnerConfigs.CHROME_LINUX));
+    BenchmarkWorkerConfig benchmarkData = new BenchmarkWorkerConfig(moduleName,
+        Arrays.asList(RunnerConfigs.CHROME_LINUX), devJar, userJar);
 
     ProgressHandler progressHandler = Mockito.mock(ProgressHandler.class);
     String ip = "127.0.0.1";
@@ -113,11 +122,12 @@
         benchmarkData, progressHandler, ip, 8080, benchmarkCompileOutputDir, randomStringProvider);
 
     Mockito.doThrow(new BenchmarkCompilerException("test")).when(compiler)
-        .compile(moduleName, workDir);
+        .compile(moduleName, workDir, devJar, userJar);
 
     worker.run();
 
-    Mockito.verify(compiler).compile(Mockito.eq(moduleName), Mockito.<File>anyObject());
+    Mockito.verify(compiler).compile(Mockito.eq(moduleName), Mockito.<File> anyObject(),
+        Mockito.eq(devJar), Mockito.eq(userJar));
     Mockito.verify(progressHandler).onCompilationFailed(Mockito.anyString());
     Mockito.verifyZeroInteractions(runnerProvider);
     Assert.assertFalse(workDir.exists());
@@ -140,7 +150,7 @@
 
     File workDir = new File(benchmarkCompileOutputDir, "randomDir1");
 
-    Mockito.verify(compiler).compile(moduleName, workDir);
+    Mockito.verify(compiler).compile(moduleName, workDir, devJar, userJar);
 
     Mockito.verify(runnerProvider).create(runnerConfig,
         "http://" + ip + ":" + port + "/__bench/randomDir1/" + moduleName + ".html");
@@ -166,7 +176,7 @@
     worker.run();
     File workDir = new File(benchmarkCompileOutputDir, "randomDir1");
 
-    Mockito.verify(compiler).compile(moduleName, workDir);
+    Mockito.verify(compiler).compile(moduleName, workDir, devJar, userJar);
 
     Mockito.verify(runnerProvider).create(runnerConfig,
         "http://" + ip + ":" + port + "/__bench/randomDir1/" + moduleName + ".html");
@@ -204,7 +214,7 @@
 
     File workDir = new File(benchmarkCompileOutputDir, "randomDir1");
 
-    Mockito.verify(compiler).compile(moduleName, workDir);
+    Mockito.verify(compiler).compile(moduleName, workDir, devJar, userJar);
 
     Mockito.verify(runnerProvider).create(runnerConfig,
         "http://" + ip + ":" + port + "/__bench/randomDir1/" + moduleName + ".html");
diff --git a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/CliInteractorTest.java b/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/CliInteractorTest.java
index 983c99e..0c40a61 100644
--- a/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/CliInteractorTest.java
+++ b/compileserver/src/test/java/com/google/gwt/benchmark/compileserver/server/manager/CliInteractorTest.java
@@ -47,6 +47,8 @@
   private File compilerOutputDir;
   private CliInteractor scriptInteractor;
   private File scriptDirectoryFail;
+  private File devJar;
+  private File userJar;
 
   @Before
   public void setup() {
@@ -57,6 +59,8 @@
     gwtSourceLocation = new File("./target/fakesource/");
     benchmarkSourceLocation = new File("./target/fakebenchmark/");
     compilerOutputDir = new File("./target/compilerout/");
+    devJar = new File(gwtSourceLocation, "build/staging/gwt-0.0.0/gwt-dev.jar");
+    userJar = new File(gwtSourceLocation, "build/staging/gwt-0.0.0/gwt-user.jar");
 
     scriptInteractor = new CliInteractor(scriptDirectory, persistenceDir, gwtSourceLocation,
         benchmarkSourceLocation);
@@ -65,7 +69,7 @@
 
   @Test
   public void testCompileModule() throws BenchmarkCompilerException, IOException {
-    scriptInteractor.compile("myModule1", compilerOutputDir);
+    scriptInteractor.compile("myModule1", compilerOutputDir, devJar, userJar);
 
     FileInputStream inputStream = null;
     try {
@@ -79,11 +83,11 @@
 
       Assert.assertEquals("myModule1", split[0]);
       Assert.assertEquals(
-          new File(gwtSourceLocation, "build/staging/gwt-0.0.0/gwt-dev.jar").getAbsolutePath(),
+          devJar.getAbsolutePath(),
           new File(split[1]).getAbsolutePath());
 
       Assert.assertEquals(
-          new File(gwtSourceLocation, "build/staging/gwt-0.0.0/gwt-user.jar").getAbsolutePath(),
+          userJar.getAbsolutePath(),
           new File(split[2]).getAbsolutePath());
 
       Assert.assertEquals(benchmarkSourceLocation.getAbsolutePath(),
@@ -100,7 +104,7 @@
     scriptInteractor = new CliInteractor(scriptDirectoryFail, persistenceDir, gwtSourceLocation,
         benchmarkSourceLocation);
     try {
-      scriptInteractor.compile("myModule1", compilerOutputDir);
+      scriptInteractor.compile("myModule1", compilerOutputDir, devJar, userJar);
       Assert.fail("Expected exception did not occur");
     } catch (BenchmarkCompilerException e) {
       Assert.assertEquals("Command returned with 1 This is my errormessage!\n",