Skip to content

Commit c00f722

Browse files
authored
Merge pull request #18 from DigitalSmile/17-add-validation-in-annotation-processing
Fixes #17 and some errors during validation. Minor refactoring.
2 parents 912b18a + abb2aa6 commit c00f722

File tree

15 files changed

+369
-76
lines changed

15 files changed

+369
-76
lines changed

.github/workflows/gradle.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ jobs:
3030
distribution: 'corretto'
3131
- name: Get GCC 12 for system header files
3232
run: |
33-
sudo apt install gcc-12
33+
sudo apt-get update && sudo apt-get install gcc-12
3434
- name: Install libcurl library for tests
3535
run: |
36-
sudo apt install libcurl4-openssl-dev
36+
sudo apt-get update && sudo apt-get install libcurl4-openssl-dev
3737
- name: Get libcurl source for tests
3838
run: |
3939
cd annotation-processor-test/src/test/resources/libcurl && git clone --depth 1 https://github.com/curl/curl.git

annotation-processor-test/src/test/java/io/github/digitalsmile/gpio/functions/FunctionTest.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package io.github.digitalsmile.gpio.functions;
22

33
import io.github.digitalsmile.annotation.NativeMemory;
4-
import io.github.digitalsmile.annotation.NativeMemoryOptions;
54
import io.github.digitalsmile.annotation.function.*;
65
import io.github.digitalsmile.annotation.NativeMemoryException;
6+
import io.github.digitalsmile.annotation.library.NativeFunction;
7+
import io.github.digitalsmile.annotation.library.NativeMemoryLibrary;
78
import io.github.digitalsmile.annotation.types.interfaces.NativeMemoryLayout;
89

9-
@NativeMemory(library = "3333")
10-
@NativeFunctions({
11-
@NativeFunction(name = "123", javaName = "123")
12-
})
10+
import java.util.List;
11+
1312
public interface FunctionTest {
1413
@NativeManualFunction(name = "ioctl", useErrno = true)
1514
int callByValue(int fd, long command, long data) throws NativeMemoryException;

annotation-processor-test/src/test/java/io/github/digitalsmile/gpio/libcurl/Libcurl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import io.github.digitalsmile.annotation.NativeMemoryOptions;
66
import io.github.digitalsmile.annotation.function.ByAddress;
77
import io.github.digitalsmile.annotation.function.NativeManualFunction;
8+
import io.github.digitalsmile.annotation.library.NativeFunction;
9+
import io.github.digitalsmile.annotation.library.NativeMemoryLibrary;
810
import io.github.digitalsmile.annotation.structure.Enum;
911
import io.github.digitalsmile.annotation.structure.Enums;
1012
import io.github.digitalsmile.annotation.structure.Struct;

annotation-processor-test/src/test/java/io/github/digitalsmile/gpio/libvlc/LibVLC.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
@NativeMemory(headers = "libvlc/vlc/include/vlc/vlc.h")
1717
@NativeMemoryOptions(
1818
includes = "libvlc/vlc/include",
19-
systemIncludes = "/usr/lib/gcc/x86_64-linux-gnu/12/include/")
19+
systemIncludes = "/usr/lib/gcc/x86_64-linux-gnu/12/include/",
20+
debugMode = true
21+
)
2022
@Structs
2123
@Enums
2224
@Unions
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.github.digitalsmile.gpio.smbus;
2+
3+
import io.github.digitalsmile.annotation.NativeMemory;
4+
import io.github.digitalsmile.annotation.NativeMemoryOptions;
5+
import io.github.digitalsmile.annotation.structure.Struct;
6+
import io.github.digitalsmile.annotation.structure.Structs;
7+
8+
//@NativeMemory(headers = "/home/ds/linux/include/uapi/linux/i2c-dev.h")
9+
@NativeMemoryOptions(
10+
includes = "/home/ds/linux/include/",
11+
systemIncludes = {"/home/ds/linux/include/"})
12+
@Structs({
13+
@Struct(name = "i2c_smbus_ioctl_data", javaName = "SMBusIoctlData")
14+
})
15+
public interface SMBus {
16+
}

annotation-processor/src/main/java/io/github/digitalsmile/NativeProcessor.java

