/* * 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.device; import com.facebook.buck.log.Logger; import com.facebook.buck.util.ProcessExecutor; import com.facebook.buck.util.ProcessExecutorParams; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.nio.file.Path; import java.util.EnumSet; import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; public class AppleDeviceHelper { private static final Logger LOG = Logger.get(AppleDeviceHelper.class); private static final Pattern DEVICE_DESCRIPTION_PATTERN = Pattern.compile("([a-f0-9]{40}) \\| (.*)"); private final ProcessExecutor processExecutor; private final Path deviceHelperPath; public AppleDeviceHelper(ProcessExecutor processExecutor, Path deviceHelperPath) { this.processExecutor = processExecutor; this.deviceHelperPath = deviceHelperPath; } /** * Runs the helper program to enumerate all currently-connected devices. * * @return A ImmutableMap with entries where the key is the UDID of the device, and the value is a * human readable description (e.g. "iPhone (iPhone 5S) (USB)") * @throws IOException * @throws InterruptedException */ public ImmutableMap<String, String> getConnectedDevices() { ProcessExecutorParams processExecutorParams = ProcessExecutorParams.builder() .setCommand(ImmutableList.of(deviceHelperPath.toString(), "list")) .build(); // Must specify that stdout is expected or else output may be wrapped in Ansi escape chars. Set<ProcessExecutor.Option> options = EnumSet.of(ProcessExecutor.Option.EXPECTING_STD_OUT); ProcessExecutor.Result result; try { result = processExecutor.launchAndExecute( processExecutorParams, options, /* stdin */ Optional.empty(), /* timeOutMs */ Optional.of((long) 5000), /* timeOutHandler */ Optional.empty()); } catch (InterruptedException | IOException e) { LOG.warn("Could not execute device helper."); return ImmutableMap.of(); } if (result.isTimedOut()) { throw new RuntimeException("Device helper failed: timed out"); } if (result.getExitCode() != 0) { throw new RuntimeException(result.getMessageForUnexpectedResult("Device helper")); } Matcher matcher = DEVICE_DESCRIPTION_PATTERN.matcher(result.getStdout().get()); ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); while (matcher.find()) { String udid = matcher.group(1); String description = matcher.group(2); builder.put(udid, description); LOG.debug("Found device: " + udid + ", " + description); } return builder.build(); } /** * Attempts to install a bundle on the device. The bundle must be code-signed already. * * @param udid The identifier of the device * @param bundlePath The path to the bundle root (e.g. {@code /path/to/Example.app/}) * @return true if successful, false otherwise. */ public boolean installBundleOnDevice(String udid, Path bundlePath) { ProcessExecutorParams processExecutorParams = ProcessExecutorParams.builder() .setCommand( ImmutableList.of( deviceHelperPath.toString(), "--json", udid, "install", bundlePath.toString())) .build(); Set<ProcessExecutor.Option> options = EnumSet.of(ProcessExecutor.Option.PRINT_STD_OUT, ProcessExecutor.Option.PRINT_STD_ERR); ProcessExecutor.Result result; try { result = processExecutor.launchAndExecute( processExecutorParams, options, /* stdin */ Optional.empty(), /* timeOutMs */ Optional.of((long) 60000), /* timeOutHandler */ Optional.empty()); } catch (InterruptedException | IOException e) { LOG.warn("Could not execute device helper."); return false; } if (result.isTimedOut()) { throw new RuntimeException("Device helper failed: timed out"); } return (result.getExitCode() == 0); } /** * Attempts to run a bundle on the device. The bundle must be installed already. * * @param udid The identifier of the device * @param bundleID The bundle ID (e.g. {@code com.example.DemoApp}) * @return true if successful, false otherwise. */ public boolean runBundleOnDevice(String udid, String bundleID) { ProcessExecutorParams processExecutorParams = ProcessExecutorParams.builder() .setCommand( ImmutableList.of(deviceHelperPath.toString(), "--json", udid, "launch", bundleID)) .build(); Set<ProcessExecutor.Option> options = EnumSet.of(ProcessExecutor.Option.PRINT_STD_OUT, ProcessExecutor.Option.PRINT_STD_ERR); ProcessExecutor.Result result; try { result = processExecutor.launchAndExecute( processExecutorParams, options, /* stdin */ Optional.empty(), /* timeOutMs */ Optional.of((long) 60000), /* timeOutHandler */ Optional.empty()); } catch (InterruptedException | IOException e) { LOG.warn("Could not execute device helper."); return false; } if (result.isTimedOut()) { throw new RuntimeException("Device helper failed: timed out"); } return (result.getExitCode() == 0); } }