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);
+ }
+}