Lines changed: 77 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44
import io.avaje.prism.GeneratePrism;
55
import io.github.digitalsmile.annotation.ArenaType;
66
import io.github.digitalsmile.annotation.NativeMemory;
7-
import io.github.digitalsmile.annotation.NativeMemoryException;
87
import io.github.digitalsmile.annotation.NativeMemoryOptions;
98
import io.github.digitalsmile.annotation.function.ByAddress;
109
import io.github.digitalsmile.annotation.function.NativeManualFunction;
1110
import io.github.digitalsmile.annotation.function.Returns;
12-
import io.github.digitalsmile.annotation.structure.Enums;
13-
import io.github.digitalsmile.annotation.structure.Structs;
14-
import io.github.digitalsmile.annotation.structure.Unions;
11+
import io.github.digitalsmile.annotation.library.NativeMemoryLibrary;
12+
import io.github.digitalsmile.annotation.structure.*;
13+
import io.github.digitalsmile.annotation.structure.Enum;
1514
import io.github.digitalsmile.annotation.types.interfaces.NativeMemoryLayout;
1615
import io.github.digitalsmile.composers.*;
1716
import io.github.digitalsmile.functions.FunctionNode;
@@ -25,6 +24,8 @@
2524
import io.github.digitalsmile.headers.mapping.OriginalType;
2625
import io.github.digitalsmile.headers.model.NativeMemoryNode;
2726
import io.github.digitalsmile.headers.model.NodeType;
27+
import io.github.digitalsmile.validation.NativeProcessorValidator;
28+
import io.github.digitalsmile.validation.ValidationException;
2829
import org.openjdk.jextract.Declaration;
2930
import org.openjdk.jextract.JextractTool;
3031
import org.openjdk.jextract.Position;
@@ -50,13 +51,18 @@
5051
import java.util.regex.Pattern;
5152
import java.util.stream.Stream;
5253

54+
import static java.util.Collections.emptyList;
55+
5356
@GeneratePrism(NativeManualFunction.class)
5457
@SupportedSourceVersion(SourceVersion.RELEASE_22)
5558
public class NativeProcessor extends AbstractProcessor {
5659

60+
private NativeProcessorValidator validator;
61+
62+
5763
@Override
5864
public Set<String> getSupportedAnnotationTypes() {
59-
return Set.of(NativeMemory.class.getName());
65+
return Set.of(NativeMemory.class.getName(), NativeMemoryLibrary.class.getName(), NativeManualFunction.class.getName());
6066
}
6167

6268

@@ -65,75 +71,105 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
6571
if (roundEnv.processingOver()) {
6672
return true;
6773
}
68-
for (Element rootElement : roundEnv.getElementsAnnotatedWith(NativeMemory.class)) {
74+
this.validator = new NativeProcessorValidator(processingEnv.getMessager(), processingEnv.getElementUtils(), processingEnv.getTypeUtils());
75+
76+
for (Element rootElement : roundEnv.getRootElements()) {
77+
if (!rootElement.getKind().isInterface()) {
78+
continue;
79+
}
80+
var nativeMemory = rootElement.getAnnotation(NativeMemory.class);
81+
var manualFunctionElements = rootElement.getEnclosedElements().stream().filter(p -> p.getAnnotation(NativeManualFunction.class) != null).toList();
82+
var automaticFunctionElements = rootElement.getAnnotation(NativeMemoryLibrary.class);
83+
if (nativeMemory == null && manualFunctionElements.isEmpty() && automaticFunctionElements == null) {
84+
continue;
85+
}
86+
6987
try {
70-
var nativeAnnotation = rootElement.getAnnotation(NativeMemory.class);
71-
var nativeOptions = rootElement.getAnnotation(NativeMemoryOptions.class);
72-
var headerFiles = nativeAnnotation.headers();
88+
var parsed = Collections.<NativeMemoryNode>emptyList();
7389
var packageName = processingEnv.getElementUtils().getPackageOf(rootElement).getQualifiedName().toString();
90+
var nativeOptions = rootElement.getAnnotation(NativeMemoryOptions.class);
7491
if (nativeOptions != null && !nativeOptions.packageName().isEmpty()) {
75-
packageName = nativeOptions.packageName();
92+
packageName = validator.validatePackageName(nativeOptions.packageName());
7693
}
7794
PackageName.setDefaultPackageName(packageName);
7895

79-
var parsed = processHeaderFiles(rootElement, headerFiles, packageName, nativeOptions);
8096

81-
List<Element> functionElements = new ArrayList<Element>(roundEnv.getElementsAnnotatedWith(NativeManualFunction.class)).stream()
82-
.filter(f -> f.getEnclosingElement().equals(rootElement)).toList();
83-
processFunctions(rootElement, functionElements, packageName, parsed, nativeOptions);
97+
if (nativeMemory != null) {
98+
var headerFiles = nativeMemory.headers();
99+
parsed = processHeaderFiles(rootElement, headerFiles, packageName, nativeOptions);
100+
}
101+
102+
List<Element> manualFunctions = new ArrayList<>();
103+
for (Element manualFunction : manualFunctionElements) {
104+
validator.validateManualFunction(manualFunction);
105+
manualFunctions.add(manualFunction);
106+
}
107+
108+
if (automaticFunctionElements != null) {
109+
validator.validateAutomaticFunctions(parsed, automaticFunctionElements);
110+
}
84111

112+
processFunctions(rootElement, manualFunctions, packageName, parsed, nativeOptions);
113+
} catch (ValidationException e) {
114+
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage(), e.getElement());
85115
} catch (Throwable e) {
86116
printStackTrace(e);
87-
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
117+
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage(), rootElement);
88118
}
89119
}
90120
return true;
91121
}
92122

