diff --git a/src/main/java/com/teamscale/upload/utils/LogUtils.java b/src/main/java/com/teamscale/upload/utils/LogUtils.java index 9172f15..471d3cc 100644 --- a/src/main/java/com/teamscale/upload/utils/LogUtils.java +++ b/src/main/java/com/teamscale/upload/utils/LogUtils.java @@ -22,8 +22,7 @@ public static void enableStackTracePrintingForKnownErrors() { */ public static void fail(String message, SafeResponse response) { String url = response.unsafeResponse.request().url().toString(); - fail("Upload to Teamscale failed:\n\n" + message + "\n\nTeamscale's response:\n" + url + "\n" - + response.body); + fail("Upload to Teamscale failed:\n\n" + message + "\n\nTeamscale's response:\n" + url + "\n" + response.body); } /** @@ -51,8 +50,7 @@ public static void failWithoutStackTrace(String message, Throwable throwable) { throwable.printStackTrace(); } else { System.err.println("ERROR: " + throwable.getClass().getSimpleName() + ": " + throwable.getMessage()); - System.err.println( - "Stack trace suppressed. Rerun this command with --stacktrace to see the stack trace."); + System.err.println("Stack trace suppressed. Rerun this command with --stacktrace to see the stack trace."); } fail(message); } @@ -108,6 +106,20 @@ public static void debug(String message) { } } + /** + * Print a debug message to stdout and log the given throwable. + *

+ * Use to log information that is not useful during normal operations but + * helpful when something goes wrong. + */ + public static void debug(String message, Throwable throwable) { + if (!debugLogEnabled) { + return; + } + debug(message); + throwable.printStackTrace(); + } + /** * Enables debug logging and all stack traces. */ diff --git a/src/main/java/com/teamscale/upload/xcode/XCResultConverter.java b/src/main/java/com/teamscale/upload/xcode/XCResultConverter.java index d16b7d4..73612b1 100644 --- a/src/main/java/com/teamscale/upload/xcode/XCResultConverter.java +++ b/src/main/java/com/teamscale/upload/xcode/XCResultConverter.java @@ -7,6 +7,7 @@ import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Queue; @@ -109,9 +110,9 @@ private static List getSourceFiles(File reportDirectory) throws Conversi ProcessResult result = ProcessUtils.run("xcrun", "xccov", "view", "--archive", "--file-list", reportDirectory.getAbsolutePath()); if (!result.wasSuccessful()) { - throw new ConversionException( + throw ConversionException.withProcessResult( "Error while obtaining file list from XCResult archive " + reportDirectory.getAbsolutePath(), - result.exception); + result); } return result.output.lines().sorted().collect(toList()); } @@ -209,11 +210,21 @@ private File getReportDirectory(File report) throws IOException, ConversionExcep private ActionsInvocationRecord getActionsInvocationRecord(File reportDirectory) throws InterruptedException, ConversionException { - ProcessResult result = ProcessUtils.run("xcrun", "xcresulttool", "get", "--path", - reportDirectory.getAbsolutePath(), "--format", "json"); + List command = new ArrayList<>(); + Collections.addAll(command, "xcrun", "xcresulttool", "get", "--path", reportDirectory.getAbsolutePath(), + "--format", "json"); + if (XcodeVersion.determine().major() >= 16) { + // Starting with Xcode 16 this command is marked as deprecated and will fail if + // ran without the legacy flag + // see TS-40724 for more information + command.add("--legacy"); + } + + ProcessResult result = ProcessUtils.run(command.toArray(new String[0])); if (!result.wasSuccessful()) { - throw new ConversionException("Error while obtaining ActionInvocationsRecord from XCResult archive " - + reportDirectory.getAbsolutePath(), result.exception); + throw ConversionException + .withProcessResult("Error while obtaining ActionInvocationsRecord from XCResult archive " + + reportDirectory.getAbsolutePath(), result); } String actionsInvocationRecordJson = result.output; return new Gson().fromJson(actionsInvocationRecordJson, ActionsInvocationRecord.class); @@ -305,5 +316,17 @@ public ConversionException(String message) { public ConversionException(String message, Exception e) { super(message, e); } + + /** + * Creates a {@link ConversionException} with the given message and the + * {@linkplain ProcessResult#errorOutput error output of the command}. + */ + public static ConversionException withProcessResult(String message, ProcessResult processResult) { + String messageIncludingErrorOutput = message; + if (processResult.errorOutput != null) { + messageIncludingErrorOutput += " (command output: " + processResult.errorOutput + ")"; + } + return new ConversionException(messageIncludingErrorOutput, processResult.exception); + } } } diff --git a/src/main/java/com/teamscale/upload/xcode/XcodeVersion.java b/src/main/java/com/teamscale/upload/xcode/XcodeVersion.java new file mode 100644 index 0000000..b1af384 --- /dev/null +++ b/src/main/java/com/teamscale/upload/xcode/XcodeVersion.java @@ -0,0 +1,59 @@ +package com.teamscale.upload.xcode; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.teamscale.upload.autodetect_revision.ProcessUtils; +import com.teamscale.upload.utils.LogUtils; + +/** + * Represents an Xcode version. + * + * @param major + * The minor version number (e.g., {@code 16} for version + * {@code 16.1} or {@link Integer#MAX_VALUE} for latest version) + * @param minor + * The minor version number (e.g., {@code 1} for version {@code 16.1} + * or {@link Integer#MAX_VALUE} for latest version) + */ +public record XcodeVersion(int major, int minor) { + + /** + * Pattern that matches the output of an {@code xcodebuild -version} command to + * determine the installed version. + */ + private static final Pattern XCODE_BUILD_VERSION_PATTERN = Pattern + .compile("^Xcode (?\\d+)\\.(?\\d+)"); + + /** Determines the version installed on this machine. */ + public static XcodeVersion determine() { + ProcessUtils.ProcessResult result = ProcessUtils.run("xcodebuild", "-version"); + if (!result.wasSuccessful()) { + LogUtils.warn("Could not determine installed Xcode version. Assuming latest Xcode version is installed."); + LogUtils.debug("Error whilst running 'xcodebuild -version' command: ", result.exception); + return latestVersion(); + } + + String xcodeBuildVersionCommandOutput = result.output; + Matcher m = XCODE_BUILD_VERSION_PATTERN.matcher(xcodeBuildVersionCommandOutput); + if (!m.find()) { + LogUtils.warn("Could not determine installed Xcode version. Assuming latest Xcode version is installed."); + LogUtils.debug("Output of 'xcodebuild -version' command:\n" + xcodeBuildVersionCommandOutput); + return latestVersion(); + } + + int major = Integer.parseInt(m.group("major")); + int minor = Integer.parseInt(m.group("minor")); + return new XcodeVersion(major, minor); + } + + /** + * Returns a {@link XcodeVersion} that represents the latest version. + *

+ * Instead of determining the version via the web, {@link #major} and + * {@link #minor} will be simply set to {@link Integer#MAX_VALUE}. + */ + private static XcodeVersion latestVersion() { + return new XcodeVersion(Integer.MAX_VALUE, Integer.MAX_VALUE); + } +}