Skip to content

Commit a9088de

Browse files
committed
SCANJLIB-208 Use uname -m to detect the architecture on Unixes
1 parent 91cb0e9 commit a9088de

4 files changed

Lines changed: 194 additions & 2 deletions

File tree

lib/src/main/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapper.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.apache.commons.io.FileUtils;
3030
import org.slf4j.Logger;
3131
import org.slf4j.LoggerFactory;
32+
import org.sonarsource.scanner.lib.internal.ArchResolver;
3233
import org.sonarsource.scanner.lib.internal.InternalProperties;
3334
import org.sonarsource.scanner.lib.internal.IsolatedLauncherFactory;
3435
import org.sonarsource.scanner.lib.internal.OsResolver;
@@ -155,7 +156,9 @@ private void initBootstrapDefaultValues() {
155156
if (!bootstrapProperties.containsKey(SCANNER_OS)) {
156157
setBootstrapProperty(SCANNER_OS, new OsResolver(system, new Paths2()).getOs().name().toLowerCase(Locale.ENGLISH));
157158
}
158-
setBootstrapPropertyIfNotAlreadySet(SCANNER_ARCH, system.getProperty("os.arch"));
159+
if (!bootstrapProperties.containsKey(SCANNER_ARCH)) {
160+
setBootstrapProperty(SCANNER_ARCH, new ArchResolver().getCpuArch());
161+
}
159162
}
160163

161164
private String getSonarCloudUrl() {
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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.BufferedReader;
23+
import java.io.InputStreamReader;
24+
import java.nio.charset.StandardCharsets;
25+
import java.util.Optional;
26+
import org.apache.commons.lang3.SystemUtils;
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
29+
import org.sonarsource.scanner.lib.System2;
30+
31+
public class ArchResolver {
32+
33+
private static final Logger LOG = LoggerFactory.getLogger(ArchResolver.class);
34+
35+
private final System2 system;
36+
private final ProcessWrapperFactory processWrapperFactory;
37+
private final boolean useUname;
38+
39+
public ArchResolver() {
40+
this(new System2(), new ProcessWrapperFactory(), SystemUtils.IS_OS_UNIX);
41+
}
42+
43+
ArchResolver(System2 system, ProcessWrapperFactory processWrapperFactory, boolean useUname) {
44+
this.system = system;
45+
this.processWrapperFactory = processWrapperFactory;
46+
this.useUname = useUname;
47+
}
48+
49+
/**
50+
* We don't want to only rely on the system property 'os.arch' to detect the architecture on macOS, since it returns the target architecture of
51+
* the current JVM, which may be different from the architecture of the OS. For example, a 32-bit JVM can run on a 64-bit OS.
52+
*/
53+
public String getCpuArch() {
54+
Optional<String> archFromUname = Optional.empty();
55+
if (useUname) {
56+
archFromUname = tryGetArchUsingUname();
57+
}
58+
return archFromUname.orElseGet(() -> system.getProperty("os.arch"));
59+
}
60+
61+
private Optional<String> tryGetArchUsingUname() {
62+
try {
63+
ProcessWrapperFactory.ProcessWrapper process = processWrapperFactory.create("uname", "-m");
64+
65+
String arch;
66+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
67+
arch = reader.lines().findFirst().orElseThrow(() -> new IllegalStateException("No output from 'uname -m'"));
68+
LOG.debug("uname -m returned '{}'", arch);
69+
}
70+
71+
int exit = process.waitFor();
72+
if (exit != 0) {
73+
LOG.debug("Command exited with code: {}", exit);
74+
return Optional.empty();
75+
}
76+
77+
return Optional.of(arch);
78+
} catch (Exception e) {
79+
LOG.debug("Failed to get the architecture using 'uname -m'", e);
80+
return Optional.empty();
81+
}
82+
}
83+
84+
}

