-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[Java] Add PlatformDetector for runtime classifier selection
#2173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
edburns
merged 3 commits into
edburns/1917-java-embed-rust-cli-runtime-dd-3039924-agentic-run-02
from
copilot/edburns1917-java-embed-rust-cli-runtime-dd-3039924
Jul 30, 2026
Merged
Changes from 2 commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
298 changes: 298 additions & 0 deletions
298
java/sdk/src/main/java/com/github/copilot/ffi/PlatformDetector.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,298 @@ | ||
| /*--------------------------------------------------------------------------------------------- | ||
| * Copyright (c) Microsoft Corporation. All rights reserved. | ||
| *--------------------------------------------------------------------------------------------*/ | ||
|
|
||
| package com.github.copilot.ffi; | ||
|
|
||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.util.Locale; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
|
|
||
| /** | ||
| * Detects the current platform and resolves the runtime classifier. | ||
| */ | ||
| public final class PlatformDetector { | ||
| private static final int ELF_HEADER_PROBE_BYTES = 2048; | ||
| private static final int ELF_MAGIC_0 = 0x7F; | ||
| private static final int ELF_MAGIC_1 = 'E'; | ||
| private static final int ELF_MAGIC_2 = 'L'; | ||
| private static final int ELF_MAGIC_3 = 'F'; | ||
| private static final int ELF_CLASS_32 = 1; | ||
| private static final int ELF_CLASS_64 = 2; | ||
| private static final int ELF_DATA_LITTLE_ENDIAN = 1; | ||
| private static final int ELF_DATA_BIG_ENDIAN = 2; | ||
| private static final int PT_INTERP = 3; | ||
|
|
||
| private static final Set<String> SUPPORTED_CLASSIFIERS = Set.of("linux-x64", "linux-arm64", "linuxmusl-x64", | ||
| "linuxmusl-arm64", "darwin-x64", "darwin-arm64", "win32-x64", "win32-arm64"); | ||
|
|
||
| private static final Map<ClassifierKey, String> CLASSIFIER_BY_KEY = Map.ofEntries( | ||
| Map.entry(new ClassifierKey("linux", "x64", LinuxLibc.GLIBC), "linux-x64"), | ||
| Map.entry(new ClassifierKey("linux", "arm64", LinuxLibc.GLIBC), "linux-arm64"), | ||
| Map.entry(new ClassifierKey("linux", "x64", LinuxLibc.MUSL), "linuxmusl-x64"), | ||
| Map.entry(new ClassifierKey("linux", "arm64", LinuxLibc.MUSL), "linuxmusl-arm64"), | ||
| Map.entry(new ClassifierKey("linux", "x64", LinuxLibc.UNKNOWN), "linux-x64"), | ||
| Map.entry(new ClassifierKey("linux", "arm64", LinuxLibc.UNKNOWN), "linux-arm64"), | ||
| Map.entry(new ClassifierKey("darwin", "x64", LinuxLibc.UNKNOWN), "darwin-x64"), | ||
| Map.entry(new ClassifierKey("darwin", "arm64", LinuxLibc.UNKNOWN), "darwin-arm64"), | ||
| Map.entry(new ClassifierKey("win32", "x64", LinuxLibc.UNKNOWN), "win32-x64"), | ||
| Map.entry(new ClassifierKey("win32", "arm64", LinuxLibc.UNKNOWN), "win32-arm64")); | ||
|
|
||
| private PlatformDetector() { | ||
| } | ||
|
|
||
| /** | ||
| * Linux C runtime classification. | ||
| */ | ||
| public enum LinuxLibc { | ||
| /** GNU libc runtime. */ | ||
| GLIBC, | ||
|
|
||
| /** musl libc runtime. */ | ||
| MUSL, | ||
|
|
||
| /** Unknown or undetectable runtime. */ | ||
| UNKNOWN | ||
| } | ||
|
|
||
| /** | ||
| * Detects the normalized operating system identifier. | ||
| * | ||
| * @return {@code darwin}, {@code linux}, or {@code win32} | ||
| */ | ||
| public static String detectOs() { | ||
| return detectOs(System.getProperty("os.name", "")); | ||
| } | ||
|
|
||
| /** | ||
| * Detects the normalized architecture identifier. | ||
| * | ||
| * @return {@code x64} or {@code arm64} | ||
| */ | ||
| public static String detectArch() { | ||
| return detectArch(System.getProperty("os.arch", "")); | ||
| } | ||
|
|
||
| /** | ||
| * Detects the Linux libc variant using {@code /proc/self/exe} PT_INTERP. | ||
| * | ||
| * @return Linux libc classification; {@code UNKNOWN} on non-Linux or parse | ||
| * failures | ||
| */ | ||
| public static LinuxLibc detectLinuxLibc() { | ||
| if (!"linux".equals(detectOs())) { | ||
| return LinuxLibc.UNKNOWN; | ||
| } | ||
| return detectLinuxLibc(Path.of("/proc/self/exe")); | ||
| } | ||
|
|
||
| /** | ||
| * Detects the runtime classifier for the current platform. | ||
| * | ||
| * @return platform classifier string | ||
| */ | ||
| public static String detectClassifier() { | ||
| return detectClassifier(detectOs(), detectArch(), detectLinuxLibc()); | ||
| } | ||
|
|
||
| static String detectOs(String osName) { | ||
| String normalized = osName.toLowerCase(Locale.ROOT); | ||
| if (normalized.contains("mac") || normalized.contains("darwin")) { | ||
| return "darwin"; | ||
| } | ||
| if (normalized.contains("win")) { | ||
| return "win32"; | ||
| } | ||
| if (normalized.contains("linux")) { | ||
| return "linux"; | ||
| } | ||
| throw new IllegalStateException("Unsupported os.name: " + osName); | ||
| } | ||
|
|
||
| static String detectArch(String osArch) { | ||
| String normalized = osArch.toLowerCase(Locale.ROOT).replace('-', '_'); | ||
| if (normalized.equals("amd64") || normalized.equals("x86_64") || normalized.equals("x64")) { | ||
| return "x64"; | ||
| } | ||
| if (normalized.equals("aarch64") || normalized.equals("arm64")) { | ||
| return "arm64"; | ||
| } | ||
| throw new IllegalStateException("Unsupported os.arch: " + osArch); | ||
| } | ||
|
|
||
| static LinuxLibc detectLinuxLibc(Path executablePath) { | ||
| try { | ||
| return detectLinuxLibc(readPrefix(executablePath, ELF_HEADER_PROBE_BYTES)); | ||
| } catch (IOException ex) { | ||
| return LinuxLibc.UNKNOWN; | ||
| } | ||
| } | ||
|
|
||
| static LinuxLibc detectLinuxLibc(byte[] elfPrefix) throws IOException { | ||
| String interpreter = readElfPtInterp(elfPrefix); | ||
| if (interpreter.contains("/ld-musl-")) { | ||
| return LinuxLibc.MUSL; | ||
| } | ||
| if (interpreter.contains("/ld-linux-")) { | ||
| return LinuxLibc.GLIBC; | ||
| } | ||
| return LinuxLibc.UNKNOWN; | ||
| } | ||
|
|
||
| static String detectClassifier(String os, String arch, LinuxLibc linuxLibc) { | ||
| LinuxLibc classifierLibc = "linux".equals(os) ? linuxLibc : LinuxLibc.UNKNOWN; | ||
| String classifier = CLASSIFIER_BY_KEY.get(new ClassifierKey(os, arch, classifierLibc)); | ||
| if (classifier == null || !SUPPORTED_CLASSIFIERS.contains(classifier)) { | ||
| throw new IllegalStateException( | ||
| "Unsupported platform tuple: os=" + os + ", arch=" + arch + ", libc=" + classifierLibc); | ||
| } | ||
| return classifier; | ||
| } | ||
|
|
||
| static Set<String> supportedClassifiers() { | ||
| return SUPPORTED_CLASSIFIERS; | ||
| } | ||
|
|
||
| private static String readElfPtInterp(byte[] probe) throws IOException { | ||
| int size = probe.length; | ||
| if (size < 64) { | ||
| throw new IOException("ELF probe too small: " + size + " bytes"); | ||
| } | ||
| if ((probe[0] & 0xFF) != ELF_MAGIC_0 || (probe[1] & 0xFF) != ELF_MAGIC_1 || (probe[2] & 0xFF) != ELF_MAGIC_2 | ||
| || (probe[3] & 0xFF) != ELF_MAGIC_3) { | ||
| throw new IOException("Not an ELF executable"); | ||
| } | ||
|
|
||
| int elfClass = probe[4] & 0xFF; | ||
| int elfData = probe[5] & 0xFF; | ||
| if (elfData != ELF_DATA_LITTLE_ENDIAN && elfData != ELF_DATA_BIG_ENDIAN) { | ||
| throw new IOException("Unsupported ELF data encoding: " + elfData); | ||
| } | ||
| boolean littleEndian = elfData == ELF_DATA_LITTLE_ENDIAN; | ||
|
|
||
| long phoff; | ||
| int phentsize; | ||
| int phnum; | ||
| if (elfClass == ELF_CLASS_64) { | ||
| phoff = readUInt64(probe, 32, littleEndian); | ||
| phentsize = readUInt16(probe, 54, littleEndian); | ||
| phnum = readUInt16(probe, 56, littleEndian); | ||
| } else if (elfClass == ELF_CLASS_32) { | ||
| phoff = readUInt32(probe, 28, littleEndian); | ||
| phentsize = readUInt16(probe, 42, littleEndian); | ||
| phnum = readUInt16(probe, 44, littleEndian); | ||
| } else { | ||
| throw new IOException("Unsupported ELF class: " + elfClass); | ||
| } | ||
|
|
||
| if (phoff < 0 || phoff >= size) { | ||
| throw new IOException("Program header table offset outside probe window: " + phoff); | ||
| } | ||
| if (phentsize <= 0 || phnum <= 0) { | ||
| throw new IOException("Invalid ELF program header metadata: phentsize=" + phentsize + ", phnum=" + phnum); | ||
| } | ||
|
|
||
| for (int i = 0; i < phnum; i++) { | ||
| long baseLong = phoff + ((long) i * phentsize); | ||
| if (baseLong < 0 || baseLong > Integer.MAX_VALUE) { | ||
| break; | ||
| } | ||
| int base = (int) baseLong; | ||
| if (base + phentsize > size) { | ||
| break; | ||
| } | ||
|
|
||
| long pType = readUInt32(probe, base, littleEndian); | ||
| if (pType != PT_INTERP) { | ||
| continue; | ||
| } | ||
|
|
||
| long pOffset; | ||
| long pFileSize; | ||
| if (elfClass == ELF_CLASS_64) { | ||
| pOffset = readUInt64(probe, base + 8, littleEndian); | ||
| pFileSize = readUInt64(probe, base + 32, littleEndian); | ||
| } else { | ||
| pOffset = readUInt32(probe, base + 4, littleEndian); | ||
| pFileSize = readUInt32(probe, base + 16, littleEndian); | ||
| } | ||
|
|
||
| if (pOffset < 0 || pFileSize <= 0 || pOffset > Integer.MAX_VALUE || pFileSize > Integer.MAX_VALUE) { | ||
| throw new IOException("Invalid PT_INTERP bounds"); | ||
| } | ||
|
|
||
| int start = (int) pOffset; | ||
| int end = start + (int) pFileSize; | ||
| if (end > size) { | ||
| throw new IOException("PT_INTERP extends past probe window; increase probe size"); | ||
| } | ||
|
|
||
| int nulIndex = start; | ||
| while (nulIndex < end && probe[nulIndex] != 0) { | ||
| nulIndex++; | ||
| } | ||
| if (nulIndex == start) { | ||
| throw new IOException("Empty PT_INTERP segment"); | ||
| } | ||
| return new String(probe, start, nulIndex - start, StandardCharsets.UTF_8); | ||
| } | ||
|
|
||
| throw new IOException("ELF PT_INTERP segment not found"); | ||
| } | ||
|
|
||
| private static byte[] readPrefix(Path path, int maxBytes) throws IOException { | ||
| byte[] buffer = new byte[maxBytes]; | ||
| int total = 0; | ||
| try (InputStream in = Files.newInputStream(path)) { | ||
| while (total < maxBytes) { | ||
| int read = in.read(buffer, total, maxBytes - total); | ||
| if (read < 0) { | ||
| break; | ||
| } | ||
| total += read; | ||
| } | ||
| } | ||
| byte[] resized = new byte[total]; | ||
| System.arraycopy(buffer, 0, resized, 0, total); | ||
| return resized; | ||
| } | ||
|
|
||
| private static int readUInt16(byte[] data, int offset, boolean littleEndian) { | ||
| int b0 = data[offset] & 0xFF; | ||
| int b1 = data[offset + 1] & 0xFF; | ||
| return littleEndian ? (b0 | (b1 << 8)) : ((b0 << 8) | b1); | ||
| } | ||
|
|
||
| private static long readUInt32(byte[] data, int offset, boolean littleEndian) { | ||
| long b0 = data[offset] & 0xFFL; | ||
| long b1 = data[offset + 1] & 0xFFL; | ||
| long b2 = data[offset + 2] & 0xFFL; | ||
| long b3 = data[offset + 3] & 0xFFL; | ||
| if (littleEndian) { | ||
| return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); | ||
| } | ||
| return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; | ||
| } | ||
|
|
||
| private static long readUInt64(byte[] data, int offset, boolean littleEndian) { | ||
| long result = 0L; | ||
| if (littleEndian) { | ||
| for (int i = 7; i >= 0; i--) { | ||
| result = (result << 8) | (data[offset + i] & 0xFFL); | ||
| } | ||
| return result; | ||
| } | ||
| for (int i = 0; i < 8; i++) { | ||
| result = (result << 8) | (data[offset + i] & 0xFFL); | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| private record ClassifierKey(String os, String arch, LinuxLibc libc) { | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.