private static void screenCaptureToFile(AbstractBuild build, AndroidDeviceContext device)
     throws IOException, InterruptedException {
   FilePath screencapFile = build.getWorkspace().createTempFile("screencap", ".png");
   OutputStream screencapStream = screencapFile.write();
   FilterOutputStream replaceFilterStream = new ReplaceFilterOutputStream(screencapStream);
   device.screenshot(replaceFilterStream);
   screencapFile.copyTo(new FilePath(build.getArtifactsDir()).child("screencap.png"));
 }
  private String discoverAndroidSdkHome(
      AbstractBuild build, Launcher launcher, BuildListener listener) {
    final EnvVars envVars = Utils.getEnvironment(build, listener);
    final Map<String, String> buildVars = build.getBuildVariables();

    // SDK location
    Node node = Computer.currentComputer().getNode();
    String androidHome = Utils.expandVariables(envVars, buildVars, descriptor.androidHome);
    androidHome = SdkUtils.discoverAndroidHome(launcher, node, envVars, androidHome);
    return androidHome;
  }
  private String getNodeDefinedMavenHome(AbstractBuild<?, ?> build) {
    Node currentNode = build.getBuiltOn();
    DescribableList<NodeProperty<?>, NodePropertyDescriptor> properties =
        currentNode.getNodeProperties();
    ToolLocationNodeProperty toolLocation = properties.get(ToolLocationNodeProperty.class);
    if (toolLocation != null) {

      List<ToolLocationNodeProperty.ToolLocation> locations = toolLocation.getLocations();
      if (locations != null) {
        for (ToolLocationNodeProperty.ToolLocation location : locations) {
          if (location.getType().isSubTypeOf(Maven.MavenInstallation.class)) {
            String installationHome = location.getHome();
            if (StringUtils.isNotBlank(installationHome)) {
              return installationHome;
            }
          }
        }
      }
    }
    return null;
  }
  @Override
  public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener)
      throws InterruptedException, IOException {
    EnvVars env = build.getEnvironment(listener);
    FilePath workDir = build.getModuleRoot();
    ArgumentListBuilder cmdLine = buildMavenCmdLine(build, listener, env);
    StringBuilder javaPathBuilder = new StringBuilder();

    JDK configuredJdk = build.getProject().getJDK();
    if (configuredJdk != null) {
      javaPathBuilder
          .append(build.getProject().getJDK().getBinDir().getCanonicalPath())
          .append(File.separator);
    }
    javaPathBuilder.append("java");
    if (!launcher.isUnix()) {
      javaPathBuilder.append(".exe");
    }
    String[] cmds = cmdLine.toCommandArray();
    try {
      // listener.getLogger().println("Executing: " + cmdLine.toStringWithQuote());
      int exitValue =
          launcher
              .launch()
              .cmds(new File(javaPathBuilder.toString()), cmds)
              .envs(env)
              .stdout(listener)
              .pwd(workDir)
              .join();
      boolean success = (exitValue == 0);
      build.setResult(success ? Result.SUCCESS : Result.FAILURE);
      return success;
    } catch (IOException e) {
      Util.displayIOException(e, listener);
      e.printStackTrace(listener.fatalError("command execution failed"));
      build.setResult(Result.FAILURE);
      return false;
    }
  }
  private ArgumentListBuilder buildMavenCmdLine(
      AbstractBuild<?, ?> build, BuildListener listener, EnvVars env)
      throws IOException, InterruptedException {

    FilePath mavenHome = getMavenHomeDir(build, listener, env);

    if (!mavenHome.exists()) {
      listener.error("Couldn't find Maven home: " + mavenHome.getRemote());
      throw new Run.RunnerAbortedException();
    }

    ArgumentListBuilder args = new ArgumentListBuilder();

    FilePath mavenBootDir = new FilePath(mavenHome, "boot");
    FilePath[] classworldsCandidates = mavenBootDir.list("plexus-classworlds*.jar");
    if (classworldsCandidates == null || classworldsCandidates.length == 0) {
      listener.error("Couldn't find classworlds jar under " + mavenBootDir.getRemote());
      throw new Run.RunnerAbortedException();
    }

    FilePath classWorldsJar = classworldsCandidates[0];

    // classpath
    args.add("-classpath");
    // String cpSeparator = launcher.isUnix() ? ":" : ";";

    args.add(classWorldsJar.getRemote());

    // maven home
    args.addKeyValuePair("-D", "maven.home", mavenHome.getRemote(), false);

    String buildInfoPropertiesFile = env.get(BuildInfoConfigProperties.PROP_PROPS_FILE);
    boolean artifactoryIntegration = StringUtils.isNotBlank(buildInfoPropertiesFile);
    listener
        .getLogger()
        .println("Artifactory integration is " + (artifactoryIntegration ? "enabled" : "disabled"));
    String classworldsConfPath;
    if (artifactoryIntegration) {

      args.addKeyValuePair(
          "-D", BuildInfoConfigProperties.PROP_PROPS_FILE, buildInfoPropertiesFile, false);

      // use the classworlds conf packaged with this plugin and resolve the extractor libs
      File maven3ExtractorJar = Which.jarFile(Maven3BuildInfoLogger.class);
      FilePath actualDependencyDirectory =
          PluginDependencyHelper.getActualDependencyDirectory(build, maven3ExtractorJar);

      if (getMavenOpts() == null || !getMavenOpts().contains("-Dm3plugin.lib")) {
        args.addKeyValuePair("-D", "m3plugin.lib", actualDependencyDirectory.getRemote(), false);
      }

      URL classworldsResource =
          getClass()
              .getClassLoader()
              .getResource("org/jfrog/hudson/maven3/classworlds-freestyle.conf");

      File classworldsConfFile =
          new File(URLDecoder.decode(classworldsResource.getFile(), "utf-8"));
      if (!classworldsConfFile.exists()) {
        listener.error(
            "Unable to locate classworlds configuration file under "
                + classworldsConfFile.getAbsolutePath());
        throw new Run.RunnerAbortedException();
      }

      // If we are on a remote slave, make a temp copy of the customized classworlds conf
      if (Computer.currentComputer() instanceof SlaveComputer) {

        FilePath remoteClassworlds =
            build.getWorkspace().createTextTempFile("classworlds", "conf", "", false);
        remoteClassworlds.copyFrom(classworldsResource);
        classworldsConfPath = remoteClassworlds.getRemote();
      } else {
        classworldsConfPath = classworldsConfFile.getCanonicalPath();
      }
    } else {
      classworldsConfPath = new FilePath(mavenHome, "bin/m2.conf").getRemote();
    }

    args.addKeyValuePair("-D", "classworlds.conf", classworldsConfPath, false);

    // maven opts
    if (StringUtils.isNotBlank(getMavenOpts())) {
      String mavenOpts = Util.replaceMacro(getMavenOpts(), build.getBuildVariableResolver());
      args.add(mavenOpts);
    }

    // classworlds launcher main class
    args.add(CLASSWORLDS_LAUNCHER);

    // pom file to build
    String rootPom = getRootPom();
    if (StringUtils.isNotBlank(rootPom)) {
      args.add("-f", rootPom);
    }

    // maven goals
    args.addTokenized(getGoals());

    return args;
  }
  @Override
  public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) {
    // This method deserves a refactor and cleanup.
    boolean success = true;
    Log log = new Log(listener);
    if (Result.FAILURE.equals(build.getResult())) {
      log.info("Not deploying due to job being in FAILED state.");
      return success;
    }

    logStartHeader(log);
    // todo: getting from descriptor is ugly. refactor?
    getDescriptorImpl().setGlobalConfiguration();
    OctopusApi api = getDescriptorImpl().api;

    VariableResolver resolver = build.getBuildVariableResolver();
    EnvVars envVars;
    try {
      envVars = build.getEnvironment(listener);
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Failed to retrieve environment variables for this build - '%s'", ex.getMessage()));
      return false;
    }
    EnvironmentVariableValueInjector envInjector =
        new EnvironmentVariableValueInjector(resolver, envVars);
    // NOTE: hiding the member variables of the same name with their env-injected equivalents
    String project = envInjector.injectEnvironmentVariableValues(this.project);
    String releaseVersion = envInjector.injectEnvironmentVariableValues(this.releaseVersion);
    String environment = envInjector.injectEnvironmentVariableValues(this.environment);
    String variables = envInjector.injectEnvironmentVariableValues(this.variables);

    com.octopusdeploy.api.Project p = null;
    try {
      p = api.getProjectByName(project);
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Retrieving project name '%s' failed with message '%s'", project, ex.getMessage()));
      success = false;
    }
    com.octopusdeploy.api.Environment env = null;
    try {
      env = api.getEnvironmentByName(environment);
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Retrieving environment name '%s' failed with message '%s'",
              environment, ex.getMessage()));
      success = false;
    }
    if (p == null) {
      log.fatal("Project was not found.");
      success = false;
    }
    if (env == null) {
      log.fatal("Environment was not found.");
      success = false;
    }
    if (!success) // Early exit
    {
      return success;
    }
    Set<com.octopusdeploy.api.Release> releases = null;
    try {
      releases = api.getReleasesForProject(p.getId());
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Retrieving releases for project '%s' failed with message '%s'",
              project, ex.getMessage()));
      success = false;
    }
    if (releases == null) {
      log.fatal("Releases was not found.");
      return false;
    }
    Release releaseToDeploy = null;
    for (Release r : releases) {
      if (releaseVersion.equals(r.getVersion())) {
        releaseToDeploy = r;
        break;
      }
    }
    if (releaseToDeploy == null) // early exit
    {
      log.fatal(
          String.format(
              "Unable to find release version %s for project %s", releaseVersion, project));
      return false;
    }
    Properties properties = new Properties();
    try {
      properties.load(new StringReader(variables));
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Unable to load entry variables failed with message '%s'", ex.getMessage()));
      success = false;
    }

    // TODO: Can we tell if we need to call? For now I will always try and get variable and use if I
    // find them
    Set<com.octopusdeploy.api.Variable> variablesForDeploy = null;

    try {
      String releaseId = releaseToDeploy.getId();
      String environmentId = env.getId();
      variablesForDeploy =
          api.getVariablesByReleaseAndEnvironment(releaseId, environmentId, properties);
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Retrieving variables for release '%s' to environment '%s' failed with message '%s'",
              releaseToDeploy.getId(), env.getName(), ex.getMessage()));
      success = false;
    }
    try {
      String results =
          api.executeDeployment(releaseToDeploy.getId(), env.getId(), variablesForDeploy);
      if (isTaskJson(results)) {
        JSON resultJson = JSONSerializer.toJSON(results);
        String urlSuffix = ((JSONObject) resultJson).getJSONObject("Links").getString("Web");
        String url = getDescriptorImpl().octopusHost;
        if (url.endsWith("/")) {
          url = url.substring(0, url.length() - 2);
        }
        log.info("Deployment executed: \n\t" + url + urlSuffix);
        build.addAction(
            new BuildInfoSummary(
                BuildInfoSummary.OctopusDeployEventType.Deployment, url + urlSuffix));
        if (waitForDeployment) {

          log.info("Waiting for deployment to complete.");
          String resultState = waitForDeploymentCompletion(resultJson, api, log);
          if (resultState == null) {
            log.info("Marking build failed due to failure in waiting for deployment to complete.");
            success = false;
          }

          if ("Failed".equals(resultState)) {
            log.info("Marking build failed due to deployment task status.");
            success = false;
          }
        }
      }
    } catch (IOException ex) {
      log.fatal("Failed to deploy: " + ex.getMessage());
      success = false;
    }

    return success;
  }
  @Override
  public BuildWrapper.Environment setUp(
      AbstractBuild build, Launcher launcher, BuildListener listener)
      throws IOException, InterruptedException {
    final PrintStream logger = listener.getLogger();

    final DeviceFarmApi api = new DeviceFarmApiImpl();
    long start = System.currentTimeMillis();

    try {
      EnvVars environment = build.getEnvironment(listener);
      String expendedTag = environment.expand(tag);

      log(logger, Messages.TRYING_TO_CONNECT_API_SERVER(deviceApiUrl, expendedTag));
      api.connectApiServer(
          logger,
          deviceApiUrl,
          expendedTag,
          build.getProject().getAbsoluteUrl() + build.getNumber());

      final RemoteDevice reserved =
          api.waitApiResponse(
              logger, DEVICE_WAIT_TIMEOUT_IN_MILLIS, DEVICE_READY_CHECK_INTERVAL_IN_MS);
      log(
          logger,
          Messages.DEVICE_IS_READY(passedSeconds(start), reserved.ip, reserved.port, reserved.url));

      if (descriptor == null) {
        descriptor = Hudson.getInstance().getDescriptorByType(DescriptorImpl.class);
      }

      // Substitute environment and build variables into config
      final String androidHome = discoverAndroidSdkHome(build, launcher, listener);
      log(logger, Messages.USING_SDK(androidHome));

      AndroidSdk sdk = new AndroidSdk(androidHome, androidHome);
      final AndroidDeviceContext device =
          new AndroidDeviceContext(build, launcher, listener, sdk, reserved.ip, reserved.port);
      // disconnect first to workaround previous error
      device.disconnect();

      // connect device with adb
      device.connect(DEVICE_CONNECT_TIMEOUT_IN_MILLIS);

      device.waitDeviceReady(logger, DEVICE_CONNECT_TIMEOUT_IN_MILLIS, 1000);
      // check availability
      device.devices();

      // unlock screen
      device.unlockScreen();

      // Start dumping logcat to temporary file
      final LogcatCollector logcatCollector = new LogcatCollector(build, device);
      logcatCollector.start();

      return new BuildWrapper.Environment() {
        @Override
        public void buildEnvVars(Map<String, String> env) {
          env.put("ANDROID_IP", device.ip());
          env.put("ANDROID_HOME", androidHome);
          env.put("ANDROID_SDK_HOME", androidHome);
          env.put("ANDROID_PORT", Integer.toString(device.port()));
          env.put("ANDROID_SERIAL", device.serial());
        }

        @Override
        public boolean tearDown(AbstractBuild build, BuildListener listener)
            throws IOException, InterruptedException {
          cleanUp(build, device, api, logcatCollector);

          return true;
        }
      };

    } catch (FailedToConnectApiServerException e) {
      log(logger, Messages.FAILED_TO_CONNECT_API_SERVER());
    } catch (MalformedResponseException e) {
      log(logger, Messages.FAILED_TO_PARSE_DEVICE_FARM_RESPONSE());
    } catch (TimeoutException e) {
      log(logger, Messages.DEVICE_WAIT_TIMEOUT(passedSeconds(start)));
    } catch (NoDeviceAvailableException e) {
      log(logger, Messages.NO_SUCH_DEVICE());
    }

    build.setResult(Result.NOT_BUILT);
    cleanUp(null, null, api, null);
    return null;
  }