diff --git a/processstarter/build.gradle b/processstarter/build.gradle new file mode 100644 index 0000000000..9400893e38 --- /dev/null +++ b/processstarter/build.gradle @@ -0,0 +1,84 @@ +import org.gradle.internal.os.OperatingSystem + +if (!project.hasProperty('onlylinuxathena')) { + + description = "Process Starter" + + apply plugin: 'cpp' + apply plugin: 'objective-cpp' + apply plugin: 'visual-studio' + apply plugin: 'edu.wpi.first.NativeUtils' + + ext { + nativeName = 'processstarter' + } + + apply from: "${rootDir}/shared/config.gradle" + + // Replace shared crt with static crt. + // Note this means no wpilib binaries can be dependencies + nativeUtils.platformConfigs.named(nativeUtils.wpi.platforms.windowsx64).configure { + cppCompiler.debugArgs.remove('/MDd') + cppCompiler.debugArgs.add('/MTd') + cppCompiler.releaseArgs.remove('/MD') + cppCompiler.releaseArgs.add('/MT') + } + + project(':').libraryBuild.dependsOn build + + model { + components { + "${nativeName}"(NativeExecutableSpec) { + baseName = 'processstarter' + binaries.all { + if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) { + it.buildable = false + return + } + if (it.targetPlatform.operatingSystem.isMacOsX()) { + it.sources { + macObjCpp(ObjectiveCppSourceSet) { + source { + srcDirs 'src/main/native/osx' + include '**/*.mm' + } + exportedHeaders { + srcDirs 'src/main/native/include' + include '**/*.h' + } + } + } + } else if (it.targetPlatform.operatingSystem.isLinux()) { + it.sources { + linuxCpp(CppSourceSet) { + source { + srcDirs 'src/main/native/linux' + include '**/*.cpp' + } + exportedHeaders { + srcDirs 'src/main/native/include' + include '**/*.h' + } + } + } + } else if (it.targetPlatform.operatingSystem.isWindows()) { + it.sources { + windowsCpp(CppSourceSet) { + source { + srcDirs 'src/main/native/windows' + include '**/*.cpp' + } + exportedHeaders { + srcDirs 'src/main/native/include' + include '**/*.h' + } + } + } + } + } + } + } + } + + apply from: 'publish.gradle' +} diff --git a/processstarter/publish.gradle b/processstarter/publish.gradle new file mode 100644 index 0000000000..be976d26ef --- /dev/null +++ b/processstarter/publish.gradle @@ -0,0 +1,67 @@ +apply plugin: 'maven-publish' + +def baseArtifactId = 'processstarter' +def artifactGroupId = 'edu.wpi.first.tools' +def zipBaseName = '_GROUP_edu_wpi_first_tools_ID_processstarter_CLS' + +def outputsFolder = file("$project.buildDir/outputs") + +model { + tasks { + // Create the run task. + $.components.processstarter.binaries.each { bin -> + if (bin.buildable && bin.name.toLowerCase().contains("debug") && nativeUtils.isNativeDesktopPlatform(bin.targetPlatform)) { + Task run = project.tasks.create("run", Exec) { + commandLine bin.tasks.install.runScriptFile.get().asFile.toString() + } + run.dependsOn bin.tasks.install + } + } + } + publishing { + def processstarterTaskList = [] + $.components.each { component -> + component.binaries.each { binary -> + if (binary in NativeExecutableBinarySpec && binary.component.name.contains("processstarter")) { + if (binary.buildable && (binary.name.contains('Release') || binary.name.contains('release'))) { + // We are now in the binary that we want. + // This is the default application path for the ZIP task. + def applicationPath = binary.executable.file + + // Create the ZIP. + def task = project.tasks.create("copyprocessstarterExecutable" + binary.targetPlatform.architecture.name, Zip) { + description("Copies the processstarter executable to the outputs directory.") + destinationDirectory = outputsFolder + + archiveBaseName = '_M_' + zipBaseName + duplicatesStrategy = 'exclude' + classifier = nativeUtils.getPublishClassifier(binary) + + from(licenseFile) { + into '/' + } + + from(applicationPath) + } + + task.dependsOn binary.tasks.link + processstarterTaskList.add(task) + project.build.dependsOn task + project.artifacts { task } + addTaskToCopyAllOutputs(task) + } + } + } + } + + publications { + processstarter(MavenPublication) { + processstarterTaskList.each { artifact it } + + artifactId = baseArtifactId + groupId = artifactGroupId + version wpilibVersioning.version.get() + } + } + } +} diff --git a/processstarter/src/main/native/linux/main.cpp b/processstarter/src/main/native/linux/main.cpp new file mode 100644 index 0000000000..255be937eb --- /dev/null +++ b/processstarter/src/main/native/linux/main.cpp @@ -0,0 +1,86 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +int main(int argc, char* argv[]) { + char path[PATH_MAX]; + char dest[PATH_MAX]; + std::memset(dest, 0, sizeof(dest)); // readlink does not null terminate! + pid_t pid = getpid(); + std::snprintf(path, PATH_MAX, "/proc/%d/exe", pid); + int readlink_len = readlink(path, dest, PATH_MAX); + if (readlink_len < 0) { + std::perror("readlink"); + return 1; + } else if (readlink_len == PATH_MAX) { + std::printf("Truncation occured\n"); + return 1; + } + + std::filesystem::path exePath{dest}; + if (exePath.empty()) { + return 1; + } + + if (!exePath.has_stem()) { + return 1; + } + + if (!exePath.has_parent_path()) { + return 1; + } + + std::filesystem::path jarPath{exePath}; + jarPath.replace_extension("jar"); + std::filesystem::path parentPath{exePath.parent_path()}; + + if (!parentPath.has_parent_path()) { + return 1; + } + std::filesystem::path toolsFolder{parentPath.parent_path()}; + + std::filesystem::path Java = toolsFolder / "jdk" / "bin" / "java"; + + pid = 0; + std::string data = jarPath; + std::string jarArg = "-jar"; + char* const arguments[] = {jarArg.data(), data.data(), nullptr}; + + int status = + posix_spawn(&pid, Java.c_str(), nullptr, nullptr, arguments, environ); + if (status != 0) { + char* home = std::getenv("JAVA_HOME"); + std::string javaLocal = "java"; + if (home != nullptr) { + std::filesystem::path javaHomePath{home}; + javaHomePath /= "bin"; + javaHomePath /= "java"; + javaLocal = javaHomePath; + } + + status = posix_spawn(&pid, javaLocal.c_str(), nullptr, nullptr, arguments, + environ); + if (status != 0) { + return 1; + } + } + + int childPid = syscall(SYS_pidfd_open, pid, 0); + if (childPid <= 0) { + return 1; + } + + struct pollfd pfd = {childPid, POLLIN, 0}; + return poll(&pfd, 1, 3000); +} diff --git a/processstarter/src/main/native/osx/main.mm b/processstarter/src/main/native/osx/main.mm new file mode 100644 index 0000000000..57b6678464 --- /dev/null +++ b/processstarter/src/main/native/osx/main.mm @@ -0,0 +1,80 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#import +#include +#include + +int main(int argc, char* argv[]) { + (void)argc; + (void)argv; + NSString* exePathPlat = [[NSBundle mainBundle] bundlePath]; + NSString* identifier = [[NSBundle mainBundle] bundleIdentifier]; + if (identifier == nil) { + exePathPlat = [[NSBundle mainBundle] executablePath]; + } + + std::filesystem::path exePath{[exePathPlat UTF8String]}; + if (exePath.empty()) { + return 1; + } + + if (!exePath.has_stem()) { + return 1; + } + + if (!exePath.has_parent_path()) { + return 1; + } + + std::filesystem::path jarPath{exePath}; + jarPath.replace_extension("jar"); + std::filesystem::path parentPath{exePath.parent_path()}; + + if (!parentPath.has_parent_path()) { + return 1; + } + std::filesystem::path toolsFolder{parentPath.parent_path()}; + + std::filesystem::path java = toolsFolder / "jdk" / "bin" / "java"; + + NSArray* Arguments = + @[ @"-jar", [NSString stringWithFormat:@"%s", jarPath.c_str()] ]; + + NSTask* task = [[NSTask alloc] init]; + task.launchPath = [NSString stringWithFormat:@"%s", java.c_str()]; + task.arguments = Arguments; + task.terminationHandler = ^(NSTask* t) { + (void)t; + CFRunLoopStop(CFRunLoopGetMain()); + }; + + if (![task launchAndReturnError:nil]) { + task.terminationHandler = nil; + + NSString* javaHome = + [[[NSProcessInfo processInfo] environment] objectForKey:@"JAVA_HOME"]; + task = [[NSTask alloc] init]; + task.launchPath = @"java"; + if (javaHome != nil) { + std::filesystem::path javaHomePath{[javaHome UTF8String]}; + javaHomePath /= "bin"; + javaHomePath /= "java"; + task.launchPath = [NSString stringWithFormat:@"%s", javaHomePath.c_str()]; + } + task.arguments = Arguments; + task.terminationHandler = ^(NSTask* t) { + (void)t; + CFRunLoopStop(CFRunLoopGetMain()); + }; + + if (![task launchAndReturnError:nil]) { + return 1; + } + } + + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 3, false); + + return task.running ? 0 : 1; +} diff --git a/processstarter/src/main/native/windows/main.cpp b/processstarter/src/main/native/windows/main.cpp new file mode 100644 index 0000000000..8b22aef0d8 --- /dev/null +++ b/processstarter/src/main/native/windows/main.cpp @@ -0,0 +1,85 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include + +#include "Windows.h" + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPTSTR pCmdLine, int nCmdShow) { + DWORD Status; + WCHAR ExePathRaw[1024]; + Status = GetModuleFileNameW(NULL, ExePathRaw, 1024); + if (Status == 0) { + DWORD LastError = GetLastError(); + return LastError; + } + + std::filesystem::path ExePath{ExePathRaw}; + if (ExePath.empty()) { + return 1; + } + + if (!ExePath.has_stem()) { + return 1; + } + + if (!ExePath.has_parent_path()) { + return 1; + } + + std::filesystem::path JarPath{ExePath}; + JarPath.replace_extension(L"jar"); + std::filesystem::path ParentPath{ExePath.parent_path()}; + + if (!ParentPath.has_parent_path()) { + return 1; + } + std::filesystem::path ToolsFolder{ParentPath.parent_path()}; + + std::filesystem::path Javaw = ToolsFolder / L"jdk" / L"bin" / L"javaw.exe"; + + std::wstring ToRun = L" -jar \""; + ToRun += JarPath; + ToRun += L"\""; + + STARTUPINFOW StartupInfo; + PROCESS_INFORMATION ProcessInfo; + + ZeroMemory(&StartupInfo, sizeof(StartupInfo)); + StartupInfo.cb = sizeof(StartupInfo); + ZeroMemory(&ProcessInfo, sizeof(ProcessInfo)); + + if (!CreateProcessW(Javaw.c_str(), ToRun.data(), NULL, NULL, FALSE, 0, NULL, + NULL, &StartupInfo, &ProcessInfo)) { + ZeroMemory(&StartupInfo, sizeof(StartupInfo)); + StartupInfo.cb = sizeof(StartupInfo); + ZeroMemory(&ProcessInfo, sizeof(ProcessInfo)); + + ToRun = L" -jar \""; + ToRun += JarPath; + ToRun += L"\""; + + Status = GetEnvironmentVariableW(L"JAVA_HOME", ExePathRaw, 1024); + std::wstring JavawLocal = L"javaw"; + if (Status != 0 && Status < 1024) { + std::filesystem::path JavaHomePath{ExePathRaw}; + JavaHomePath /= "bin"; + JavaHomePath /= "javaw.exe"; + JavawLocal = JavaHomePath; + } + + if (!CreateProcessW(JavawLocal.c_str(), ToRun.data(), NULL, NULL, FALSE, 0, + NULL, NULL, &StartupInfo, &ProcessInfo)) { + return 1; + } + } + + Status = + WaitForSingleObject(ProcessInfo.hProcess, 3000); // Wait for 3 seconds + CloseHandle(ProcessInfo.hProcess); + CloseHandle(ProcessInfo.hThread); + + return Status == WAIT_TIMEOUT ? 0 : 1; +} diff --git a/settings.gradle b/settings.gradle index 818046b2d9..03e06fde3d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -52,6 +52,7 @@ include 'docs' include 'msvcruntime' include 'ntcoreffi' include 'apriltag' +include 'processstarter' buildCache { def cred = {