Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
Copyright 2014-2018 Appium Contributors

This product includes software adapted from Selenium (https://www.selenium.dev/),
licensed under the Apache License, Version 2.0. See individual source files under
io.appium.java_client.internal for details of which classes are adapted and from where.
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.appium.java_client.internal.process;

import org.jspecify.annotations.Nullable;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;

/**
* Locates a named executable by scanning the PATH.
* Adapted from Selenium's {@code org.openqa.selenium.os.ExecutableFinder}
* (Apache License 2.0), vendored here to drop the {@code selenium-os} dependency and
* trimmed to bare-name PATH lookup, the only way this class is actually called.
*/
public class ExecutableFinder {

private static final boolean IS_WINDOWS = osNameContains("win");
private static final boolean IS_MAC = osNameContains("mac");

private static final List<String> ENDINGS = IS_WINDOWS
? List.of("", ".cmd", ".exe", ".com", ".bat")
: singletonList("");

private static boolean osNameContains(String needle) {
return System.getProperty("os.name", "").toLowerCase(Locale.ROOT).contains(needle);
}

/**
* Finds a named executable by scanning the PATH. On Windows, common executable
* endings (".com", ".bat" and ".exe") are tried in addition to the bare name.
*
* @param named the name of the executable to find, e.g. "node"
* @return the absolute path to the executable, or {@code null} if no match is found
*/
@Nullable
public String find(String named) {
List<String> pathSegments = new ArrayList<>(fromEnvironment());
if (IS_MAC) {
pathSegments.addAll(macSpecificPathSegments());
}

for (String pathSegment : pathSegments) {
for (String ending : ENDINGS) {
File file = new File(pathSegment, named + ending);
if (canExecute(file)) {
return file.getAbsolutePath();
}
}
}
return null;
}

private List<String> fromEnvironment() {
String pathName = "PATH";
Map<String, String> env = System.getenv();
if (!env.containsKey(pathName)) {
for (String key : env.keySet()) {
if (pathName.equalsIgnoreCase(key)) {
pathName = key;
break;
}
}
}
String path = env.get(pathName);
return path != null ? List.of(path.split(File.pathSeparator)) : emptyList();
}

private List<String> macSpecificPathSegments() {
File pathFile = new File("/etc/paths");
if (pathFile.exists()) {
try {
return Files.readAllLines(pathFile.toPath());
} catch (IOException e) {
// Ignore, PATH env var segments are still used
}
}
return emptyList();
}

private static boolean canExecute(File file) {
return file.exists() && !file.isDirectory() && file.canExecute();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.google.gson.GsonBuilder;
import io.appium.java_client.android.options.context.SupportsChromedriverExecutableOption;
import io.appium.java_client.android.options.signing.SupportsKeystoreOptions;
import io.appium.java_client.internal.process.ExecutableFinder;
import io.appium.java_client.remote.MobileBrowserType;
import io.appium.java_client.remote.options.SupportsAppOption;
import io.appium.java_client.service.local.flags.GeneralServerFlag;
Expand All @@ -28,7 +29,6 @@
import org.jspecify.annotations.Nullable;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Platform;
import org.openqa.selenium.os.ExecutableFinder;
import org.openqa.selenium.remote.Browser;
import org.openqa.selenium.remote.service.DriverService;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.appium.java_client.internal.process;

import org.junit.jupiter.api.Test;

import java.util.UUID;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;

class ExecutableFinderTest {

@Test
void returnsNullWhenNothingMatches() {
String named = "definitely-not-a-real-executable-" + UUID.randomUUID();

assertNull(new ExecutableFinder().find(named));
}

@Test
void findsAnExecutableOnPath() {
// The JVM running this test was itself launched via a PATH-resolvable "java",
// so this exercises the real PATH-scanning branch without mocking the environment.
assertNotNull(new ExecutableFinder().find("java"));
}
}
Loading