Skip to content

Commit 19221c9

Browse files
committed
SCANJLIB-220 Get full path of java on windows
1 parent 7037c2e commit 19221c9

5 files changed

Lines changed: 150 additions & 12 deletions

File tree

lib/src/main/java/org/sonarsource/scanner/lib/internal/JavaRunnerFactory.java

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@
2222
import com.google.gson.Gson;
2323
import com.google.gson.annotations.SerializedName;
2424
import com.google.gson.reflect.TypeToken;
25+
import java.io.BufferedReader;
2526
import java.io.FileOutputStream;
2627
import java.io.IOException;
28+
import java.io.InputStreamReader;
2729
import java.lang.reflect.Type;
2830
import java.nio.channels.FileChannel;
2931
import java.nio.channels.FileLock;
3032
import java.nio.channels.OverlappingFileLockException;
33+
import java.nio.charset.StandardCharsets;
3134
import java.nio.file.Files;
3235
import java.nio.file.Path;
3336
import java.nio.file.Paths;
@@ -37,7 +40,6 @@
3740
import java.util.Optional;
3841
import javax.annotation.Nullable;
3942
import org.apache.commons.lang3.StringUtils;
40-
import org.apache.commons.lang3.SystemUtils;
4143
import org.sonarsource.scanner.lib.System2;
4244
import org.sonarsource.scanner.lib.internal.cache.CachedFile;
4345
import org.sonarsource.scanner.lib.internal.cache.FileCache;
@@ -61,10 +63,12 @@ public class JavaRunnerFactory {
6163

6264
private final Logger logger;
6365
private final System2 system;
66+
private final ProcessWrapperFactory processWrapperFactory;
6467

65-
public JavaRunnerFactory(Logger logger, System2 system) {
68+
public JavaRunnerFactory(Logger logger, System2 system, ProcessWrapperFactory processWrapperFactory) {
6669
this.logger = logger;
6770
this.system = system;
71+
this.processWrapperFactory = processWrapperFactory;
6872
}
6973

7074
public JavaRunner createRunner(ServerConnection serverConnection, FileCache fileCache, Map<String, String> properties) {
@@ -83,7 +87,7 @@ public JavaRunner createRunner(ServerConnection serverConnection, FileCache file
8387
}
8488
}
8589
String javaHome = system.getEnvironmentVariable("JAVA_HOME");
86-
var javaExe = "java" + (SystemUtils.IS_OS_WINDOWS ? ".exe" : "");
90+
var javaExe = "java" + (isOsWindows() ? ".exe" : "");
8791
if (javaHome != null) {
8892
var javaExecutable = Paths.get(javaHome, "bin", javaExe);
8993
if (Files.exists(javaExecutable)) {
@@ -92,7 +96,36 @@ public JavaRunner createRunner(ServerConnection serverConnection, FileCache file
9296
}
9397
}
9498
logger.info("The java executable in the PATH will be used");
95-
return new JavaRunner(Paths.get(javaExe), logger, JreCacheHit.DISABLED);
99+
return new JavaRunner(isOsWindows() ? findJavaInPath(javaExe) : Paths.get(javaExe), logger, JreCacheHit.DISABLED);
100+
}
101+
102+
private boolean isOsWindows() {
103+
String osName = system.getProperty("os.name");
104+
return osName != null && osName.startsWith("Windows");
105+
}
106+
107+
private Path findJavaInPath(String javaExe) {
108+
// Windows will search current directory in addition to the PATH variable, which is unsecure.
109+
// To avoid it we use where.exe to find the java binary only in PATH.
110+
try {
111+
ProcessWrapperFactory.ProcessWrapper process = processWrapperFactory.create("C:\\Windows\\System32\\where.exe", "$PATH:" + javaExe);
112+
113+
Path javaExecutable;
114+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
115+
javaExecutable = Paths.get(reader.lines().findFirst().orElseThrow());
116+
logger.debug(format("Found java executable in PATH at '%s'", javaExecutable.toAbsolutePath()));
117+
}
118+
119+
int exit = process.waitFor();
120+
if (exit != 0) {
121+
throw new IllegalStateException(format("Command execution exited with code: %d", exit));
122+
}
123+
124+
return javaExecutable;
125+
} catch (Exception e) {
126+
Thread.currentThread().interrupt();
127+
throw new IllegalStateException("Cannot find java executable in PATH", e);
128+
}
96129
}
97130

98131
private Optional<CachedFile> getJreFromServer(ServerConnection serverConnection, FileCache fileCache, Map<String, String> properties, boolean retry) {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* SonarScanner Java Library
3+
* Copyright (C) 2011-2024 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonarsource.scanner.lib.internal;
21+
22+
import java.io.IOException;
23+
import java.io.InputStream;
24+
25+
public class ProcessWrapperFactory {
26+
27+
public ProcessWrapper create(String... command) throws IOException {
28+
return new ProcessWrapper(new ProcessBuilder()
29+
.command(command)
30+
.start());
31+
}
32+
33+
public static class ProcessWrapper {
34+
private final Process process;
35+
36+
public ProcessWrapper(Process process) {
37+
this.process = process;
38+
}
39+
40+
public InputStream getInputStream() {
41+
return process.getInputStream();
42+
}
43+
44+
public int waitFor() throws InterruptedException {
45+
return process.waitFor();
46+
}
47+
}
48+
}

lib/src/main/java/org/sonarsource/scanner/lib/internal/ScannerEngineLauncherFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public class ScannerEngineLauncherFactory {
4141

4242
public ScannerEngineLauncherFactory(Logger logger, System2 system) {
4343
this.logger = logger;
44-
this.javaRunnerFactory = new JavaRunnerFactory(logger, system);
44+
this.javaRunnerFactory = new JavaRunnerFactory(logger, system, new ProcessWrapperFactory());
4545
}
4646

4747
ScannerEngineLauncherFactory(Logger logger, JavaRunnerFactory javaRunnerFactory) {

lib/src/test/java/org/sonarsource/scanner/lib/internal/JavaRunnerFactoryTest.java

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import java.util.Map;
3030
import org.apache.commons.io.FileUtils;
3131
import org.apache.commons.io.IOUtils;
32-
import org.apache.commons.lang3.SystemUtils;
32+
import org.junit.jupiter.api.BeforeEach;
3333
import org.junit.jupiter.api.Test;
3434
import org.junit.jupiter.api.io.TempDir;
3535
import org.sonarsource.scanner.lib.System2;
@@ -58,12 +58,18 @@ class JavaRunnerFactoryTest {
5858
private final FileCache fileCache = mock(FileCache.class);
5959
private final System2 system = mock(System2.class);
6060
private final Logger logger = mock(Logger.class);
61+
private final ProcessWrapperFactory processWrapperFactory = mock(ProcessWrapperFactory.class);
6162

62-
private final JavaRunnerFactory underTest = new JavaRunnerFactory(logger, system);
63+
private final JavaRunnerFactory underTest = new JavaRunnerFactory(logger, system, processWrapperFactory);
6364

6465
@TempDir
6566
private Path temp;
6667

68+
@BeforeEach
69+
void setUp() {
70+
when(system.getProperty("os.name")).thenReturn("linux");
71+
}
72+
6773
@Test
6874
void createRunner_jreProvisioning() throws IOException {
6975
var jre = temp.resolve("fake-jre.zip");
@@ -86,7 +92,7 @@ void createRunner_jreProvisioning_noMatch_fallback_to_local() throws IOException
8692

8793
JavaRunner runner = underTest.createRunner(serverConnection, fileCache, props);
8894

89-
assertThat(runner.getJavaExecutable()).isEqualTo(Paths.get("java"+ (SystemUtils.IS_OS_WINDOWS ? ".exe" : "")));
95+
assertThat(runner.getJavaExecutable()).isEqualTo(Paths.get("java"));
9096
assertThat(runner.getJreCacheHit()).isEqualTo(JreCacheHit.DISABLED);
9197
}
9298

@@ -105,15 +111,14 @@ void createRunner_jreExeProperty() {
105111
void createRunner_jreProvisioningSkipAndJavaHome() throws IOException {
106112
var javaHome = temp.toAbsolutePath();
107113
Files.createDirectories(javaHome.resolve("bin"));
108-
Files.createFile(javaHome.resolve("bin/java" + (SystemUtils.IS_OS_WINDOWS ? ".exe" : "")));
114+
Files.createFile(javaHome.resolve("bin/java"));
109115

110116
when(system.getEnvironmentVariable("JAVA_HOME")).thenReturn(javaHome.toString());
111117
Map<String, String> properties = Map.of(SKIP_JRE_PROVISIONING, "true");
112118

113119
JavaRunner runner = underTest.createRunner(serverConnection, fileCache, properties);
114120

115-
assertThat(runner.getJavaExecutable()).isEqualTo(
116-
temp.resolve("bin/java" + (SystemUtils.IS_OS_WINDOWS ? ".exe" : "")));
121+
assertThat(runner.getJavaExecutable()).isEqualTo(temp.resolve("bin/java"));
117122
assertThat(runner.getJreCacheHit()).isEqualTo(JreCacheHit.DISABLED);
118123
}
119124

@@ -123,7 +128,23 @@ void createRunner_jreProvisioningSkipAndNoJavaHome() {
123128

124129
JavaRunner runner = underTest.createRunner(serverConnection, fileCache, properties);
125130

126-
assertThat(runner.getJavaExecutable()).isEqualTo(Paths.get("java" + (SystemUtils.IS_OS_WINDOWS ? ".exe" : "")));
131+
assertThat(runner.getJavaExecutable()).isEqualTo(Paths.get("java"));
132+
assertThat(runner.getJreCacheHit()).isEqualTo(JreCacheHit.DISABLED);
133+
}
134+
135+
@Test
136+
void createRunner_jreProvisioningSkipAndNoJavaHome_windows() throws IOException, InterruptedException {
137+
when(system.getProperty("os.name")).thenReturn("Windows 10");
138+
ProcessWrapperFactory.ProcessWrapper processWrapper = mock(ProcessWrapperFactory.ProcessWrapper.class);
139+
when(processWrapper.getInputStream()).thenReturn(IOUtils.toInputStream("C:\\bin\\java.exe", StandardCharsets.UTF_8));
140+
when(processWrapper.waitFor()).thenReturn(0);
141+
when(processWrapperFactory.create("C:\\Windows\\System32\\where.exe", "$PATH:java.exe")).thenReturn(processWrapper);
142+
143+
Map<String, String> properties = Map.of(SKIP_JRE_PROVISIONING, "true");
144+
145+
JavaRunner runner = underTest.createRunner(serverConnection, fileCache, properties);
146+
147+
assertThat(runner.getJavaExecutable()).isEqualTo(Paths.get("C:\\bin\\java.exe"));
127148
assertThat(runner.getJreCacheHit()).isEqualTo(JreCacheHit.DISABLED);
128149
}
129150

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* SonarScanner Java Library
3+
* Copyright (C) 2011-2024 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonarsource.scanner.lib.internal;
21+
22+
import java.io.IOException;
23+
import java.nio.charset.StandardCharsets;
24+
import org.junit.jupiter.api.Test;
25+
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
28+
class ProcessWrapperFactoryTest {
29+
30+
@Test
31+
void create() throws IOException, InterruptedException {
32+
ProcessWrapperFactory.ProcessWrapper process = new ProcessWrapperFactory().create("java", "--version");
33+
assertThat(process.getInputStream()).asString(StandardCharsets.UTF_8).isNotNull();
34+
assertThat(process.waitFor()).isZero();
35+
}
36+
}

0 commit comments

Comments
 (0)