93-
public record Type(String name, String javaName) {
94-
}
95-
96-
private List<NativeMemoryNode> processHeaderFiles(Element element, String[] headerFiles, String packageName, NativeMemoryOptions options) {
123+
private List<NativeMemoryNode> processHeaderFiles(Element element, String[] headerFiles, String packageName, NativeMemoryOptions options) throws ValidationException {
97124
if (headerFiles.length == 0) {
98-
return Collections.emptyList();
125+
return emptyList();
99126
}
100127
List<Path> headerPaths = getHeaderPaths(headerFiles);
101128
for (Path path : headerPaths) {
102129
if (!path.toFile().exists()) {
103130
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Cannot find header file '" + path + "'! Please, check file location", element);
104-
return Collections.emptyList();
131+
return emptyList();
105132
}
106133
}
107134

108135
var structsAnnotation = element.getAnnotation(Structs.class);
109136
var unionsAnnotation = element.getAnnotation(Unions.class);
110137
var enumsAnnotation = element.getAnnotation(Enums.class);
111138

112-
List<Type> structs = null;
139+
List<String> structs = null;
113140
if (structsAnnotation != null) {
114-
structs = Arrays.stream(structsAnnotation.value()).map(struct -> new Type(struct.name(), struct.javaName())).toList();
115-
Arrays.stream(structsAnnotation.value()).forEach(struct -> PrettyName.addName(struct.name(), struct.javaName()));
141+
structs = Arrays.stream(structsAnnotation.value()).map(Struct::name).toList();
142+
for (Struct struct : structsAnnotation.value()) {
143+
var javaName = validator.validateJavaName(struct.javaName());
144+
PrettyName.addName(struct.name(), javaName);
145+
}
116146
}
117-
List<Type> enums = null;
147+
List<String> enums = null;
118148
if (enumsAnnotation != null) {
119-
enums = Arrays.stream(enumsAnnotation.value()).map(enoom -> new Type(enoom.name(), enoom.javaName())).toList();
120-
Arrays.stream(enumsAnnotation.value()).forEach(enoom -> PrettyName.addName(enoom.name(), enoom.javaName()));
149+
enums = Arrays.stream(enumsAnnotation.value()).map(Enum::name).toList();
150+
for (Enum enoom : enumsAnnotation.value()) {
151+
var javaName = validator.validateJavaName(enoom.javaName());
152+
PrettyName.addName(enoom.name(), javaName);
153+
}
121154
}
122-
List<Type> unions = null;
155+
List<String> unions = null;
123156
if (unionsAnnotation != null) {
124-
unions = Arrays.stream(unionsAnnotation.value()).map(union -> new Type(union.name(), union.javaName())).toList();
125-
Arrays.stream(unionsAnnotation.value()).forEach(union -> PrettyName.addName(union.name(), union.javaName()));
157+
unions = Arrays.stream(unionsAnnotation.value()).map(Union::name).toList();
158+
for (Union union : unionsAnnotation.value()) {
159+
var javaName = validator.validateJavaName(union.javaName());
160+
PrettyName.addName(union.name(), javaName);
161+
}
126162
}
127163

128164
var rootConstants = false;
129165
var debug = false;
130166
var systemHeader = false;
131-
List<String> includes = Collections.emptyList();
132-
List<String> systemIncludes = Collections.emptyList();
167+
List<String> includes = emptyList();
168+
List<String> systemIncludes = emptyList();
133169
if (options != null) {
134170
rootConstants = options.processRootConstants();
135171
includes = getHeaderPaths(options.includes()).stream().map(p -> "-I" + p.toFile().getAbsolutePath()).toList();
136-
systemIncludes = Arrays.stream(options.systemIncludes()).map(p -> "-isystem" + p).toList();
172+
systemIncludes = getHeaderPaths(options.systemIncludes()).stream().map(p -> "-isystem" + p.toFile().getAbsolutePath()).toList();
137173
debug = options.debugMode();
138174
systemHeader = options.systemHeader();
139175
}
@@ -149,7 +185,7 @@ private List<NativeMemoryNode> processHeaderFiles(Element element, String[] head
149185
printStackTrace(e);
150186
}
151187
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
152-
return Collections.emptyList();
188+
return emptyList();
153189
}
154190
}
155191
var parser = new Parser(packageName, processingEnv.getMessager(), processingEnv.getFiler());
@@ -167,11 +203,11 @@ private List<NativeMemoryNode> processHeaderFiles(Element element, String[] head
167203
}
168204
return parsed;
169205
} catch (Throwable e) {
170-
if (debug) {
171-
printStackTrace(e);
172-
}
206+
//if (debug) {
207+
printStackTrace(e);
208+
//}
173209
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
174-
return Collections.emptyList();
210+
return emptyList();
175211
}
176212
}
177213

