Compare commits
No commits in common. "master" and "passfile" have entirely different histories.
43
CLAUDE.md
43
CLAUDE.md
@ -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.
|
||||
|
||||
@ -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":
|
||||
|
||||
@ -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
172
setup.sh
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user