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
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,12 @@ node_modules
.idea
.vscode
.todo

# Build artifacts of the :codegen subproject
/codegen/.gradle
/codegen/build
/codegen/bin

# Generated by the generateApiBindings task (see codegen/, build.gradle)
/src/main/java/com/zhekasmirnov/innercore/api/gen
/src/test/java/com/zhekasmirnov/innercore/api/gen
62 changes: 62 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ repositories {
}
}

configurations {
// Isolated classpath for the forked codegen JVM (the :codegen subproject + JavaParser).
codegenClasspath
}

dependencies {
codegenClasspath project(':codegen')

// https://repo.lanink.cn/#browse/browse:maven-public:cn/nukkit/Nukkit/MOT-SNAPSHOT
/*implementation (group: 'cn.nukkit', name: 'Nukkit', version: 'MOT-20240213.074103-139') {
exclude group: 'org.apache.logging.log4j', module: 'log4j-slf4j-impl'
Expand All @@ -42,6 +49,15 @@ dependencies {

implementation fileTree (dir: 'iclibs', include: '*.jar')
implementation fileTree (dir: 'iclibs/dex2jar', include: '*.jar')

testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.test {
dependsOn 'generateApiBindingsTest'
useJUnitPlatform()
}

def targetJavaVersion = 17
Expand All @@ -67,6 +83,52 @@ run {
standardInput System.in
}

// Generates JS-binding base classes (com.zhekasmirnov.innercore.api.gen.*BindGenBase)
// from @ApiBinding-annotated sources, before compilation (the hand-written module classes
// extend the generated bases). The generator lives in the :codegen subproject and runs in a
// forked JVM so its JavaParser dependency stays isolated from Gradle's bundled copy.
def apiGenSourceRoot = file('src/main/java/org/innercore')
def apiGenOutputDir = layout.projectDirectory.dir('src/main/java/com/zhekasmirnov/innercore/api/gen')

def apiGenSourceTestRoot = file('src/test/java/org/innercore')
def apiGenOutputTestDir = layout.projectDirectory.dir('src/test/java/com/zhekasmirnov/innercore/api/gen')

tasks.register('generateApiBindingsTest', JavaExec) {
group = 'codegen'
description = 'Generates JS binding base classes from @ApiBinding-annotated sources.'
javaLauncher = javaToolchains.launcherFor {
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
if (JavaVersion.current() < javaVersion) {
languageVersion = JavaLanguageVersion.of(targetJavaVersion)
}
}
classpath = configurations.codegenClasspath
mainClass = 'org.innercore.codegen.Codegen'
args 'com.zhekasmirnov.innercore.api.gen', apiGenOutputTestDir.asFile.absolutePath, apiGenSourceTestRoot.absolutePath
inputs.files(fileTree(apiGenSourceRoot) { include '**/*.java' }).withPropertyName('apiBindingSources')
outputs.dir(apiGenOutputDir).withPropertyName('apiBindingOutput')
}

tasks.register('generateApiBindings', JavaExec) {
group = 'codegen'
description = 'Generates JS binding base classes from @ApiBinding-annotated sources.'
javaLauncher = javaToolchains.launcherFor {
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
if (JavaVersion.current() < javaVersion) {
languageVersion = JavaLanguageVersion.of(targetJavaVersion)
}
}
classpath = configurations.codegenClasspath
mainClass = 'org.innercore.codegen.Codegen'
args 'com.zhekasmirnov.innercore.api.gen', apiGenOutputDir.asFile.absolutePath, apiGenSourceRoot.absolutePath
inputs.files(fileTree(apiGenSourceRoot) { include '**/*.java' }).withPropertyName('apiBindingSources')
outputs.dir(apiGenOutputDir).withPropertyName('apiBindingOutput')
}

tasks.named('compileJava') {
dependsOn 'generateApiBindings'
}

