mobile-debug-mcp 0.20.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/dist/manage/android.js +5 -2
- package/dist/system/gradle.js +101 -0
- package/dist/system/index.js +8 -2
- package/dist/utils/android/utils.js +33 -1
- package/docs/CHANGELOG.md +3 -0
- package/package.json +1 -1
- package/src/manage/android.ts +5 -1
- package/src/system/gradle.ts +98 -0
- package/src/system/index.ts +8 -2
- package/src/utils/android/utils.ts +27 -1
package/README.md
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
A minimal, secure MCP server for AI-assisted mobile development. Build, install, and inspect Android/iOS apps from an MCP-compatible client.
|
|
4
4
|
|
|
5
|
-
> **Note:**
|
|
5
|
+
> **Note:**
|
|
6
|
+
> * iOS only tested on simulator.
|
|
7
|
+
> * Flutter iOS projects not fetching logs
|
|
8
|
+
> * React native not tested
|
|
6
9
|
|
|
7
10
|
## Requirements
|
|
8
11
|
|
|
@@ -28,10 +31,9 @@ You will need to add ADB_PATH for Android and XCRUN_PATH and IDB_PATH for iOS.
|
|
|
28
31
|
|
|
29
32
|
## Usage
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
I have a crash on the app, can you diagnose it, fix and validate using the mcp tools available
|
|
34
|
+
Examples:
|
|
35
|
+
* I have a crash on the app, can you diagnose it, fix and validate using the mcp tools available
|
|
36
|
+
* Add a button, hook into the repository and confirm API request successful
|
|
35
37
|
|
|
36
38
|
## Docs
|
|
37
39
|
|
package/dist/manage/android.js
CHANGED
|
@@ -64,10 +64,13 @@ export class AndroidManage {
|
|
|
64
64
|
const spawnOpts = { cwd: apkPath, env };
|
|
65
65
|
if (useWrapper) {
|
|
66
66
|
await fs.chmod(wrapperPath, 0o755).catch(() => { });
|
|
67
|
+
// Run wrapper directly to avoid shell splitting of args
|
|
68
|
+
spawnOpts.shell = false;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// Execute gradle directly without a shell so paths with spaces are preserved
|
|
67
72
|
spawnOpts.shell = false;
|
|
68
73
|
}
|
|
69
|
-
else
|
|
70
|
-
spawnOpts.shell = true;
|
|
71
74
|
const proc = spawn(execCmd, gradleArgs, spawnOpts);
|
|
72
75
|
let stderr = '';
|
|
73
76
|
await new Promise((resolve, reject) => {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
function readPropertiesFile(p) {
|
|
5
|
+
try {
|
|
6
|
+
const txt = readFileSync(p, { encoding: 'utf8' });
|
|
7
|
+
const lines = String(txt).split(/\r?\n/);
|
|
8
|
+
const out = {};
|
|
9
|
+
for (const l of lines) {
|
|
10
|
+
const trimmed = l.trim();
|
|
11
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
12
|
+
continue;
|
|
13
|
+
const idx = trimmed.indexOf('=');
|
|
14
|
+
if (idx === -1)
|
|
15
|
+
continue;
|
|
16
|
+
const k = trimmed.substring(0, idx).trim();
|
|
17
|
+
const v = trimmed.substring(idx + 1).trim();
|
|
18
|
+
out[k] = v;
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function javaBinExists(p) {
|
|
27
|
+
if (!p)
|
|
28
|
+
return false;
|
|
29
|
+
try {
|
|
30
|
+
const javaPath = path.join(p, 'bin', 'java');
|
|
31
|
+
if (existsSync(javaPath))
|
|
32
|
+
return true;
|
|
33
|
+
const alt = path.join(p, 'Contents', 'Home', 'bin', 'java');
|
|
34
|
+
if (existsSync(alt))
|
|
35
|
+
return true;
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export async function checkGradle() {
|
|
43
|
+
const issues = [];
|
|
44
|
+
const filesChecked = [];
|
|
45
|
+
const suggestedFixes = [];
|
|
46
|
+
let gradleJavaHome;
|
|
47
|
+
// 1) explicit env
|
|
48
|
+
if (process.env.GRADLE_JAVA_HOME) {
|
|
49
|
+
gradleJavaHome = process.env.GRADLE_JAVA_HOME;
|
|
50
|
+
if (!javaBinExists(gradleJavaHome)) {
|
|
51
|
+
issues.push(`GRADLE_JAVA_HOME is set to '${gradleJavaHome}' but no java binary was found there`);
|
|
52
|
+
suggestedFixes.push('Unset GRADLE_JAVA_HOME or point it to a valid JDK (e.g., /Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home)');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// 2) user gradle.properties
|
|
56
|
+
const gradleUserHome = process.env.GRADLE_USER_HOME || path.join(os.homedir(), '.gradle');
|
|
57
|
+
const userProps = path.join(gradleUserHome, 'gradle.properties');
|
|
58
|
+
filesChecked.push(userProps);
|
|
59
|
+
try {
|
|
60
|
+
const props = readPropertiesFile(userProps);
|
|
61
|
+
if (props['org.gradle.java.home']) {
|
|
62
|
+
const p = props['org.gradle.java.home'];
|
|
63
|
+
gradleJavaHome = gradleJavaHome || p;
|
|
64
|
+
if (!javaBinExists(p)) {
|
|
65
|
+
issues.push(`org.gradle.java.home in ${userProps} points to '${p}' which does not look like a valid JDK`);
|
|
66
|
+
suggestedFixes.push(`Edit ${userProps} to remove or correct org.gradle.java.home`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (e) { /* ignore */ }
|
|
71
|
+
// 3) system gradle.properties
|
|
72
|
+
const systemProps = '/etc/gradle/gradle.properties';
|
|
73
|
+
filesChecked.push(systemProps);
|
|
74
|
+
try {
|
|
75
|
+
const props = readPropertiesFile(systemProps);
|
|
76
|
+
if (props['org.gradle.java.home']) {
|
|
77
|
+
const p = props['org.gradle.java.home'];
|
|
78
|
+
gradleJavaHome = gradleJavaHome || p;
|
|
79
|
+
if (!javaBinExists(p)) {
|
|
80
|
+
issues.push(`org.gradle.java.home in ${systemProps} points to '${p}' which does not look like a valid JDK`);
|
|
81
|
+
suggestedFixes.push(`Edit ${systemProps} to remove or correct org.gradle.java.home`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (e) { /* ignore */ }
|
|
86
|
+
// 4) GRADLE_HOME fallback
|
|
87
|
+
if (!gradleJavaHome && process.env.GRADLE_HOME) {
|
|
88
|
+
filesChecked.push(process.env.GRADLE_HOME);
|
|
89
|
+
if (javaBinExists(process.env.GRADLE_HOME)) {
|
|
90
|
+
gradleJavaHome = process.env.GRADLE_HOME;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const gradleValid = !!gradleJavaHome && javaBinExists(gradleJavaHome);
|
|
94
|
+
if (!gradleJavaHome) {
|
|
95
|
+
// no explicit gradle java home detected — not an issue
|
|
96
|
+
}
|
|
97
|
+
else if (!gradleValid) {
|
|
98
|
+
issues.push(`Detected org.gradle.java.home = '${gradleJavaHome}' but it is invalid`);
|
|
99
|
+
}
|
|
100
|
+
return { gradleJavaHome, gradleValid, filesChecked, issues, suggestedFixes };
|
|
101
|
+
}
|
package/dist/system/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { checkAndroid } from './android.js';
|
|
2
2
|
import { checkIOS } from './ios.js';
|
|
3
|
+
import { checkGradle } from './gradle.js';
|
|
3
4
|
export async function getSystemStatus() {
|
|
4
5
|
try {
|
|
5
6
|
const android = await checkAndroid();
|
|
6
7
|
const ios = await checkIOS();
|
|
7
|
-
const
|
|
8
|
+
const gradle = await checkGradle();
|
|
9
|
+
const issues = [...android.issues, ...ios.issues, ...(gradle.issues || [])];
|
|
8
10
|
const success = issues.length === 0;
|
|
9
11
|
return {
|
|
10
12
|
success,
|
|
@@ -17,7 +19,11 @@ export async function getSystemStatus() {
|
|
|
17
19
|
issues,
|
|
18
20
|
appInstalled: android.appInstalled,
|
|
19
21
|
iosAvailable: ios.iosAvailable,
|
|
20
|
-
iosDevices: ios.iosDevices
|
|
22
|
+
iosDevices: ios.iosDevices,
|
|
23
|
+
gradleJavaHome: gradle.gradleJavaHome,
|
|
24
|
+
gradleValid: gradle.gradleValid,
|
|
25
|
+
gradleFilesChecked: gradle.filesChecked,
|
|
26
|
+
gradleSuggestedFixes: gradle.suggestedFixes
|
|
21
27
|
};
|
|
22
28
|
}
|
|
23
29
|
catch (e) {
|
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { detectJavaHome } from '../java.js';
|
|
4
4
|
import { execCmd } from '../exec.js';
|
|
5
5
|
import { spawnSync } from 'child_process';
|
|
6
|
+
import { checkGradle } from '../../system/gradle.js';
|
|
6
7
|
function findInPath(cmd) {
|
|
7
8
|
try {
|
|
8
9
|
// prefer command -v for POSIX
|
|
@@ -76,6 +77,14 @@ export async function prepareGradle(projectPath) {
|
|
|
76
77
|
gradleArgs.push('-Dorg.gradle.caching=false');
|
|
77
78
|
}
|
|
78
79
|
const detectedJavaHome = await detectJavaHome().catch(() => undefined);
|
|
80
|
+
// Check for problematic org.gradle.java.home entries (env or properties) and avoid passing invalid values to Gradle
|
|
81
|
+
let gradleCheck;
|
|
82
|
+
try {
|
|
83
|
+
gradleCheck = await checkGradle();
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
gradleCheck = { gradleJavaHome: undefined, gradleValid: false, filesChecked: [], issues: [] };
|
|
87
|
+
}
|
|
79
88
|
const env = Object.assign({}, process.env);
|
|
80
89
|
// Ensure child processes can find Android platform-tools (adb, etc.) by
|
|
81
90
|
// prepending the platform-tools directory to PATH for spawned processes.
|
|
@@ -90,6 +99,7 @@ export async function prepareGradle(projectPath) {
|
|
|
90
99
|
console.debug(`[prepareGradle] error resolving adbPath: ${String(e)}`);
|
|
91
100
|
}
|
|
92
101
|
const pathParts = [];
|
|
102
|
+
// Prefer a detected (validated) Java home from the system/IDE
|
|
93
103
|
if (detectedJavaHome) {
|
|
94
104
|
if (env.JAVA_HOME !== detectedJavaHome) {
|
|
95
105
|
env.JAVA_HOME = detectedJavaHome;
|
|
@@ -100,6 +110,26 @@ export async function prepareGradle(projectPath) {
|
|
|
100
110
|
gradleArgs.push('--no-daemon');
|
|
101
111
|
env.GRADLE_JAVA_HOME = detectedJavaHome;
|
|
102
112
|
}
|
|
113
|
+
else if (gradleCheck && gradleCheck.gradleJavaHome) {
|
|
114
|
+
// There's an org.gradle.java.home configured somewhere (env or gradle.properties)
|
|
115
|
+
if (gradleCheck.gradleValid) {
|
|
116
|
+
const p = gradleCheck.gradleJavaHome;
|
|
117
|
+
const javaBin = path.join(p, 'bin');
|
|
118
|
+
if (!env.PATH || !env.PATH.includes(javaBin))
|
|
119
|
+
pathParts.push(javaBin);
|
|
120
|
+
gradleArgs.push(`-Dorg.gradle.java.home=${p}`);
|
|
121
|
+
gradleArgs.push('--no-daemon');
|
|
122
|
+
env.GRADLE_JAVA_HOME = p;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
// Invalid gradle java home detected: avoid passing it to Gradle and remove from spawn env
|
|
126
|
+
console.debug(`[prepareGradle] Invalid org.gradle.java.home detected (${gradleCheck.gradleJavaHome}); removing from spawn env to avoid Gradle error.`);
|
|
127
|
+
try {
|
|
128
|
+
delete env.GRADLE_JAVA_HOME;
|
|
129
|
+
}
|
|
130
|
+
catch (e) { }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
103
133
|
if (platformToolsDir) {
|
|
104
134
|
// Prepend platform-tools so gradle and child tools find adb without modifying global env
|
|
105
135
|
if (!env.PATH || !env.PATH.includes(platformToolsDir)) {
|
|
@@ -138,10 +168,12 @@ export async function prepareGradle(projectPath) {
|
|
|
138
168
|
catch (e) {
|
|
139
169
|
console.debug('[prepareGradle] chmod failed for gradlew:', String(e));
|
|
140
170
|
}
|
|
171
|
+
// Execute the wrapper directly without a shell to avoid shell tokenization of args (spaces in paths)
|
|
141
172
|
spawnOpts.shell = false;
|
|
142
173
|
}
|
|
143
174
|
else {
|
|
144
|
-
|
|
175
|
+
// Prefer executing gradle directly without invoking a shell to preserve argument boundaries
|
|
176
|
+
spawnOpts.shell = false;
|
|
145
177
|
}
|
|
146
178
|
return { execCmd, gradleArgs, spawnOpts };
|
|
147
179
|
}
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the **Mobile Debug MCP** project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.20.1]
|
|
6
|
+
- Fixes gradle home issue for android
|
|
7
|
+
|
|
5
8
|
## [0.20.0]
|
|
6
9
|
- Added `get_system_status` tool and refactored system health checks into `src/system`.
|
|
7
10
|
- Provides a fast environment healthcheck (ADB availability/version, connected devices, log access, Android env vars, and basic iOS xcrun/simulator checks).
|
package/package.json
CHANGED
package/src/manage/android.ts
CHANGED
|
@@ -64,8 +64,12 @@ export class AndroidManage {
|
|
|
64
64
|
const spawnOpts: any = { cwd: apkPath, env }
|
|
65
65
|
if (useWrapper) {
|
|
66
66
|
await fs.chmod(wrapperPath, 0o755).catch(() => {})
|
|
67
|
+
// Run wrapper directly to avoid shell splitting of args
|
|
67
68
|
spawnOpts.shell = false
|
|
68
|
-
} else
|
|
69
|
+
} else {
|
|
70
|
+
// Execute gradle directly without a shell so paths with spaces are preserved
|
|
71
|
+
spawnOpts.shell = false
|
|
72
|
+
}
|
|
69
73
|
|
|
70
74
|
const proc = spawn(execCmd, gradleArgs, spawnOpts)
|
|
71
75
|
let stderr = ''
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import os from 'os'
|
|
4
|
+
|
|
5
|
+
function readPropertiesFile(p: string): Record<string,string> {
|
|
6
|
+
try {
|
|
7
|
+
const txt = readFileSync(p, { encoding: 'utf8' })
|
|
8
|
+
const lines = String(txt).split(/\r?\n/)
|
|
9
|
+
const out: Record<string,string> = {}
|
|
10
|
+
for (const l of lines) {
|
|
11
|
+
const trimmed = l.trim()
|
|
12
|
+
if (!trimmed || trimmed.startsWith('#')) continue
|
|
13
|
+
const idx = trimmed.indexOf('=')
|
|
14
|
+
if (idx === -1) continue
|
|
15
|
+
const k = trimmed.substring(0, idx).trim()
|
|
16
|
+
const v = trimmed.substring(idx+1).trim()
|
|
17
|
+
out[k] = v
|
|
18
|
+
}
|
|
19
|
+
return out
|
|
20
|
+
} catch (e: unknown) {
|
|
21
|
+
return {}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function javaBinExists(p?: string): boolean {
|
|
26
|
+
if (!p) return false
|
|
27
|
+
try {
|
|
28
|
+
const javaPath = path.join(p, 'bin', 'java')
|
|
29
|
+
if (existsSync(javaPath)) return true
|
|
30
|
+
const alt = path.join(p, 'Contents', 'Home', 'bin', 'java')
|
|
31
|
+
if (existsSync(alt)) return true
|
|
32
|
+
return false
|
|
33
|
+
} catch (e: unknown) { return false }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function checkGradle(): Promise<{ gradleJavaHome?: string; gradleValid: boolean; filesChecked: string[]; issues: string[]; suggestedFixes?: string[] }> {
|
|
37
|
+
const issues: string[] = []
|
|
38
|
+
const filesChecked: string[] = []
|
|
39
|
+
const suggestedFixes: string[] = []
|
|
40
|
+
let gradleJavaHome: string | undefined
|
|
41
|
+
|
|
42
|
+
// 1) explicit env
|
|
43
|
+
if (process.env.GRADLE_JAVA_HOME) {
|
|
44
|
+
gradleJavaHome = process.env.GRADLE_JAVA_HOME
|
|
45
|
+
if (!javaBinExists(gradleJavaHome)) {
|
|
46
|
+
issues.push(`GRADLE_JAVA_HOME is set to '${gradleJavaHome}' but no java binary was found there`)
|
|
47
|
+
suggestedFixes.push('Unset GRADLE_JAVA_HOME or point it to a valid JDK (e.g., /Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home)')
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 2) user gradle.properties
|
|
52
|
+
const gradleUserHome = process.env.GRADLE_USER_HOME || path.join(os.homedir(), '.gradle')
|
|
53
|
+
const userProps = path.join(gradleUserHome, 'gradle.properties')
|
|
54
|
+
filesChecked.push(userProps)
|
|
55
|
+
try {
|
|
56
|
+
const props = readPropertiesFile(userProps)
|
|
57
|
+
if (props['org.gradle.java.home']) {
|
|
58
|
+
const p = props['org.gradle.java.home']
|
|
59
|
+
gradleJavaHome = gradleJavaHome || p
|
|
60
|
+
if (!javaBinExists(p)) {
|
|
61
|
+
issues.push(`org.gradle.java.home in ${userProps} points to '${p}' which does not look like a valid JDK`)
|
|
62
|
+
suggestedFixes.push(`Edit ${userProps} to remove or correct org.gradle.java.home`)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch (e: unknown) { /* ignore */ }
|
|
66
|
+
|
|
67
|
+
// 3) system gradle.properties
|
|
68
|
+
const systemProps = '/etc/gradle/gradle.properties'
|
|
69
|
+
filesChecked.push(systemProps)
|
|
70
|
+
try {
|
|
71
|
+
const props = readPropertiesFile(systemProps)
|
|
72
|
+
if (props['org.gradle.java.home']) {
|
|
73
|
+
const p = props['org.gradle.java.home']
|
|
74
|
+
gradleJavaHome = gradleJavaHome || p
|
|
75
|
+
if (!javaBinExists(p)) {
|
|
76
|
+
issues.push(`org.gradle.java.home in ${systemProps} points to '${p}' which does not look like a valid JDK`)
|
|
77
|
+
suggestedFixes.push(`Edit ${systemProps} to remove or correct org.gradle.java.home`)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (e: unknown) { /* ignore */ }
|
|
81
|
+
|
|
82
|
+
// 4) GRADLE_HOME fallback
|
|
83
|
+
if (!gradleJavaHome && process.env.GRADLE_HOME) {
|
|
84
|
+
filesChecked.push(process.env.GRADLE_HOME)
|
|
85
|
+
if (javaBinExists(process.env.GRADLE_HOME)) {
|
|
86
|
+
gradleJavaHome = process.env.GRADLE_HOME
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const gradleValid = !!gradleJavaHome && javaBinExists(gradleJavaHome)
|
|
91
|
+
if (!gradleJavaHome) {
|
|
92
|
+
// no explicit gradle java home detected — not an issue
|
|
93
|
+
} else if (!gradleValid) {
|
|
94
|
+
issues.push(`Detected org.gradle.java.home = '${gradleJavaHome}' but it is invalid`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { gradleJavaHome, gradleValid, filesChecked, issues, suggestedFixes }
|
|
98
|
+
}
|
package/src/system/index.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { checkAndroid } from './android.js'
|
|
2
2
|
import { checkIOS } from './ios.js'
|
|
3
|
+
import { checkGradle } from './gradle.js'
|
|
3
4
|
|
|
4
5
|
export async function getSystemStatus() {
|
|
5
6
|
try {
|
|
6
7
|
const android = await checkAndroid()
|
|
7
8
|
const ios = await checkIOS()
|
|
8
|
-
const
|
|
9
|
+
const gradle = await checkGradle()
|
|
10
|
+
const issues = [...android.issues, ...ios.issues, ...(gradle.issues || [])]
|
|
9
11
|
|
|
10
12
|
const success = issues.length === 0
|
|
11
13
|
return {
|
|
@@ -19,7 +21,11 @@ export async function getSystemStatus() {
|
|
|
19
21
|
issues,
|
|
20
22
|
appInstalled: android.appInstalled,
|
|
21
23
|
iosAvailable: ios.iosAvailable,
|
|
22
|
-
iosDevices: ios.iosDevices
|
|
24
|
+
iosDevices: ios.iosDevices,
|
|
25
|
+
gradleJavaHome: gradle.gradleJavaHome,
|
|
26
|
+
gradleValid: gradle.gradleValid,
|
|
27
|
+
gradleFilesChecked: gradle.filesChecked,
|
|
28
|
+
gradleSuggestedFixes: gradle.suggestedFixes
|
|
23
29
|
}
|
|
24
30
|
} catch (e: unknown) {
|
|
25
31
|
return { success: false, issues: ['Internal error: ' + (e instanceof Error ? e.message : String(e))] }
|
|
@@ -4,6 +4,7 @@ import path from 'path'
|
|
|
4
4
|
import { detectJavaHome } from '../java.js'
|
|
5
5
|
import { execCmd } from '../exec.js'
|
|
6
6
|
import { spawnSync } from 'child_process'
|
|
7
|
+
import { checkGradle } from '../../system/gradle.js'
|
|
7
8
|
|
|
8
9
|
function findInPath(cmd: string): string | null {
|
|
9
10
|
try {
|
|
@@ -73,6 +74,14 @@ export async function prepareGradle(projectPath: string): Promise<{ execCmd: str
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
const detectedJavaHome = await detectJavaHome().catch(() => undefined)
|
|
77
|
+
// Check for problematic org.gradle.java.home entries (env or properties) and avoid passing invalid values to Gradle
|
|
78
|
+
let gradleCheck
|
|
79
|
+
try {
|
|
80
|
+
gradleCheck = await checkGradle()
|
|
81
|
+
} catch (e: unknown) {
|
|
82
|
+
gradleCheck = { gradleJavaHome: undefined, gradleValid: false, filesChecked: [], issues: [] }
|
|
83
|
+
}
|
|
84
|
+
|
|
76
85
|
const env = Object.assign({}, process.env)
|
|
77
86
|
|
|
78
87
|
// Ensure child processes can find Android platform-tools (adb, etc.) by
|
|
@@ -86,6 +95,7 @@ export async function prepareGradle(projectPath: string): Promise<{ execCmd: str
|
|
|
86
95
|
} catch (e: unknown) { console.debug(`[prepareGradle] error resolving adbPath: ${String(e)}`) }
|
|
87
96
|
|
|
88
97
|
const pathParts: string[] = []
|
|
98
|
+
// Prefer a detected (validated) Java home from the system/IDE
|
|
89
99
|
if (detectedJavaHome) {
|
|
90
100
|
if (env.JAVA_HOME !== detectedJavaHome) {
|
|
91
101
|
env.JAVA_HOME = detectedJavaHome
|
|
@@ -95,6 +105,20 @@ export async function prepareGradle(projectPath: string): Promise<{ execCmd: str
|
|
|
95
105
|
gradleArgs.push(`-Dorg.gradle.java.home=${detectedJavaHome}`)
|
|
96
106
|
gradleArgs.push('--no-daemon')
|
|
97
107
|
env.GRADLE_JAVA_HOME = detectedJavaHome
|
|
108
|
+
} else if (gradleCheck && gradleCheck.gradleJavaHome) {
|
|
109
|
+
// There's an org.gradle.java.home configured somewhere (env or gradle.properties)
|
|
110
|
+
if (gradleCheck.gradleValid) {
|
|
111
|
+
const p = gradleCheck.gradleJavaHome as string
|
|
112
|
+
const javaBin = path.join(p, 'bin')
|
|
113
|
+
if (!env.PATH || !env.PATH.includes(javaBin)) pathParts.push(javaBin)
|
|
114
|
+
gradleArgs.push(`-Dorg.gradle.java.home=${p}`)
|
|
115
|
+
gradleArgs.push('--no-daemon')
|
|
116
|
+
env.GRADLE_JAVA_HOME = p
|
|
117
|
+
} else {
|
|
118
|
+
// Invalid gradle java home detected: avoid passing it to Gradle and remove from spawn env
|
|
119
|
+
console.debug(`[prepareGradle] Invalid org.gradle.java.home detected (${gradleCheck.gradleJavaHome}); removing from spawn env to avoid Gradle error.`)
|
|
120
|
+
try { delete env.GRADLE_JAVA_HOME } catch (e: unknown) { }
|
|
121
|
+
}
|
|
98
122
|
}
|
|
99
123
|
|
|
100
124
|
if (platformToolsDir) {
|
|
@@ -126,9 +150,11 @@ export async function prepareGradle(projectPath: string): Promise<{ execCmd: str
|
|
|
126
150
|
const spawnOpts: any = { cwd: projectPath, env }
|
|
127
151
|
if (useWrapper) {
|
|
128
152
|
try { await fsPromises.chmod(gradlewPath, 0o755) } catch (e: unknown) { console.debug('[prepareGradle] chmod failed for gradlew:', String(e)) }
|
|
153
|
+
// Execute the wrapper directly without a shell to avoid shell tokenization of args (spaces in paths)
|
|
129
154
|
spawnOpts.shell = false
|
|
130
155
|
} else {
|
|
131
|
-
|
|
156
|
+
// Prefer executing gradle directly without invoking a shell to preserve argument boundaries
|
|
157
|
+
spawnOpts.shell = false
|
|
132
158
|
}
|
|
133
159
|
|
|
134
160
|
return { execCmd, gradleArgs, spawnOpts }
|