Compare commits

..

No commits in common. "master" and "passfile" have entirely different histories.

4 changed files with 47 additions and 379 deletions

View File

@ -58,30 +58,9 @@ The tool operates in two modes:
**Migration Mode** (original functionality):
- Activates when destination flags are provided
- Reads from source, writes to destination with batched INSERTs (or bulk copy with `-b`)
- Reads from source, writes to destination with batched INSERTs
- Shows progress counters and timing information
**Bulk Copy** (migration mode, `-b`) — uses the dest's native bulk path; falls
back to the INSERT path for any other dest (e.g. DB2):
*SQL Server dest* — streams the source ResultSet over the TDS bulk-load
protocol via `SQLServerBulkCopy` (no per-batch INSERT round trips; a 1.27M-row,
~298-col load went ~111 min → ~4 min). A `BulkSource` adapter
(`ISQLServerBulkData`) maps source type names to JDBC types we control:
string-ish types (text/varchar/char/bpchar/json/jsonb/uuid **and numeric**) are
declared NVARCHAR and read via `getString` so SQL Server converts losslessly —
numeric goes this route because PG reports unconstrained numeric as scale 0,
which a typed DECIMAL path would round (123.45 → 123).
*Postgres dest* — streams via `COPY <table> FROM STDIN WITH (FORMAT csv)` using
the JDBC `CopyManager`. COPY is text-based, so the server parses each field into
the column type — no per-type handling. Every non-null value is CSV-quoted
(empty string stays distinct from NULL, which is an empty unquoted field); rows
flush in 1000-row buffers.
Both emit a `\r`-counter every 10k rows for live progress and print the final
row count.
### Data Flow
**Query Mode:**
@ -97,15 +76,12 @@ row count.
2. Read SQL query from file specified by -sq flag
3. Connect to source and destination databases via JDBC
4. Execute source query and fetch results (fetch size: 10,000 rows)
5. Optionally clear target table before insert if -c flag is set
6. With `-b`: bulk-load via the dest's native path (SQL Server → `SQLServerBulkCopy`,
Postgres → `COPY FROM STDIN`). Otherwise: build batched INSERT statements
(250 rows per batch) and execute them against the destination table (-dt)
5. Build batched INSERT statements (250 rows per batch)
6. Execute batches against destination table specified by -dt flag
7. Optionally clear target table before insert if -c flag is set
### Type Handling
The tool includes explicit handling for different SQL data types in a switch statement (migration mode). Quoted string types: VARCHAR/NVARCHAR, TEXT/NTEXT, CHAR/NCHAR, CLOB/NCLOB, and the PostgreSQL string-ish types JSON, JSONB, BPCHAR (PG `char(n)`), and UUID. Date/time types (DATE, TIME, TIMESTAMP/DATETIME variants) are also quoted. String types get quote escaping (`'` → `''`) and optional trimming.
**Caveat — the `default` case emits values UNQUOTED** (correct for numerics like INT*/NUMERIC, which is why they're not listed). Any *string-typed* column whose JDBC type name isn't in the switch falls here and breaks the generated INSERT with a syntax error (e.g. PostgreSQL `bool``'t'`/`'f'` is currently unhandled). When adding a new source type, decide: numeric → leave to default; anything string-like → add a quoted case. A more robust future fix is to flip the default to quote-as-string with an explicit numeric allowlist.
The tool includes explicit handling for different SQL data types in a switch statement (lines 229-312). Supported types include VARCHAR, TEXT, CHAR, CLOB, DATE, TIME, TIMESTAMP, and BIGINT. String types get quote escaping and optional trimming.
### Database Drivers
JDBC drivers are configured in `jrunner/build.gradle`:
@ -131,7 +107,6 @@ Command-line flags:
- `-dt` - fully qualified destination table name (migration mode only)
- `-t` - trim text fields (default: true)
- `-c` - clear target table before insert (default: true, migration mode only)
- `-b` - bulk load into dest (migration mode): SQL Server via SQLServerBulkCopy, Postgres via COPY
- `-f` - output format: csv, tsv (query mode only, default: csv)
## Key Implementation Details
@ -165,10 +140,10 @@ Both modes use a streaming architecture with no array storage of result rows:
- Only holds up to 250 rows worth of SQL text in memory at once
**JDBC Fetch Size:**
- Both modes set `stmt.setFetchSize(10000)` — a hint to fetch 10,000 rows at a time
- The application processes rows one at a time via `rs.next()`; the only buffer is the driver's fetch window
**⚠️ PostgreSQL requires autoCommit=false for fetchSize to take effect.** The PG JDBC driver IGNORES `setFetchSize` while autoCommit is true and instead loads the ENTIRE result set into memory (OOMs / GC-thrashes on large source tables). So in **migration mode** the source connection is set to `setAutoCommit(false)` right after connecting, which enables a server-side cursor and makes streaming actually stream. This is done **only in migration mode** — query mode leaves autoCommit at its default because callers run committed DDL/DML through query mode (e.g. external tools), and autoCommit=false would roll those statements back on connection close. (jt400/MSSQL drivers stream regardless, so only PG is affected.)
- Both modes set `stmt.setFetchSize(10000)` (line 190)
- This is a hint to the JDBC driver to fetch 10,000 rows at a time from the database
- The driver maintains this internal buffer for network efficiency
- The application code never sees or stores all 10,000 rows - it processes them one at a time via `rs.next()`
### Batch Size (Migration Mode)
INSERT statements are batched at 250 rows (hardcoded around line 356). Rows are streamed into a SQL string buffer as VALUES clauses. When 250 rows accumulate in the string, it is prepended with "INSERT INTO {table} VALUES" and executed, then the string is cleared.

View File

@ -6,13 +6,6 @@ import java.nio.file.Path ;
import java.nio.file.Paths;
import java.time.*;
import java.io.IOException;
import com.microsoft.sqlserver.jdbc.SQLServerBulkCopy;
import com.microsoft.sqlserver.jdbc.SQLServerBulkCopyOptions;
import com.microsoft.sqlserver.jdbc.ISQLServerBulkData;
import com.microsoft.sqlserver.jdbc.SQLServerException;
import org.postgresql.PGConnection;
import org.postgresql.copy.CopyManager;
import org.postgresql.copy.CopyIn;
public class jrunner {
//static final String QUERY = "SELECT * from rlarp.osm LIMIT 100";
@ -31,7 +24,6 @@ public class jrunner {
String dt = "";
Boolean trim = true;
Boolean clear = true;
Boolean bulk = false;
Integer r = 0;
Integer t = 0;
String sql = "";
@ -65,7 +57,6 @@ public class jrunner {
msg = msg + nl + "-dt fully qualified name of destination table";
msg = msg + nl + "-t trim text";
msg = msg + nl + "-c clear target table";
msg = msg + nl + "-b bulk copy into destination (SQL Server dest only)";
msg = msg + nl + "-f output format (csv, tsv, table, json) - default: csv";
msg = msg + nl + "--help info";
msg = msg + nl + "";
@ -134,9 +125,6 @@ public class jrunner {
case "-c":
clear = true;
break;
case "-b":
bulk = true;
break;
case "-f":
outputFormat = args[i+1].toLowerCase();
break;
@ -312,72 +300,6 @@ public class jrunner {
e.printStackTrace();
System.exit(0);
}
} else if (bulk && dcu.toLowerCase().startsWith("jdbc:sqlserver:")) {
//-------------------------------bulk copy (SQL Server dest)-------------------------------------------------
// Stream the source ResultSet straight into SQL Server over the TDS
// bulk-load protocol no per-row INSERT round trips. Source type
// names map to JDBC types via BulkSource (string-ish -> NVARCHAR).
System.out.println("------------bulk copy-------------------------------------");
try {
SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(dcon);
bulkCopy.setDestinationTableName(dt);
SQLServerBulkCopyOptions options = new SQLServerBulkCopyOptions();
options.setBatchSize(10000);
options.setBulkCopyTimeout(0);
bulkCopy.setBulkCopyOptions(options);
BulkSource src = new BulkSource(rs, cols, dtn, trim);
bulkCopy.writeToServer(src);
bulkCopy.close();
// leading \r starts a fresh line so the count doesn't concatenate
// onto the last progress tick; the trailing " rows written" makes
// it parseable.
System.out.print("\r" + src.rowsWritten());
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
} else if (bulk && dcu.toLowerCase().startsWith("jdbc:postgresql:")) {
//-------------------------------bulk copy (COPY, Postgres dest)--------------------------------------------
// Stream the source ResultSet into Postgres via COPY ... FROM STDIN.
// COPY is text-based: each field is sent as CSV text and the server
// parses it into the column type, so there's no per-type quoting.
// Non-null values are always CSV-quoted; NULL is an empty unquoted
// field; column order must match the dest (positional, as always).
System.out.println("------------bulk copy (COPY)------------------------------");
try {
CopyManager cm = ((PGConnection) dcon).getCopyAPI();
CopyIn cin = cm.copyIn("COPY " + dt + " FROM STDIN WITH (FORMAT csv)");
StringBuilder buf = new StringBuilder();
long rows = 0;
while (rs.next()) {
for (int i = 1; i <= cols; i++) {
if (i > 1) { buf.append(','); }
String val = rs.getString(i);
if (!rs.wasNull() && val != null) {
if (trim) { val = val.trim(); }
buf.append('"').append(val.replace("\"", "\"\"")).append('"');
}
// else: empty field -> NULL
}
buf.append('\n');
rows++;
if (rows % 1000 == 0) {
byte[] b = buf.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
cin.writeToCopy(b, 0, b.length);
buf.setLength(0);
if (rows % 10000 == 0) { System.out.print("\r" + rows); System.out.flush(); }
}
}
if (buf.length() > 0) {
byte[] b = buf.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
cin.writeToCopy(b, 0, b.length);
}
cin.endCopy();
System.out.print("\r" + rows);
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
} else {
System.out.println("------------row count-------------------------------------");
//-------------------------------build & execute sql-------------------------------------------------------------
@ -556,135 +478,6 @@ public class jrunner {
System.out.println();
}
// Adapts a source ResultSet to SQLServerBulkCopy. Maps source type names to
// JDBC types we control: string-ish PG types (text/varchar/char/bpchar/json/
// jsonb/uuid/...) are declared NVARCHAR and read via getString mirroring the
// INSERT path's "quote it" choices and sidestepping unsupported types (jsonb
// reports as OTHER, which writeToServer(ResultSet) can't handle directly).
static class BulkSource implements ISQLServerBulkData {
private final ResultSet rs;
private final ResultSetMetaData md;
private final int cols;
private final boolean trim;
private final int[] jdbcType;
private final boolean[] asString;
private long rows = 0;
BulkSource(ResultSet rs, int cols, String[] dtn, boolean trim) throws SQLException {
this.rs = rs;
this.cols = cols;
this.trim = trim;
this.md = rs.getMetaData();
this.jdbcType = new int[cols + 1];
this.asString = new boolean[cols + 1];
for (int i = 1; i <= cols; i++) {
String t = (dtn[i] == null ? "" : dtn[i].toUpperCase());
switch (t) {
case "INT2": case "SMALLINT":
jdbcType[i] = Types.SMALLINT; break;
case "INT4": case "INT": case "INTEGER": case "SERIAL":
jdbcType[i] = Types.INTEGER; break;
case "INT8": case "BIGINT": case "BIGSERIAL":
jdbcType[i] = Types.BIGINT; break;
// numeric/decimal: PG reports unconstrained numeric as
// scale 0, which makes bulk copy round (123.45 -> 123). Send
// the exact text instead and let SQL Server convert it into
// the dest column losslessly.
case "FLOAT4": case "REAL":
jdbcType[i] = Types.REAL; break;
case "FLOAT8": case "DOUBLE": case "DOUBLE PRECISION":
jdbcType[i] = Types.DOUBLE; break;
case "BOOL": case "BOOLEAN": case "BIT":
jdbcType[i] = Types.BIT; break;
case "DATE":
jdbcType[i] = Types.DATE; break;
case "TIME":
jdbcType[i] = Types.TIME; break;
case "TIMESTAMP": case "TIMESTAMPTZ":
case "DATETIME": case "DATETIME2": case "SMALLDATETIME":
jdbcType[i] = Types.TIMESTAMP; break;
case "BYTEA": case "BINARY": case "VARBINARY":
jdbcType[i] = Types.VARBINARY; break;
default:
jdbcType[i] = Types.LONGNVARCHAR;
asString[i] = true;
break;
}
}
}
public java.util.Set<Integer> getColumnOrdinals() {
java.util.Set<Integer> ords = new java.util.TreeSet<Integer>();
for (int i = 1; i <= cols; i++) { ords.add(i); }
return ords;
}
public String getColumnName(int column) {
try { return md.getColumnName(column); }
catch (SQLException e) { return "col" + column; }
}
public int getColumnType(int column) { return jdbcType[column]; }
public int getPrecision(int column) {
if (asString[column]) { return 0; } // LONGNVARCHAR -> nvarchar(max)
try {
int p = md.getPrecision(column);
return (p > 0 ? p : 38);
} catch (SQLException e) { return 38; }
}
public int getScale(int column) {
if (jdbcType[column] == Types.DECIMAL) {
try { return md.getScale(column); }
catch (SQLException e) { return 0; }
}
return 0;
}
public boolean next() throws SQLServerException {
try { return rs.next(); }
catch (SQLException e) { throw new RuntimeException(e); }
}
public long rowsWritten() { return rows; }
public Object[] getRowData() throws SQLServerException {
rows++;
// live progress: emit an in-place counter every 10k rows (the
// caller pulls one row at a time, so this runs during the load).
if (rows % 10000 == 0) { System.out.print("\r" + rows); System.out.flush(); }
Object[] row = new Object[cols];
try {
for (int i = 1; i <= cols; i++) {
Object v;
switch (jdbcType[i]) {
case Types.SMALLINT:
case Types.INTEGER:
case Types.BIGINT:
case Types.REAL:
case Types.DOUBLE: v = rs.getObject(i); break;
case Types.DECIMAL: v = rs.getBigDecimal(i); break;
case Types.BIT: v = rs.getBoolean(i); break;
case Types.DATE: v = rs.getDate(i); break;
case Types.TIME: v = rs.getTime(i); break;
case Types.TIMESTAMP:v = rs.getTimestamp(i); break;
case Types.VARBINARY:v = rs.getBytes(i); break;
default: {
String s = rs.getString(i);
if (s != null && trim) { s = s.trim(); }
v = s;
break;
}
}
if (rs.wasNull()) { v = null; }
row[i - 1] = v;
}
} catch (SQLException e) { throw new RuntimeException(e); }
return row;
}
}
private static void outputQueryResults(ResultSet rs, int cols, String[] dtn, String format) throws SQLException {
switch (format) {
case "csv":

View File

@ -187,8 +187,4 @@ jrunner -scu jdbc:postgresql://source:5432/sourcedb \
**Options:**
- `-t` - trim text fields (default: true)
- `-c` - clear target table before insert (default: true)
- `-b` - bulk load into the destination instead of batched INSERTs — far faster
on large/wide tables. SQL Server: TDS bulk-load via SQLServerBulkCopy.
Postgres: COPY FROM STDIN. Other dests (e.g. DB2) fall back to INSERT.
(migration mode only)
- `-f` - output format: csv, tsv (query mode only, default: csv)

172
setup.sh
View File

@ -17,120 +17,41 @@ if [[ "$OSTYPE" != "linux-gnu"* ]]; then
echo ""
fi
# Java versions this project builds with. MAX_JAVA is capped by the Gradle
# wrapper -- gradle-8.5 refuses to run on anything newer than JDK 21. Raise it
# when you bump gradle/wrapper/gradle-wrapper.properties.
MIN_JAVA=11
MAX_JAVA=21
GRADLE_WRAPPER_VERSION=$(sed -n 's/.*gradle-\([0-9.]*\)-bin\.zip/\1/p' \
gradle/wrapper/gradle-wrapper.properties 2>/dev/null)
# Function to check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Function to get Java major version (8, 11, 17, ...); echoes 0 if undetectable
# Function to get Java version
get_java_version() {
local line full major
if ! command_exists java; then
echo "0"
return
fi
# grep the version line rather than taking head -1: some JVMs prepend
# "Picked up _JAVA_OPTIONS: ..." when those env vars are set
line=$(java -version 2>&1 | grep -Ei '(openjdk|java|jre)[a-z ]* version' | head -n 1)
full=$(echo "$line" | awk -F '"' '{print $2}')
major=$(echo "$full" | awk -F '[._-]' '{print $1}')
# legacy "1.8.0_381" style -> 8
if [ "$major" = "1" ]; then
major=$(echo "$full" | awk -F '.' '{print $2}')
fi
case "$major" in
''|*[!0-9]*) major=0 ;;
esac
echo "$major"
}
# Highest OpenJDK major version offered by the package manager, within
# [MIN_JAVA, MAX_JAVA]. Echoes nothing if it can't find one.
latest_installable_jdk() {
local versions=""
if command_exists apt-cache; then
versions=$(apt-cache pkgnames openjdk- 2>/dev/null \
| sed -n 's/^openjdk-\([0-9]\+\)-jdk$/\1/p')
elif command_exists dnf; then
versions=$(dnf -q list --available 'java-*-openjdk-devel' 2>/dev/null \
| sed -n 's/^java-1\?\.\?\([0-9]\+\)[^ ]*-openjdk-devel\..*/\1/p')
elif command_exists pacman; then
versions=$(pacman -Ssq '^jdk[0-9]+-openjdk$' 2>/dev/null \
| sed -n 's/^jdk\([0-9]\+\)-openjdk$/\1/p')
fi
echo "$versions" | awk -v min="$MIN_JAVA" -v max="$MAX_JAVA" \
'$1 ~ /^[0-9]+$/ && $1 >= min && $1 <= max && $1 > best { best = $1 }
END { if (best) print best }'
}
# Install a given OpenJDK major version via the system package manager
install_jdk() {
local ver="$1"
if command_exists apt; then
sudo apt update && sudo apt install -y "openjdk-${ver}-jdk"
elif command_exists dnf; then
sudo dnf install -y "java-${ver}-openjdk-devel"
elif command_exists pacman; then
sudo pacman -S --noconfirm "jdk${ver}-openjdk"
if command_exists java; then
java -version 2>&1 | head -n 1 | awk -F '"' '{print $2}' | awk -F '.' '{print $1}'
else
echo -e "${RED}Could not detect package manager. Please install Java manually.${NC}"
return 1
echo "0"
fi
}
# The JDK directory holding the java on PATH
java_home_from_path() {
local j
j=$(command -v java) || return 1
j=$(readlink -f "$j") || return 1
dirname "$(dirname "$j")"
}
# Gradle honours JAVA_HOME ahead of PATH, so a JAVA_HOME left over from a
# hand-installed JDK breaks every build even when `java` itself works fine.
fix_java_home() {
local detected
detected=$(java_home_from_path) || return 0
[ -n "$JAVA_HOME" ] && [ -x "$JAVA_HOME/bin/java" ] && return 0
if [ -n "$JAVA_HOME" ]; then
echo -e "${YELLOW}!${NC} JAVA_HOME is set to ${JAVA_HOME}, which has no bin/java"
echo " Using ${detected} for this run. To fix it permanently, put this in"
echo " your shell profile (it follows update-alternatives, so it won't go stale):"
echo " export JAVA_HOME=\$(dirname \"\$(dirname \"\$(readlink -f \"\$(command -v java)\")\")\")"
fi
export JAVA_HOME="$detected"
}
echo "Checking dependencies..."
echo ""
# Check Java
JAVA_VERSION=$(get_java_version)
LATEST_JDK=$(latest_installable_jdk)
if [ "$JAVA_VERSION" -ge "$MIN_JAVA" ]; then
if [ "$JAVA_VERSION" -ge 11 ]; then
echo -e "${GREEN}${NC} Java $JAVA_VERSION detected"
java -version 2>&1 | grep -Ei '(openjdk|java|jre)[a-z ]* version' | head -n 1
java -version 2>&1 | head -n 1
JAVA_OK=true
else
echo -e "${RED}${NC} Java ${MIN_JAVA}+ not found"
echo -e "${RED}${NC} Java 11+ not found"
JAVA_OK=false
fi
# Check Git
if command_exists git; then
echo -e "${GREEN}${NC} Git detected"
GIT_OK=true
DEPENDENCIES_OK=true
else
echo -e "${RED}${NC} Git not found"
GIT_OK=false
DEPENDENCIES_OK=false
fi
# Check if gradlew exists
@ -144,36 +65,45 @@ fi
echo ""
# Install the newest supported JDK, either because none is present or because
# the installed one is behind what the package manager offers
SUGGEST=${LATEST_JDK:-$MAX_JAVA}
# If Java is missing, offer installation help
if [ "$JAVA_OK" = false ]; then
echo -e "${YELLOW}Java ${MIN_JAVA}+ is required to build and run jrunner${NC}"
echo -e "${YELLOW}Java 11+ is required to build and run jrunner${NC}"
echo ""
echo "Installation options:"
echo ""
echo "1. Install OpenJDK via package manager (recommended):"
echo " Ubuntu/Debian: sudo apt update && sudo apt install openjdk-${SUGGEST}-jdk"
echo " Fedora/RHEL: sudo dnf install java-${SUGGEST}-openjdk-devel"
echo " Arch: sudo pacman -S jdk${SUGGEST}-openjdk"
echo " Ubuntu/Debian: sudo apt update && sudo apt install openjdk-17-jdk"
echo " Fedora/RHEL: sudo dnf install java-17-openjdk-devel"
echo " Arch: sudo pacman -S jdk-openjdk"
echo ""
echo "2. Download a JDK tarball manually:"
echo " Visit: https://adoptium.net/temurin/releases/"
echo " Download JDK ${MIN_JAVA}-${MAX_JAVA} and extract to /opt"
echo " export JAVA_HOME=/opt/jdk-${SUGGEST} && export PATH=\$PATH:\$JAVA_HOME/bin"
echo "2. Download Oracle JDK manually:"
echo " Visit: https://www.oracle.com/java/technologies/downloads/"
echo " Download JDK 17+ tarball and extract to /opt"
echo " Add to PATH: export JAVA_HOME=/opt/jdk-17 && export PATH=\$PATH:\$JAVA_HOME/bin"
echo ""
read -p "Install OpenJDK ${SUGGEST} via package manager? (requires sudo) [y/N]: " install_java || install_java=""
read -p "Would you like to install OpenJDK via package manager? (requires sudo) [y/N]: " install_java
if [[ "$install_java" =~ ^[Yy]$ ]]; then
echo "Installing OpenJDK ${SUGGEST}..."
install_jdk "$SUGGEST" || exit 1
if command_exists apt; then
echo "Installing OpenJDK 17 via apt..."
sudo apt update
sudo apt install -y openjdk-17-jdk
elif command_exists dnf; then
echo "Installing OpenJDK 17 via dnf..."
sudo dnf install -y java-17-openjdk-devel
elif command_exists pacman; then
echo "Installing OpenJDK via pacman..."
sudo pacman -S --noconfirm jdk-openjdk
else
echo -e "${RED}Could not detect package manager. Please install Java manually.${NC}"
exit 1
fi
# a fresh install relocates java; re-derive JAVA_HOME from PATH
unset JAVA_HOME
# Verify installation
JAVA_VERSION=$(get_java_version)
if [ "$JAVA_VERSION" -ge "$MIN_JAVA" ]; then
echo -e "${GREEN}✓ Java $JAVA_VERSION installed successfully${NC}"
if [ "$JAVA_VERSION" -ge 11 ]; then
echo -e "${GREEN}✓ Java installed successfully${NC}"
JAVA_OK=true
else
echo -e "${RED}✗ Java installation failed${NC}"
@ -184,30 +114,12 @@ if [ "$JAVA_OK" = false ]; then
echo "Please install Java manually and re-run this script."
exit 1
fi
elif [ -n "$LATEST_JDK" ] && [ "$LATEST_JDK" -gt "$JAVA_VERSION" ]; then
echo -e "${YELLOW}!${NC} OpenJDK ${LATEST_JDK} is available (you are on ${JAVA_VERSION});"
echo " ${MAX_JAVA} is the newest the Gradle ${GRADLE_WRAPPER_VERSION:-8.5} wrapper can run on."
read -p "Upgrade to OpenJDK ${LATEST_JDK}? (requires sudo) [y/N]: " upgrade_java || upgrade_java=""
if [[ "$upgrade_java" =~ ^[Yy]$ ]]; then
echo "Installing OpenJDK ${LATEST_JDK}..."
if install_jdk "$LATEST_JDK"; then
unset JAVA_HOME
JAVA_VERSION=$(get_java_version)
echo -e "${GREEN}✓ Now on Java ${JAVA_VERSION}${NC}"
else
echo -e "${YELLOW}Upgrade failed; continuing with Java ${JAVA_VERSION}${NC}"
fi
fi
fi
# Point JAVA_HOME at a JDK that actually exists before invoking Gradle
fix_java_home
echo ""
# Check for unzip (needed for deployment)
if ! command_exists unzip; then
echo -e "${YELLOW}Note: 'unzip' is recommended for deployment${NC}"
read -p "Install unzip? [y/N]: " install_unzip || install_unzip=""
read -p "Install unzip? [y/N]: " install_unzip
if [[ "$install_unzip" =~ ^[Yy]$ ]]; then
if command_exists apt; then
sudo apt install -y unzip
@ -220,12 +132,7 @@ if ! command_exists unzip; then
fi
echo ""
if [ "$GIT_OK" = false ]; then
echo -e "${RED}Error: git is required. Install it and re-run this script.${NC}"
exit 1
fi
echo -e "${GREEN}All required dependencies are installed!${NC}"
echo " Java ${JAVA_VERSION} @ ${JAVA_HOME:-$(java_home_from_path)}"
echo ""
# Verify we're in the right directory
@ -238,13 +145,10 @@ fi
chmod +x ./gradlew
echo "Testing build system..."
# keep the output: gradle's own message names the cause (bad JAVA_HOME, no
# network for the distribution download, ...) and a bare "failed" hides it
if GRADLE_OUT=$(./gradlew --version 2>&1); then
if ./gradlew --version > /dev/null 2>&1; then
echo -e "${GREEN}${NC} Gradle wrapper working"
else
echo -e "${RED}${NC} Gradle wrapper failed"
echo "$GRADLE_OUT"
exit 1
fi