task rebuildAndroidStubs(type: JavaExec, dependsOn: classes) {
javaLauncher = javaToolchains.launcherFor {
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
Expand Down
22 changes: 22 additions & 0 deletions codegen/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
plugins {
id 'java'
}

repositories {
mavenCentral()
}

dependencies {
// Java-source parser used by the API-binding code generator.
// 3.25.x is the last line targeting Java 8 bytecode (3.26+ requires Java 17).
implementation 'com.github.javaparser:javaparser-core:3.25.10'
}

// Plain library, no gradleApi() — that is exactly why this lives in its own subproject
// rather than buildSrc: it keeps JavaParser 3.25.10 off any classpath shared with Gradle's
// bundled javaparser-core 3.17.0. The generator is launched in a forked JVM (see the root
// build's generateApiBindings task), so Java 8 bytecode runs fine on the Java 17 launcher.
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
20 changes: 20 additions & 0 deletions codegen/src/main/java/org/innercore/codegen/BindingOverload.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.innercore.codegen;

import java.util.ArrayList;
import java.util.List;

/** A binding overload prepared for code generation. */
public final class BindingOverload {
public String name = "";
public String moduleName = "";
public String mangledName = "";
public String bindType = "";
public String moduleClsName = "";
public String fnType = "";
public VarDecl ret = new VarDecl(Encoding.BASE_TYPE_VOID);
public List<VarDecl> args = new ArrayList<VarDecl>();
public int argsCnt = 0;
public boolean needJavaWrap = false;
public String callSrc = "";
public List<String> attrs = new ArrayList<String>();
}
29 changes: 29 additions & 0 deletions codegen/src/main/java/org/innercore/codegen/BindingRecord.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.innercore.codegen;

import java.util.ArrayList;
import java.util.List;

/**
* A single resolved binding, equivalent to one JSON line in the reference
* {@code gen_java_bindings.txt} store. Produced by a resolver (Java here; a C++
* resolver may contribute records too) and consumed by {@link JavaBindingGenerator}.
*/
public final class BindingRecord {
/** "Module.name". */
public String bind;
/** Fully-qualified hand-written class (Java bindings); null for native records. */
public String cls;
/** Source member name. */
public String src;
/** One of the {@code FN_TYPE_*} constants. */
public String typ;
/** Whether a Java wrapper is needed (native pipeline); always false for Java. */
public boolean wrap;
public List<VarDecl> args = new ArrayList<VarDecl>();
public VarDecl ret;
public List<String> attrs = new ArrayList<String>();
/** Mangled native name (native records only). */
public String mgl;
/** Originating C++ class (native records only). */
public String cppCls;
}
31 changes: 31 additions & 0 deletions codegen/src/main/java/org/innercore/codegen/BindingStore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.innercore.codegen;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
* In-memory replacement for the reference pipeline's intermediate text files
* ({@code gen_java_bindings.txt} + {@code gen_java_modules.txt}). It is the single
* sink that resolvers feed and the generator reads.
*
* <p>The Java resolver is the only producer today. A future C++ resolver (libclang)
* can append its native {@link BindingRecord}s here as well; the generator already
* understands native records, so generation would then "just work". If the C++ pass
* runs out-of-process, this class is the natural place to add JSON-lines
* (de)serialization.
*/
public final class BindingStore {
public final List<BindingRecord> records = new ArrayList<BindingRecord>();
/** moduleName -> fully-qualified hand-written module class. */
public final Map<String, String> javaModules = new LinkedHashMap<String, String>();

public void addRecord(BindingRecord record) {
records.add(record);
}

public void addModule(String moduleName, String fullClassName) {
javaModules.put(moduleName, fullClassName);
}
}
69 changes: 69 additions & 0 deletions codegen/src/main/java/org/innercore/codegen/CodeWriter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.innercore.codegen;

/**
* A tiny indentation-aware text buffer, mirroring the reference {@code BaseGenerator}
* write/writeln helpers. {@code write} emits an optional leading indent then the
* given parts with no trailing newline; {@code writeln} emits each line with an
* optional indent and a trailing newline.
*/
public final class CodeWriter {
private final StringBuilder sb = new StringBuilder();
private final String indentChars;

public CodeWriter() {
this(" ");
}

public CodeWriter(String indentChars) {
this.indentChars = indentChars;
}

public String text() {
return sb.toString();
}

public void reset() {
sb.setLength(0);
}

private void indent(int n) {
for (int i = 0; i < n; i++) {
sb.append(indentChars);
}
}

public void write(String... parts) {
write(0, parts);
}

public void write(int indent, String... parts) {
if (indent > 0) {
indent(indent);
}
for (String p : parts) {
sb.append(p);
}
}

/** Emit a single blank line. */
public void writeln() {
sb.append('\n');
}

public void writeln(String... lines) {
writeln(0, lines);
}

public void writeln(int indent, String... lines) {
if (lines.length == 0) {
sb.append('\n');
return;
}
for (String line : lines) {
if (indent > 0 && line.length() > 0) {
indent(indent);
}
sb.append(line).append('\n');
}
}
}
84 changes: 84 additions & 0 deletions codegen/src/main/java/org/innercore/codegen/Codegen.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.innercore.codegen;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Orchestrates the API-binding codegen: clears the output package, resolves
* {@code @ApiBinding} sources into a {@link BindingStore}, then generates the
* {@code <Module>BindGenBase} + {@code ApiModuleMap} sources.
*
* <p>This is the single-process, Java-only entry point. The C++ (libclang) pass is
* out of scope; when added, it would contribute records to the same {@link BindingStore}
* before {@link JavaBindingGenerator#generate} runs.
*/
public final class Codegen {
private Codegen() {}

/** Runs codegen over an explicit set of {@code .java} files (used by the Gradle task). */
public static boolean run(List<File> javaFiles, File outputDir, String outputPackage) {
prepareOutput(outputDir);
BindingStore store = new BindingStore();
boolean resolveOk = new JavaBindingResolver(store).resolveFiles(javaFiles);
boolean generateOk = new JavaBindingGenerator(store).generate(outputDir, outputPackage);
return resolveOk && generateOk;
}

/** Runs codegen by recursively scanning source roots (used by {@link #main}). */
public static boolean runWithRoots(List<File> sourceRoots, File outputDir, String outputPackage) {
prepareOutput(outputDir);
BindingStore store = new BindingStore();
boolean resolveOk = new JavaBindingResolver(store).resolve(sourceRoots, outputDir);
boolean generateOk = new JavaBindingGenerator(store).generate(outputDir, outputPackage);
return resolveOk && generateOk;
}

private static void prepareOutput(File outputDir) {
clearDirectory(outputDir);
if (!outputDir.exists() && !outputDir.mkdirs()) {
throw new RuntimeException("failed to create output directory " + outputDir);
}
}

private static void clearDirectory(File dir) {
if (!dir.isDirectory()) {
return;
}
try (Stream<java.nio.file.Path> walk = Files.walk(dir.toPath())) {
List<java.nio.file.Path> children = walk
.filter(p -> !p.equals(dir.toPath()))
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
for (java.nio.file.Path child : children) {
Files.deleteIfExists(child);
}
} catch (IOException e) {
throw new RuntimeException("failed to clear output directory " + dir, e);
}
}

/** CLI entry point for manual runs/debugging: {@code <outputPackage> <outputDir> <root>...}. */
public static void main(String[] args) {
if (args.length < 3) {
System.err.println("usage: Codegen <outputPackage> <outputDir> <sourceRoot>...");
System.exit(2);
}
String outputPackage = args[0];
File outputDir = new File(args[1]);
List<File> roots = new ArrayList<File>();
for (int i = 2; i < args.length; i++) {
roots.add(new File(args[i]));
}
boolean ok = runWithRoots(roots, outputDir, outputPackage);
if (!ok) {
System.err.println("codegen finished with errors");
System.exit(1);
}
}
}
Loading
Loading