lib/src/test/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapperTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ void should_set_os_and_arch() throws Exception {
200200
.bootstrap()) {
201201

202202
assertThat(scannerEngine.getBootstrapProperties()).containsEntry("sonar.scanner.os", "linux");
203-
assertThat(scannerEngine.getBootstrapProperties()).containsEntry("sonar.scanner.arch", "x64");
203+
assertThat(scannerEngine.getBootstrapProperties()).containsKey("sonar.scanner.arch");
204204
}
205205
}
206206

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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.ByteArrayInputStream;
23+
import java.io.IOException;
24+
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.api.extension.RegisterExtension;
26+
import org.slf4j.event.Level;
27+
import org.sonarsource.scanner.lib.System2;
28+
import testutils.LogTester;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.mockito.Mockito.mock;
32+
import static org.mockito.Mockito.when;
33+
34+
class ArchResolverTest {
35+
36+
@RegisterExtension
37+
private final LogTester logTester = new LogTester().setLevel(Level.DEBUG);
38+
39+
@Test
40+
void shouldUseUnameToDetectArch() throws IOException {
41+
var processWrapperFactory = mock(ProcessWrapperFactory.class);
42+
var process = mock(ProcessWrapperFactory.ProcessWrapper.class);
43+
when(process.getInputStream()).thenReturn(new ByteArrayInputStream("arm64".getBytes()));
44+
when(processWrapperFactory.create("uname", "-m")).thenReturn(process);
45+
ArchResolver archResolver = new ArchResolver(new System2(), processWrapperFactory, true);
46+
47+
String arch = archResolver.getCpuArch();
48+
49+
assertThat(arch).isEqualTo("arm64");
50+
assertThat(logTester.logs(Level.DEBUG)).contains("uname -m returned 'arm64'");
51+
}
52+
53+
@Test
54+
void shouldNotUseUnameOnWindows() {
55+
ArchResolver archResolver = new ArchResolver(new System2(), mock(ProcessWrapperFactory.class), false);
56+
57+
String arch = archResolver.getCpuArch();
58+
59+
assertThat(arch).isEqualTo(System.getProperty("os.arch"));
60+
}
61+
62+
@Test
63+
void shouldFallbackToSystemPropertiesIfExitIsNotZero() throws IOException, InterruptedException {
64+
var processWrapperFactory = mock(ProcessWrapperFactory.class);
65+
var process = mock(ProcessWrapperFactory.ProcessWrapper.class);
66+
when(process.getInputStream()).thenReturn(new ByteArrayInputStream("Error".getBytes()));
67+
when(process.waitFor()).thenReturn(-1);
68+
when(processWrapperFactory.create("uname", "-m")).thenReturn(process);
69+
ArchResolver archResolver = new ArchResolver(new System2(), processWrapperFactory, true);
70+
71+
String arch = archResolver.getCpuArch();
72+
73+
assertThat(arch).isEqualTo(System.getProperty("os.arch"));
74+
assertThat(logTester.logs(Level.DEBUG)).contains("uname -m returned 'Error'", "Command exited with code: -1");
75+
}
76+
77+
@Test
78+
void shouldFallbackToSystemPropertiesIfExceptionOnWaitFor() throws IOException, InterruptedException {
79+
var processWrapperFactory = mock(ProcessWrapperFactory.class);
80+
var process = mock(ProcessWrapperFactory.ProcessWrapper.class);
81+
when(process.getInputStream()).thenReturn(new ByteArrayInputStream("Error".getBytes()));
82+
when(process.waitFor()).thenThrow(new InterruptedException());
83+
when(processWrapperFactory.create("uname", "-m")).thenReturn(process);
84+
ArchResolver archResolver = new ArchResolver(new System2(), processWrapperFactory, true);
85+
86+
String arch = archResolver.getCpuArch();
87+
88+
assertThat(arch).isEqualTo(System.getProperty("os.arch"));
89+
assertThat(logTester.logs(Level.DEBUG)).contains("Failed to get the architecture using 'uname -m'");
90+
}
91+
92+
@Test
93+
void shouldFallbackToSystemPropertiesIfNoOutput() throws IOException {
94+
var processWrapperFactory = mock(ProcessWrapperFactory.class);
95+
var process = mock(ProcessWrapperFactory.ProcessWrapper.class);
96+
when(process.getInputStream()).thenReturn(new ByteArrayInputStream(new byte[0]));
97+
when(processWrapperFactory.create("uname", "-m")).thenReturn(process);
98+
ArchResolver archResolver = new ArchResolver(new System2(), processWrapperFactory, true);
99+
100+
String arch = archResolver.getCpuArch();
101+
102+
assertThat(arch).isEqualTo(System.getProperty("os.arch"));
103+
assertThat(logTester.logs(Level.DEBUG)).contains("Failed to get the architecture using 'uname -m'");
104+
}
105+
}

0 commit comments

Comments
 (0)