@@ -191,9 +227,6 @@ private void processStructs(NativeMemoryNode node) {
191227
}
192228

193229
private void processEnums(NativeMemoryNode node, boolean rootConstants) {
194-
if (node.nodes().isEmpty()) {
195-
return;
196-
}
197230
var name = node.getName();
198231
if (node.getName().endsWith("_constants")) {
199232
if (!rootConstants) {
@@ -225,11 +258,6 @@ private void processFunctions(Element rootElement, List<Element> functionElement
225258
if (!(element instanceof ExecutableElement functionElement)) {
226259
continue;
227260
}
228-
var throwType = processingEnv.getElementUtils().getTypeElement(NativeMemoryException.class.getName()).asType();
229-
if (!functionElement.getThrownTypes().contains(throwType)) {
230-
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Method '" + functionElement + "' must throw NativeMemoryException!", functionElement);
231-
break;
232-
}
233261
var instance = NativeManualFunctionPrism.getInstanceOn(functionElement);
234262
if (instance == null) {
235263
break;
@@ -240,7 +268,7 @@ private void processFunctions(Element rootElement, List<Element> functionElement
240268
break;
241269
}
242270
List<ParameterNode> parameters = new ArrayList<>();
243-
var returnType = OriginalType.of(processingEnv.getTypeUtils().erasure(functionElement.getReturnType()));
271+
var returnType = OriginalType.of(functionElement.getReturnType());
244272
var returnNode = flatten.stream().filter(p -> PrettyName.getObjectName(p.getName()).equals(returnType.typeName())).findFirst().orElse(null);
245273
if (returnNode == null) {
246274
var type = functionElement.getReturnType();
@@ -319,7 +347,7 @@ private OriginalType getBoundsOriginalType(ExecutableElement functionElement) {
319347

320348
private FileObject tmpFile;
321349

322-
private List<Path> getHeaderPaths(String... headerFiles) {
350+
private List<Path> getHeaderPaths(String... headerFiles) throws ValidationException {
323351
List<Path> paths = new ArrayList<>();
324352
for (String headerFile : headerFiles) {
325353
var beginVariable = headerFile.indexOf("${");
@@ -334,6 +362,7 @@ private List<Path> getHeaderPaths(String... headerFiles) {
334362
continue;
335363
}
336364
}
365+
headerFile = validator.validatePath(headerFile);
337366
Path headerPath;
338367
if (headerFile.startsWith("/")) {
339368
headerPath = Path.of(headerFile);

annotation-processor/src/main/java/io/github/digitalsmile/composers/FunctionComposer.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import javax.annotation.processing.Messager;
1717
import javax.lang.model.element.Modifier;
1818
import javax.lang.model.element.TypeParameterElement;
19+
import javax.tools.Diagnostic;
1920
import java.lang.foreign.MemorySegment;
2021
import java.lang.foreign.ValueLayout;
2122
import java.util.ArrayList;
@@ -156,9 +157,12 @@ public String compose(String packageName, String originalName, List<FunctionNode
156157
.returns(createTypeName(returnType))
157158
.addException(NativeMemoryException.class);
158159

160+
var returnTypeName = functionNode.returnNode().getType().typeName();
159161
for (TypeParameterElement typeParameterElement : functionNode.typeVariables()) {
160-
methodSpecBuilder.addTypeVariable(TypeVariableName.get(typeParameterElement))
161-
.returns(TypeName.get(typeParameterElement.asType()));
162+
methodSpecBuilder.addTypeVariable(TypeVariableName.get(typeParameterElement));
163+
if (returnTypeName.equals(typeParameterElement.asType().toString())) {
164+
methodSpecBuilder.returns(TypeName.get(typeParameterElement.asType()));
165+
}
162166
}
163167

164168
for (ParameterNode parameterNode : functionNode.functionParameters()) {

0 commit comments

Comments
 (0)