com.facebook.buck.apple.simulator.AppleCoreSimulatorServiceController.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.apple.simulator.AppleCoreSimulatorServiceController.java

Source

/*
 * Copyright 2015-present Facebook, 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.facebook.buck.apple.simulator;

import com.facebook.buck.log.Logger;
import com.facebook.buck.util.ProcessExecutor;
import com.facebook.buck.util.ProcessExecutorParams;
import com.facebook.buck.util.UserIdFetcher;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Launches, queries, and kills Apple's {@code CoreSimulator} and related {@code launchd} services.
 */
public class AppleCoreSimulatorServiceController {
    private static final Logger LOG = Logger.get(AppleCoreSimulatorServiceController.class);

    private static final Pattern LAUNCHCTL_LIST_OUTPUT_PATTERN = Pattern.compile("^(\\S+)\\s+(\\S+)\\s+(.*)$");
    private static final Pattern LAUNCHCTL_PRINT_PATH_PATTERN = Pattern.compile("^\\s*path\\s*=\\s*(.*)$");
    private static final Pattern CORE_SIMULATOR_SERVICE_PATTERN = Pattern
            .compile("(?i:com\\.apple\\.CoreSimulator\\.CoreSimulatorService)");

    private static final Pattern ALL_SIMULATOR_SERVICES_PATTERN = Pattern
            .compile("(?i:com\\.apple\\.iphonesimulator|UIKitApplication|SimulatorBridge|iOS Simulator|"
                    + "com\\.apple\\.CoreSimulator)");

    private static final int LAUNCHCTL_EXIT_SUCCESS = 0;
    private static final int LAUNCHCTL_EXIT_NO_SUCH_PROCESS = 3;

    private final ProcessExecutor processExecutor;

    public AppleCoreSimulatorServiceController(ProcessExecutor processExecutor) {
        this.processExecutor = processExecutor;
    }

    /**
     * Returns the path on disk to the running Core Simulator service, if any.
     * Returns {@code Optional.empty()} unless exactly one Core Simulator service is running.
     */
    public Optional<Path> getCoreSimulatorServicePath(UserIdFetcher userIdFetcher)
            throws IOException, InterruptedException {
        ImmutableSet<String> coreSimulatorServiceNames = getMatchingServiceNames(CORE_SIMULATOR_SERVICE_PATTERN);

        if (coreSimulatorServiceNames.size() != 1) {
            LOG.debug("Could not get core simulator service name (got %s)", coreSimulatorServiceNames);
            return Optional.empty();
        }

        String coreSimulatorServiceName = Iterables.getOnlyElement(coreSimulatorServiceNames);

        ImmutableList<String> launchctlPrintCommand = ImmutableList.of("launchctl", "print",
                String.format("user/%d/%s", userIdFetcher.getUserId(), coreSimulatorServiceName));

        LOG.debug("Getting status of service %s with %s", coreSimulatorServiceName, launchctlPrintCommand);
        ProcessExecutorParams launchctlPrintParams = ProcessExecutorParams.builder()
                .setCommand(launchctlPrintCommand).build();
        ProcessExecutor.LaunchedProcess launchctlPrintProcess = processExecutor.launchProcess(launchctlPrintParams);
        Optional<Path> result = Optional.empty();
        try (InputStreamReader stdoutReader = new InputStreamReader(launchctlPrintProcess.getInputStream(),
                StandardCharsets.UTF_8); BufferedReader bufferedStdoutReader = new BufferedReader(stdoutReader)) {
            String line;
            while ((line = bufferedStdoutReader.readLine()) != null) {
                Matcher matcher = LAUNCHCTL_PRINT_PATH_PATTERN.matcher(line);
                if (matcher.matches()) {
                    String path = matcher.group(1);
                    LOG.debug("Found path of service %s: %s", coreSimulatorServiceName, path);
                    result = Optional.of(Paths.get(path));
                    break;
                }
            }
        } finally {
            processExecutor.destroyLaunchedProcess(launchctlPrintProcess);
            processExecutor.waitForLaunchedProcess(launchctlPrintProcess);
        }

        return result;
    }

    private ImmutableSet<String> getMatchingServiceNames(Pattern serviceNamePattern)
            throws IOException, InterruptedException {
        ImmutableList<String> launchctlListCommand = ImmutableList.of("launchctl", "list");
        LOG.debug("Getting list of services with %s", launchctlListCommand);
        ProcessExecutorParams launchctlListParams = ProcessExecutorParams.builder().setCommand(launchctlListCommand)
                .build();
        ProcessExecutor.LaunchedProcess launchctlListProcess = processExecutor.launchProcess(launchctlListParams);
        ImmutableSet.Builder<String> resultBuilder = ImmutableSet.builder();
        try (InputStreamReader stdoutReader = new InputStreamReader(launchctlListProcess.getInputStream(),
                StandardCharsets.UTF_8); BufferedReader bufferedStdoutReader = new BufferedReader(stdoutReader)) {
            String line;
            while ((line = bufferedStdoutReader.readLine()) != null) {
                Matcher launchctlListOutputMatcher = LAUNCHCTL_LIST_OUTPUT_PATTERN.matcher(line);
                if (launchctlListOutputMatcher.matches()) {
                    String serviceName = launchctlListOutputMatcher.group(3);
                    Matcher serviceNameMatcher = serviceNamePattern.matcher(serviceName);
                    if (serviceNameMatcher.find()) {
                        LOG.debug("Found matching service name: %s", serviceName);
                        resultBuilder.add(serviceName);
                    }
                }
            }
        } finally {
            processExecutor.destroyLaunchedProcess(launchctlListProcess);
            processExecutor.waitForLaunchedProcess(launchctlListProcess);
        }

        return resultBuilder.build();
    }

    /**
     * Kills any running simulator processes or services.
     */
    public boolean killSimulatorProcesses() throws IOException, InterruptedException {
        ImmutableSet<String> simulatorServiceNames = getMatchingServiceNames(ALL_SIMULATOR_SERVICES_PATTERN);
        LOG.debug("Killing simulator services: %s", simulatorServiceNames);
        boolean result = true;
        for (String simulatorServiceName : simulatorServiceNames) {
            if (!killService(simulatorServiceName)) {
                result = false;
            }
        }
        return result;
    }

    private boolean killService(String serviceName) throws IOException, InterruptedException {
        ImmutableList<String> launchctlRemoveCommand = ImmutableList.of("launchctl", "remove", serviceName);
        LOG.debug("Killing simulator process with with %s", launchctlRemoveCommand);
        ProcessExecutorParams launchctlRemoveParams = ProcessExecutorParams.builder()
                .setCommand(launchctlRemoveCommand).build();
        ProcessExecutor.Result result = processExecutor.launchAndExecute(launchctlRemoveParams);
        int launchctlExitCode = result.getExitCode();
        LOG.debug("Command %s exited with code %d", launchctlRemoveParams, launchctlExitCode);
        switch (launchctlExitCode) {
        case LAUNCHCTL_EXIT_SUCCESS:
        case LAUNCHCTL_EXIT_NO_SUCH_PROCESS:
            // The process could have exited by itself or already been terminated by the time
            // we told it to die, so we have to treat "no such process" as success.
            return true;
        default:
            LOG.error(result.getMessageForUnexpectedResult(launchctlRemoveCommand.toString()));
            return false;
        }
    }
}