Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package com.codename1.ant;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.Properties;

import org.apache.tools.ant.BuildException;
Expand Down Expand Up @@ -31,61 +36,154 @@ public static boolean executeAntTask(String buildXmlFileFullPath) {
public static boolean executeAntTask(String buildXmlFileFullPath, String target, Properties properties) {

boolean success = false;
DefaultLogger consoleLogger = getConsoleLogger();

// Prepare Ant project
Project project = new Project();
File buildFile = new File(buildXmlFileFullPath);

project.setBasedir(buildFile.getParentFile().getAbsolutePath());
project.setBaseDir(buildFile.getParentFile());

project.setUserProperty("ant.file", buildFile.getAbsolutePath());
if (properties != null) {
for (String k : properties.stringPropertyNames()) {
project.setProperty(k, properties.getProperty(k));
}
// Tee stdout/stderr so that, on failure, we can recover server-reported
// error details (such as the JSON body returned by the build server) that
// the build client prints but does not propagate via the exception message.
ByteArrayOutputStream captured = new ByteArrayOutputStream();
PrintStream originalOut = System.out;
PrintStream originalErr = System.err;
PrintStream teeOut;
PrintStream teeErr;
try {
teeOut = new PrintStream(new TeeOutputStream(originalOut, captured), true, "UTF-8");
teeErr = new PrintStream(new TeeOutputStream(originalErr, captured), true, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 not supported", e);
}

project.addBuildListener(consoleLogger);
DefaultLogger consoleLogger = new DefaultLogger();
consoleLogger.setErrorPrintStream(teeErr);
consoleLogger.setOutputPrintStream(teeOut);
consoleLogger.setMessageOutputLevel(Project.MSG_INFO);

System.setOut(teeOut);
System.setErr(teeErr);

// Capture event for Ant script build start / stop / failure
try {
project.fireBuildStarted();
project.init();
ProjectHelper projectHelper = ProjectHelper.getProjectHelper();

project.addReference("ant.projectHelper", projectHelper);

projectHelper.parse(project, buildFile);

// If no target specified then default target will be executed.
String targetToExecute = (target != null && target.trim().length() > 0) ? target.trim() : project.getDefaultTarget();
project.executeTarget(targetToExecute);
project.fireBuildFinished(null);
success = true;
} catch (BuildException buildException) {
project.fireBuildFinished(buildException);
throw new RuntimeException("!!! Unable to restart the IEHS App !!!", buildException);
// Prepare Ant project
Project project = new Project();
File buildFile = new File(buildXmlFileFullPath);

project.setBasedir(buildFile.getParentFile().getAbsolutePath());
project.setBaseDir(buildFile.getParentFile());

project.setUserProperty("ant.file", buildFile.getAbsolutePath());
if (properties != null) {
for (String k : properties.stringPropertyNames()) {
project.setProperty(k, properties.getProperty(k));
}
}

project.addBuildListener(consoleLogger);

// Capture event for Ant script build start / stop / failure
try {
project.fireBuildStarted();
project.init();
ProjectHelper projectHelper = ProjectHelper.getProjectHelper();

project.addReference("ant.projectHelper", projectHelper);

projectHelper.parse(project, buildFile);

// If no target specified then default target will be executed.
String targetToExecute = (target != null && target.trim().length() > 0) ? target.trim() : project.getDefaultTarget();
project.executeTarget(targetToExecute);
project.fireBuildFinished(null);
success = true;
} catch (BuildException buildException) {
project.fireBuildFinished(buildException);
teeOut.flush();
teeErr.flush();
String capturedText;
try {
capturedText = captured.toString("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 not supported", e);
}
String detail = extractServerErrorDetail(capturedText);
StringBuilder message = new StringBuilder("Ant task failed: ").append(buildException.getMessage());
if (detail != null) {
message.append(System.lineSeparator()).append(detail);
}
throw new RuntimeException(message.toString(), buildException);
}
} finally {
System.setOut(originalOut);
System.setErr(originalErr);
}

return success;
}


/**
* Scans build output for server-reported error markers (HTTP status, response
* message, JSON error body) and returns them joined by newlines, or {@code null}
* if none were found.
*/
static String extractServerErrorDetail(String log) {
if (log == null || log.isEmpty()) {
return null;
}
StringBuilder sb = new StringBuilder();
String[] lines = log.split("\\r?\\n");
for (String raw : lines) {
String line = raw.trim();
if (line.isEmpty()) {
continue;
}
if (line.startsWith("Response message from server is:")
|| line.startsWith("Server Detailed Error Message:")
|| line.startsWith("Server returned HTTP response code:")
|| line.contains("Server returned HTTP response code:")) {
if (sb.length() > 0) {
sb.append(System.lineSeparator());
}
sb.append(line);
}
}
return sb.length() == 0 ? null : sb.toString();
}

/**
* Logger to log output generated while executing ant script in console
*
* @return
* OutputStream that writes to two underlying streams. Used to forward Ant
* output to the original console while also retaining a copy for diagnostics.
*/
private static DefaultLogger getConsoleLogger() {
DefaultLogger consoleLogger = new DefaultLogger();
consoleLogger.setErrorPrintStream(System.err);
consoleLogger.setOutputPrintStream(System.out);
consoleLogger.setMessageOutputLevel(Project.MSG_INFO);
private static final class TeeOutputStream extends OutputStream {
private final OutputStream a;
private final OutputStream b;

return consoleLogger;
}
TeeOutputStream(OutputStream a, OutputStream b) {
this.a = a;
this.b = b;
}

@Override
public void write(int byteValue) throws IOException {
a.write(byteValue);
b.write(byteValue);
}

@Override
public void write(byte[] buf, int off, int len) throws IOException {
a.write(buf, off, len);
b.write(buf, off, len);
}

@Override
public void flush() throws IOException {
a.flush();
b.flush();
}

@Override
public void close() throws IOException {
try {
a.close();
} finally {
b.close();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.codename1.ant;

import org.junit.jupiter.api.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

public class AntExecutorTest {

@Test
public void returnsNullWhenNoServerErrorMarkers() {
assertNull(AntExecutor.extractServerErrorDetail(null));
assertNull(AntExecutor.extractServerErrorDetail(""));
assertNull(AntExecutor.extractServerErrorDetail("Just some build output\nNothing interesting here"));
}

@Test
public void capturesServerJsonBodyAndStatus() {
String log = "Sending build request to the server, notice that the build might take a while to complete!\n"
+ "Sending build to account: shai@codenameone.com\n"
+ "Response message from server is: Internal Server Error\n"
+ "Server Detailed Error Message: {\"timestamp\":\"2026-05-10T03:43:19.633+00:00\",\"status\":500,\"error\":\"Internal Server Error\",\"path\":\"/appsec/7.0/build/upload\"}\n"
+ "java.io.IOException: Server returned HTTP response code: 500 for URL: https://cloud.codenameone.com/appsec/7.0/build/upload\n"
+ " at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1700)";

String detail = AntExecutor.extractServerErrorDetail(log);

assertTrue("expected response message line, got: " + detail,
detail.contains("Response message from server is: Internal Server Error"));
assertTrue("expected JSON body, got: " + detail,
detail.contains("Server Detailed Error Message: {\"timestamp\""));
assertTrue("expected HTTP status line, got: " + detail,
detail.contains("Server returned HTTP response code: 500"));
}

@Test
public void ignoresUnrelatedLines() {
String log = "Building project\nCompiling sources\nResponse message from server is: OK\nDone";
String detail = AntExecutor.extractServerErrorDetail(log);
assertEquals("Response message from server is: OK", detail);
}
}
Loading