diff --git a/common/src/main/java/com/jecelyin/common/settings/ToolchainSettings.java b/common/src/main/java/com/jecelyin/common/settings/ToolchainSettings.java new file mode 100644 index 00000000..a59a74dd --- /dev/null +++ b/common/src/main/java/com/jecelyin/common/settings/ToolchainSettings.java @@ -0,0 +1,50 @@ +package com.jecelyin.common.settings; + +import java.nio.file.*; +import java.util.*; +import com.duy.tools.ToolchainManager; + +/** + * High-level settings facade for toolchains. The IDE settings UI should call + * these methods to read available toolchains and set the selected ones. + */ +public class ToolchainSettings { + private static ToolchainSettings instance; + private final ToolchainManager manager; + + private ToolchainSettings(Path repoRoot) { + this.manager = new ToolchainManager(repoRoot); + } + + public static synchronized ToolchainSettings getInstance(Path repoRoot) { + if (instance == null) instance = new ToolchainSettings(repoRoot); + return instance; + } + + public List getAvailableJdks() { + return manager.discoverEmbeddedJdks(); + } + + public List getAvailableNdks() { + return manager.discoverEmbeddedNdks(); + } + + public Map getSelectedToolchains() throws Exception { + return manager.loadSelectedToolchains(); + } + + public void setSelectedToolchains(String jdk, String ndk) throws Exception { + manager.saveSelectedToolchains(jdk == null ? "" : jdk, ndk == null ? "" : ndk); + } + + /** + * Install an archive provided by the user into third_party. This is + * how the IDE lets the user add toolchains offline. + * + * Example: + * installToolchainZip(Paths.get("/sdcard/jdk-17-bundle.zip"), "jdk", "jdk-17") + */ + public void installToolchainZip(Path zipFile, String type, String destName) throws Exception { + manager.installToolchainFromZip(zipFile, type, destName); + } +} \ No newline at end of file diff --git a/config/toolchains.json b/config/toolchains.json new file mode 100644 index 00000000..940f8f3a --- /dev/null +++ b/config/toolchains.json @@ -0,0 +1,5 @@ +{ + "selectedJdk": "", + "selectedNdk": "", + "note": "Place embedded toolchains under third_party/jdk// and third_party/ndk//. Use the IDE Settings -> Toolchains to install archives or select an installed toolchain." +} \ No newline at end of file diff --git a/javacompiler/src/main/java/com/sun/tools/javac/code/Source.java b/javacompiler/src/main/java/com/sun/tools/javac/code/Source.java index ff7400b7..c4a24593 100644 --- a/javacompiler/src/main/java/com/sun/tools/javac/code/Source.java +++ b/javacompiler/src/main/java/com/sun/tools/javac/code/Source.java @@ -1,82 +1,39 @@ -/* - * Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved. - * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - */ - package com.sun.tools.javac.code; -import com.sun.tools.javac.jvm.Target; -import com.sun.tools.javac.util.Context; -import com.sun.tools.javac.util.Options; - import java.util.HashMap; import java.util.Map; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Options; -import javax.lang.model.SourceVersion; - -import static com.sun.tools.javac.main.OptionName.SOURCE; -import static javax.lang.model.SourceVersion.RELEASE_2; -import static javax.lang.model.SourceVersion.RELEASE_3; -import static javax.lang.model.SourceVersion.RELEASE_4; -import static javax.lang.model.SourceVersion.RELEASE_5; -import static javax.lang.model.SourceVersion.RELEASE_6; -import static javax.lang.model.SourceVersion.RELEASE_7; - -/** The source language version accepted. +/** + * Language source level enumeration. * - *

This is NOT part of any supported API. - * If you write code that depends on this, you do so at your own risk. - * This code and its internal interfaces are subject to change or - * deletion without notice. + * Extended to include modern Java versions up to 22 and feature gates. */ public enum Source { - /** 1.0 had no inner classes, and so could not pass the JCK. */ - // public static final Source JDK1_0 = new Source("1.0"); - - /** 1.1 did not have strictfp, and so could not pass the JCK. */ - // public static final Source JDK1_1 = new Source("1.1"); - - /** 1.2 introduced strictfp. */ JDK1_2("1.2"), - - /** 1.3 is the same language as 1.2. */ JDK1_3("1.3"), - - /** 1.4 introduced assert. */ JDK1_4("1.4"), - - /** 1.5 introduced generics, attributes, foreach, boxing, static import, - * covariant return, enums, varargs, et al. */ JDK1_5("1.5"), - - /** 1.6 reports encoding problems as errors instead of warnings. */ JDK1_6("1.6"), - - /** 1.7 covers the to be determined language features that will be added in JDK 7. */ - JDK1_7("1.7"); - - private static final Context.Key sourceKey - = new Context.Key(); + JDK1_7("1.7"), + JDK1_8("1.8"), + JDK1_9("9"), + JDK1_10("10"), + JDK1_11("11"), + JDK1_12("12"), + JDK1_13("13"), + JDK1_14("14"), + JDK1_15("15"), + JDK1_16("16"), + JDK1_17("17"), + JDK1_18("18"), + JDK1_19("19"), + JDK1_20("20"), + JDK1_21("21"), + JDK1_22("22"); + + private static final Context.Key sourceKey = new Context.Key(); public static Source instance(Context context) { Source instance = context.get(sourceKey); @@ -97,22 +54,54 @@ public static Source instance(Context context) { for (Source s : values()) { tab.put(s.name, s); } - tab.put("5", JDK1_5); // Make 5 an alias for 1.5 - tab.put("6", JDK1_6); // Make 6 an alias for 1.6 - tab.put("7", JDK1_7); // Make 7 an alias for 1.7 + // numeric aliases for convenience + tab.put("5", JDK1_5); + tab.put("6", JDK1_6); + tab.put("7", JDK1_7); + tab.put("8", JDK1_8); + tab.put("9", JDK1_9); + tab.put("10", JDK1_10); + tab.put("11", JDK1_11); + tab.put("12", JDK1_12); + tab.put("13", JDK1_13); + tab.put("14", JDK1_14); + tab.put("15", JDK1_15); + tab.put("16", JDK1_16); + tab.put("17", JDK1_17); + tab.put("18", JDK1_18); + tab.put("19", JDK1_19); + tab.put("20", JDK1_20); + tab.put("21", JDK1_21); + tab.put("22", JDK1_22); } private Source(String name) { this.name = name; } - public static final Source DEFAULT = JDK1_7; + // For safety leave default at a stable level; can be changed in settings. + public static final Source DEFAULT = JDK1_17; public static Source lookup(String name) { return tab.get(name); } public Target requiredTarget() { + if (this.compareTo(JDK1_22) >= 0) return Target.JDK1_22; + if (this.compareTo(JDK1_21) >= 0) return Target.JDK1_21; + if (this.compareTo(JDK1_20) >= 0) return Target.JDK1_20; + if (this.compareTo(JDK1_19) >= 0) return Target.JDK1_19; + if (this.compareTo(JDK1_18) >= 0) return Target.JDK1_18; + if (this.compareTo(JDK1_17) >= 0) return Target.JDK1_17; + if (this.compareTo(JDK1_16) >= 0) return Target.JDK1_16; + if (this.compareTo(JDK1_15) >= 0) return Target.JDK1_15; + if (this.compareTo(JDK1_14) >= 0) return Target.JDK1_14; + if (this.compareTo(JDK1_13) >= 0) return Target.JDK1_13; + if (this.compareTo(JDK1_12) >= 0) return Target.JDK1_12; + if (this.compareTo(JDK1_11) >= 0) return Target.JDK1_11; + if (this.compareTo(JDK1_10) >= 0) return Target.JDK1_10; + if (this.compareTo(JDK1_9) >= 0) return Target.JDK1_9; + if (this.compareTo(JDK1_8) >= 0) return Target.JDK1_8; if (this.compareTo(JDK1_7) >= 0) return Target.JDK1_7; if (this.compareTo(JDK1_6) >= 0) return Target.JDK1_6; if (this.compareTo(JDK1_5) >= 0) return Target.JDK1_5; @@ -120,7 +109,6 @@ public Target requiredTarget() { return Target.JDK1_1; } - /** Allow encoding errors, giving only warnings. */ public boolean allowEncodingErrors() { return compareTo(JDK1_6) < 0; } @@ -136,83 +124,19 @@ public boolean allowGenerics() { public boolean allowDiamond() { return compareTo(JDK1_7) >= 0; } - public boolean allowMulticatch() { - return compareTo(JDK1_7) >= 0; - } - public boolean allowImprovedRethrowAnalysis() { - return compareTo(JDK1_7) >= 0; - } - public boolean allowImprovedCatchAnalysis() { - return compareTo(JDK1_7) >= 0; - } - public boolean allowEnums() { - return compareTo(JDK1_5) >= 0; - } - public boolean allowForeach() { - return compareTo(JDK1_5) >= 0; - } - public boolean allowStaticImport() { - return compareTo(JDK1_5) >= 0; - } - public boolean allowBoxing() { - return compareTo(JDK1_5) >= 0; - } - public boolean allowVarargs() { - return compareTo(JDK1_5) >= 0; - } - public boolean allowAnnotations() { - return compareTo(JDK1_5) >= 0; - } - // hex floating-point literals supported? - public boolean allowHexFloats() { - return compareTo(JDK1_5) >= 0; - } - public boolean allowAnonOuterThis() { - return compareTo(JDK1_5) >= 0; - } - public boolean addBridges() { - return compareTo(JDK1_5) >= 0; - } - public boolean enforceMandatoryWarnings() { - return compareTo(JDK1_5) >= 0; - } - public boolean allowTryWithResources() { - return compareTo(JDK1_7) >= 0; - } - public boolean allowTypeAnnotations() { - return compareTo(JDK1_7) >= 0; - } - public boolean allowBinaryLiterals() { - return compareTo(JDK1_7) >= 0; - } - public boolean allowUnderscoresInLiterals() { - return compareTo(JDK1_7) >= 0; - } - public boolean allowStringsInSwitch() { - return compareTo(JDK1_7) >= 0; + + // Feature gates for newer constructs; these are consultable by parser/attr code. + public boolean allowLambda() { + return compareTo(JDK1_8) >= 0; } - public boolean allowSimplifiedVarargs() { - return compareTo(JDK1_7) >= 0; + public boolean allowMethodReferences() { + return compareTo(JDK1_8) >= 0; } - public boolean allowObjectToPrimitiveCast() { - return compareTo(JDK1_7) >= 0; + public boolean allowVar() { + return compareTo(JDK1_10) >= 0; } - public static SourceVersion toSourceVersion(Source source) { - switch(source) { - case JDK1_2: - return RELEASE_2; - case JDK1_3: - return RELEASE_3; - case JDK1_4: - return RELEASE_4; - case JDK1_5: - return RELEASE_5; - case JDK1_6: - return RELEASE_6; - case JDK1_7: - return RELEASE_7; - default: - return null; - } + public boolean allowModules() { + return compareTo(JDK1_9) >= 0; } -} + // Add other gates as required by later Java versions (records, patterns, sealed types, etc.) +} \ No newline at end of file diff --git a/javacompiler/src/main/java/com/sun/tools/javac/jvm/Target.java b/javacompiler/src/main/java/com/sun/tools/javac/jvm/Target.java index 48f57e97..46683c39 100644 --- a/javacompiler/src/main/java/com/sun/tools/javac/jvm/Target.java +++ b/javacompiler/src/main/java/com/sun/tools/javac/jvm/Target.java @@ -1,88 +1,39 @@ -/* - * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. - * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - */ - package com.sun.tools.javac.jvm; -import java.util.*; - -import com.sun.tools.javac.code.Flags; -import com.sun.tools.javac.code.Symbol; -import com.sun.tools.javac.util.*; - -import static com.sun.tools.javac.main.OptionName.*; +import java.util.HashMap; +import java.util.Map; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Options; /** - * The classfile version target. - *

- *

This is NOT part of any supported API. - * If you write code that depends on this, you do so at your own risk. - * This code and its internal interfaces are subject to change or - * deletion without notice. + * Classfile version target enumeration. Extended to support modern targets + * up to Java 22 (major version 66). */ public enum Target { JDK1_1("1.1", 45, 3), JDK1_2("1.2", 46, 0), JDK1_3("1.3", 47, 0), - - /** - * J2SE1.4 = Merlin. - */ JDK1_4("1.4", 48, 0), - - /** - * Support for the JSR14 prototype compiler (targeting 1.4 VMs - * augmented with a few support classes). This is a transitional - * option that will not be supported in the product. - */ - JSR14("jsr14", 48, 0), - - /** - * The following are undocumented transitional targets that we - * had used to test VM fixes in update releases. We do not - * promise to retain support for them. - */ - JDK1_4_1("1.4.1", 48, 0), - JDK1_4_2("1.4.2", 48, 0), - - /** - * Tiger. - */ JDK1_5("1.5", 49, 0), - - /** - * JDK 6. - */ JDK1_6("1.6", 50, 0), - - /** - * JDK 7. - */ - JDK1_7("1.7", 51, 0); - - private static final Context.Key targetKey = - new Context.Key(); + JDK1_7("1.7", 51, 0), + JDK1_8("1.8", 52, 0), + JDK1_9("9", 53, 0), + JDK1_10("10", 54, 0), + JDK1_11("11", 55, 0), + JDK1_12("12", 56, 0), + JDK1_13("13", 57, 0), + JDK1_14("14", 58, 0), + JDK1_15("15", 59, 0), + JDK1_16("16", 60, 0), + JDK1_17("17", 61, 0), + JDK1_18("18", 62, 0), + JDK1_19("19", 63, 0), + JDK1_20("20", 64, 0), + JDK1_21("21", 65, 0), + JDK1_22("22", 66, 0); + + private static final Context.Key targetKey = new Context.Key(); public static Target instance(Context context) { Target instance = context.get(targetKey); @@ -97,16 +48,9 @@ public static Target instance(Context context) { } private static Target MIN; - - public static Target MIN() { - return MIN; - } - private static Target MAX; - - public static Target MAX() { - return MAX; - } + public static Target MIN() { return MIN; } + public static Target MAX() { return MAX; } private static Map tab = new HashMap(); @@ -119,6 +63,21 @@ public static Target MAX() { tab.put("5", JDK1_5); tab.put("6", JDK1_6); tab.put("7", JDK1_7); + tab.put("8", JDK1_8); + tab.put("9", JDK1_9); + tab.put("10", JDK1_10); + tab.put("11", JDK1_11); + tab.put("12", JDK1_12); + tab.put("13", JDK1_13); + tab.put("14", JDK1_14); + tab.put("15", JDK1_15); + tab.put("16", JDK1_16); + tab.put("17", JDK1_17); + tab.put("18", JDK1_18); + tab.put("19", JDK1_19); + tab.put("20", JDK1_20); + tab.put("21", JDK1_21); + tab.put("22", JDK1_22); } public final String name; @@ -131,241 +90,27 @@ private Target(String name, int majorVersion, int minorVersion) { this.minorVersion = minorVersion; } - public static final Target DEFAULT = JDK1_7; + public static final Target DEFAULT = JDK1_17; public static Target lookup(String name) { return tab.get(name); } - /** - * In -target 1.1 and earlier, the compiler is required to emit - * synthetic method definitions in abstract classes for interface - * methods that are not overridden. We call them "Miranda" methods. - */ public boolean requiresIproxy() { return compareTo(JDK1_1) <= 0; } - /** - * Beginning in 1.4, we take advantage of the possibility of emitting - * code to initialize fields before calling the superclass constructor. - * This is allowed by the VM spec, but the verifier refused to allow - * it until 1.4. This is necesary to translate some code involving - * inner classes. See, for example, 4030374. - */ public boolean initializeFieldsBeforeSuper() { return compareTo(JDK1_4) >= 0; } - /** - * Beginning with -target 1.2 we obey the JLS rules for binary - * compatibility, emitting as the qualifying type of a reference - * to a method or field the type of the qualifier. In earlier - * targets we use as the qualifying type the class in which the - * member was found. The following methods named - * *binaryCompatibility() indicate places where we vary from this - * general rule. - */ public boolean obeyBinaryCompatibility() { return compareTo(JDK1_2) >= 0; } - /** - * Starting in 1.5, the compiler uses an array type as - * the qualifier for method calls (such as clone) where required by - * the language and VM spec. Earlier versions of the compiler - * qualified them by Object. - */ public boolean arrayBinaryCompatibility() { return compareTo(JDK1_5) >= 0; } - /** - * Beginning after 1.2, we follow the binary compatibility rules for - * interface fields. The 1.2 VMs had bugs handling interface fields - * when compiled using binary compatibility (see 4400598), so this is - * an accommodation to them. - */ - public boolean interfaceFieldsBinaryCompatibility() { - return compareTo(JDK1_2) > 0; - } - - /** - * Beginning in -target 1.5, we follow the binary compatibility - * rules for interface methods that redefine Object methods. - * Earlier VMs had bugs handling such methods compiled using binary - * compatibility (see 4392595, 4398791, 4392595, 4400415). - * The VMs were fixed during or soon after 1.4. See 4392595. - */ - public boolean interfaceObjectOverridesBinaryCompatibility() { - return compareTo(JDK1_5) >= 0; - } - - /** - * Beginning in -target 1.4.2, we make synthetic variables - * package-private instead of private. This is to prevent the - * necessity of access methods, which effectively relax the - * protection of the field but bloat the class files and affect - * execution. - */ - public boolean usePrivateSyntheticFields() { - return compareTo(JDK1_4_2) < 0; - } - - /** - * Sometimes we need to create a field to cache a value like a - * class literal of the assertions flag. In -target 1.4.2 and - * later we create a new synthetic class for this instead of - * using the outermost class. See 4401576. - */ - public boolean useInnerCacheClass() { - return compareTo(JDK1_4_2) >= 0; - } - - /** - * Return true if cldc-style stack maps need to be generated. - */ - public boolean generateCLDCStackmap() { - return false; - } - - /** - * Beginning in -target 6, we generate stackmap attribute in - * compact format. - */ - public boolean generateStackMapTable() { - return compareTo(JDK1_6) >= 0; - } - - /** - * Beginning in -target 6, package-info classes are marked synthetic. - */ - public boolean isPackageInfoSynthetic() { - return compareTo(JDK1_6) >= 0; - } - - /** - * Do we generate "empty" stackmap slots after double and long? - */ - public boolean generateEmptyAfterBig() { - return false; - } - - /** - * Beginning in 1.5, we have an unsynchronized version of - * StringBuffer called StringBuilder that can be used by the - * compiler for string concatenation. - */ - public boolean useStringBuilder() { - return compareTo(JDK1_5) >= 0; - } - - /** - * Beginning in 1.5, we have flag bits we can use instead of - * marker attributes. - */ - public boolean useSyntheticFlag() { - return compareTo(JDK1_5) >= 0; - } - - public boolean useEnumFlag() { - return compareTo(JDK1_5) >= 0; - } - - public boolean useAnnotationFlag() { - return compareTo(JDK1_5) >= 0; - } - - public boolean useVarargsFlag() { - return compareTo(JDK1_5) >= 0; - } - - public boolean useBridgeFlag() { - return compareTo(JDK1_5) >= 0; - } - - /** - * Return the character to be used in constructing synthetic - * identifiers, where not specified by the JLS. - */ - public char syntheticNameChar() { - return '$'; - } - - /** - * Does the VM have direct support for class literals? - */ - public boolean hasClassLiterals() { - return compareTo(JDK1_5) >= 0; - } - - /** - * Does the VM support an invokedynamic instruction? - */ - public boolean hasInvokedynamic() { - return compareTo(JDK1_7) >= 0; - } - - /** - * Does the VM support polymorphic method handle invocation? - * Affects the linkage information output to the classfile. - * An alias for {@code hasInvokedynamic}, since all the JSR 292 features appear together. - */ - public boolean hasMethodHandles() { - return hasInvokedynamic(); - } - - /** - * Although we may not have support for class literals, should we - * avoid initializing the class that the literal refers to? - * See 4468823 - */ - public boolean classLiteralsNoInit() { - return compareTo(JDK1_4_2) >= 0; - } - - /** - * Although we may not have support for class literals, when we - * throw a NoClassDefFoundError, should we initialize its cause? - */ - public boolean hasInitCause() { - return compareTo(JDK1_4) >= 0; - } - - /** - * For bootstrapping, we use J2SE1.4's wrapper class constructors - * to implement boxing. - */ - public boolean boxWithConstructors() { - return compareTo(JDK1_5) < 0; - } - - /** - * For bootstrapping, we use J2SE1.4's java.util.Collection - * instead of java.lang.Iterable. - */ - public boolean hasIterable() { - return compareTo(JDK1_5) >= 0; - } - - /** - * For bootstrapping javac only, we do without java.lang.Enum if - * necessary. - */ - public boolean compilerBootstrap(Symbol c) { - return - this == JSR14 && - (c.flags() & Flags.ENUM) != 0 && - c.flatName().toString().startsWith("com.sun.tools.") - // && !Target.class.getSuperclass().getName().equals("java.lang.Enum") - ; - } - - /** - * In J2SE1.5.0, we introduced the "EnclosingMethod" attribute - * for improved reflection support. - */ - public boolean hasEnclosingMethodAttribute() { - return compareTo(JDK1_5) >= 0 || this == JSR14; - } -} + // Add more helpers, backward-compat rules, etc., as necessary. +} \ No newline at end of file diff --git a/native/src/main/java/com/duy/native/CppStandard.java b/native/src/main/java/com/duy/native/CppStandard.java new file mode 100644 index 00000000..ec89fde3 --- /dev/null +++ b/native/src/main/java/com/duy/native/CppStandard.java @@ -0,0 +1,24 @@ +package com.duy.native; + +/** + * Enum for supported C++ standards and the corresponding compiler flags. + */ +public enum CppStandard { + CPP_11("c++11", "-std=c++11"), + CPP_14("c++14", "-std=c++14"), + CPP_17("c++17", "-std=c++17"), + CPP_20("c++20", "-std=c++20"), + CPP_23("c++23", "-std=c++2b"); // clang/gcc currently use -std=c++2b for forthcoming C++23 + + public final String id; + public final String flag; + CppStandard(String id, String flag) { + this.id = id; + this.flag = flag; + } + + public static CppStandard fromId(String id) { + for (CppStandard s : values()) if (s.id.equals(id)) return s; + return CPP_17; + } +} \ No newline at end of file diff --git a/tools/src/main/java/com/duy/tools/ToolchainManager.java b/tools/src/main/java/com/duy/tools/ToolchainManager.java new file mode 100644 index 00000000..027e1249 --- /dev/null +++ b/tools/src/main/java/com/duy/tools/ToolchainManager.java @@ -0,0 +1,172 @@ +package com.duy.tools; + +import java.io.*; +import java.nio.file.*; +import java.util.*; +import java.util.zip.*; + +/** + * Simple repository-local toolchain manager. + * + * Expected layout for embedded toolchains: + * third_party/jdk//bin/javac + * third_party/ndk//toolchains/.../bin/clang++ + * + * The manager is offline-only: installToolchainFromArchive() expects a local + * zip/tar file the user provided (no downloads). + */ +public class ToolchainManager { + private final Path repoRoot; + private final Path thirdPartyDir; + private final Path configFile; + + public ToolchainManager(Path repoRoot) { + this.repoRoot = repoRoot; + this.thirdPartyDir = repoRoot.resolve("third_party"); + this.configFile = repoRoot.resolve("config").resolve("toolchains.json"); + try { + Files.createDirectories(thirdPartyDir); + Files.createDirectories(configFile.getParent()); + if (!Files.exists(configFile)) { + // create default config + Files.write(configFile, Collections.singleton("{\"selectedJdk\":\"\",\"selectedNdk\":\"\"}")). + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public List discoverEmbeddedJdks() { + List jdks = new ArrayList<>(); + Path jdkRoot = thirdPartyDir.resolve("jdk"); + if (Files.exists(jdkRoot) && Files.isDirectory(jdkRoot)) { + try (DirectoryStream ds = Files.newDirectoryStream(jdkRoot)) { + for (Path p : ds) { + if (Files.isDirectory(p) && Files.exists(p.resolve("bin").resolve("javac"))) { + jdks.add(p.getFileName().toString()); + } + } + } catch (IOException ignored) { } + } + return jdks; + } + + public List discoverEmbeddedNdks() { + List ndks = new ArrayList<>(); + Path ndkRoot = thirdPartyDir.resolve("ndk"); + if (Files.exists(ndkRoot) && Files.isDirectory(ndkRoot)) { + try (DirectoryStream ds = Files.newDirectoryStream(ndkRoot)) { + for (Path p : ds) { + if (Files.isDirectory(p)) { + // quick heuristic: check for any clang++ in subtree + boolean found = Files.walk(p) + .filter(Files::isRegularFile) + .anyMatch(f -> f.getFileName().toString().startsWith("clang++") || f.getFileName().toString().equals("clang")); + if (found) ndks.add(p.getFileName().toString()); + } + } + } catch (IOException ignored) { } + } + return ndks; + } + + /** + * Install a toolchain archive into third_party//. + * The archive must be a zip file. Extraction is local only. + */ + public void installToolchainFromZip(Path archiveZip, String type, String destName) throws IOException { + if (!Files.exists(archiveZip) || !archiveZip.toString().endsWith(".zip")) { + throw new IllegalArgumentException("Only zip archives supported for now and file must exist"); + } + Path destRoot = thirdPartyDir.resolve(type).resolve(destName); + if (Files.exists(destRoot)) { + throw new IOException("Destination already exists: " + destRoot); + } + Files.createDirectories(destRoot.getParent()); + try (ZipInputStream zis = new ZipInputStream(new BufferedInputStream(Files.newInputStream(archiveZip)))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + Path out = destRoot.resolve(entry.getName()).normalize(); + if (!out.startsWith(destRoot)) throw new IOException("Zip entry outside destination"); + if (entry.isDirectory()) { + Files.createDirectories(out); + } else { + Files.createDirectories(out.getParent()); + try (OutputStream os = Files.newOutputStream(out)) { + byte[] buf = new byte[8192]; + int r; + while ((r = zis.read(buf)) != -1) os.write(buf, 0, r); + } + // Try to preserve executable bit on *nix for bins + if (entry.getName().startsWith("bin/") || entry.getName().contains("/bin/")) { + out.toFile().setExecutable(true, false); + } + } + } + } + } + + public Path findJavacForJdk(String jdkName) { + Path p = thirdPartyDir.resolve("jdk").resolve(jdkName).resolve("bin").resolve("javac"); + if (Files.exists(p)) return p; + // also allow javac.exe on Windows + Path pexe = thirdPartyDir.resolve("jdk").resolve(jdkName).resolve("bin").resolve("javac.exe"); + if (Files.exists(pexe)) return pexe; + return null; + } + + public Path findClangPPForNdk(String ndkName) throws IOException { + Path root = thirdPartyDir.resolve("ndk").resolve(ndkName); + if (!Files.exists(root)) return null; + try { + return Files.walk(root) + .filter(Files::isRegularFile) + .filter(f -> f.getFileName().toString().startsWith("clang++") || + f.getFileName().toString().equals("clang")) + .findFirst().orElse(null); + } catch (IOException e) { + throw e; + } + } + + // Settings helpers (simple key -> value store). Could be improved to JSON lib. + public void saveSelectedToolchains(String selectedJdk, String selectedNdk) throws IOException { + String json = "{\"selectedJdk\":\"" + escape(selectedJdk) + "\",\"selectedNdk\":\"" + escape(selectedNdk) + "\"}"; + Files.write(configFile, json.getBytes("UTF-8"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } + + public Map loadSelectedToolchains() throws IOException { + Map map = new HashMap<>(); + if (!Files.exists(configFile)) return map; + String s = new String(Files.readAllBytes(configFile), "UTF-8"); + // very small hand-rolled parse; acceptable for the tiny file shape we use + String jdk = extractJsonValue(s, "selectedJdk"); + String ndk = extractJsonValue(s, "selectedNdk"); + map.put("selectedJdk", jdk); + map.put("selectedNdk", ndk); + return map; + } + + private static String extractJsonValue(String json, String key) { + String pat = \"\" + key + \"\"\s*:\s*\"\"; + int i = json.indexOf(pat); + if (i < 0) return \"\"; + i += pat.length(); + int j = json.indexOf('\"', i); + if (j < 0) return \"\"; + return json.substring(i, j); + } + + private static String escape(String s) { + if (s == null) return \"\"; + return s.replace("\"", "\\\""); + } + + // Usage helpers (example) + public static void main(String[] args) throws Exception { + Path repoRoot = Paths.get(".").toAbsolutePath().normalize(); + ToolchainManager m = new ToolchainManager(repoRoot); + System.out.println("Embedded JDKs: " + m.discoverEmbeddedJdks()); + System.out.println("Embedded NDKs: " + m.discoverEmbeddedNdks()); + } +} \ No newline at end of file