package com.oracle.svm.hosted.c;
import static com.oracle.svm.core.util.VMError.shouldNotReachHere;
import static java.nio.file.FileVisitResult.CONTINUE;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import com.oracle.svm.core.SubstrateUtil;
import org.graalvm.collections.UnmodifiableEconomicMap;
import org.graalvm.compiler.options.Option;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.core.option.SubstrateOptionsParser;
import com.oracle.svm.core.util.UserError;
import com.oracle.svm.hosted.c.info.NativeCodeInfo;
import com.oracle.svm.hosted.c.query.QueryResultParser;
import org.graalvm.compiler.options.OptionKey;
import org.graalvm.compiler.options.OptionValues;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Platform;
public final class CAnnotationProcessorCache {
private static final String FILE_EXTENSION = ".cap";
public static class Options {
@Option(help = "Indicate the C Annotation Processor to use previously cached native information when generating C Type information.")
public static final HostedOptionKey<Boolean> UseCAPCache = new HostedOptionKey<Boolean>(false) {
@Override
public Boolean getValueOrDefault(UnmodifiableEconomicMap<OptionKey<?>, Object> values) {
if (!values.containsKey(this)) {
if (!ExitAfterQueryCodeGeneration.getValue() && !ImageSingletons.lookup(Platform.class).getArchitecture().equals(SubstrateUtil.getArchitectureName())) {
return true;
} else {
return false;
}
}
return (Boolean) values.get(this);
}
@Override
public Boolean getValue(OptionValues values) {
assert checkDescriptorExists();
return getValueOrDefault(values.getMap());
}
};
@Option(help = "Create a C Annotation Processor Cache. Will erase any previous cache at that same location.")
public static final HostedOptionKey<Boolean> NewCAPCache = new HostedOptionKey<>(false);
@Option(help = "Directory where information generated by the CAnnotation Processor are cached.")
public static final HostedOptionKey<String> CAPCacheDir = new HostedOptionKey<>("");
@Option(help = "Exit image generation after C Annotation Processor Cache creation.")
public static final HostedOptionKey<Boolean> ExitAfterCAPCache = new HostedOptionKey<>(false);
@Option(help = "Output query code for target platform without executing it")
public static final HostedOptionKey<Boolean> ExitAfterQueryCodeGeneration = new HostedOptionKey<>(false);
@Option(help = "Directory where query code for target platform should be output")
public static final HostedOptionKey<String> QueryCodeDir = new HostedOptionKey<>("");
}
private File cache;
private File query;
public CAnnotationProcessorCache() {
if ((Options.UseCAPCache.getValue() || Options.NewCAPCache.getValue())) {
if (!Options.ExitAfterQueryCodeGeneration.getValue() &&
(Options.CAPCacheDir.getValue() == null || Options.CAPCacheDir.getValue().isEmpty())) {
throw UserError.abort("Path to C Annotation Processor Cache must be specified using %s when the option %s or %s is used.",
SubstrateOptionsParser.commandArgument(Options.CAPCacheDir, ""), SubstrateOptionsParser.commandArgument(Options.UseCAPCache, "+"),
SubstrateOptionsParser.commandArgument(Options.NewCAPCache, "+"));
}
Path cachePath = FileSystems.getDefault().getPath(Options.CAPCacheDir.getValue()).toAbsolutePath();
cache = cachePath.toFile();
if (!cache.exists()) {
try {
cache = Files.createDirectories(cachePath).toFile();
} catch (IOException e) {
throw UserError.abort("Could not create C Annotation Processor Cache directory: %s", e.getMessage());
}
} else if (!cache.isDirectory()) {
throw UserError.abort("Path to C Annotation Processor Cache is not a directory");
} else if (Options.NewCAPCache.getValue()) {
clearCache();
}
}
if (Options.QueryCodeDir.hasBeenSet()) {
Path queryPath = FileSystems.getDefault().getPath(Options.QueryCodeDir.getValue()).toAbsolutePath();
query = queryPath.toFile();
if (!query.exists()) {
try {
query = Files.createDirectories(queryPath).toFile();
} catch (IOException e) {
throw UserError.abort("Could not create query code directory: %s", e.getMessage());
}
} else if (!query.isDirectory()) {
throw UserError.abort("Path to query code directory is not a directory");
}
} else if (Options.ExitAfterQueryCodeGeneration.hasBeenSet()) {
throw UserError.abort("Query code directory wasn't specified, use %s option.", SubstrateOptionsParser.commandArgument(Options.QueryCodeDir, "PATH"));
}
}
private static String toPath(NativeCodeInfo nativeCodeInfo) {
return nativeCodeInfo.getName().replaceAll("\\W", "_").concat(FILE_EXTENSION);
}
public void get(NativeLibraries nativeLibs, NativeCodeInfo nativeCodeInfo) {
File file = new File(cache, toPath(nativeCodeInfo));
try (FileInputStream fis = new FileInputStream(file)) {
QueryResultParser.parse(nativeLibs, nativeCodeInfo, fis);
} catch (IOException e) {
throw UserError.abort("Could not load CAPCache file. Ensure that options %s and %s are used on the same version of your application. Raw error: %s",
Options.UseCAPCache.getName(), Options.NewCAPCache, e.getMessage());
}
}
public void put(NativeCodeInfo nativeCodeInfo, List<String> lines) {
if (!Options.NewCAPCache.getValue()) {
return;
}
File cachedItem = new File(cache, toPath(nativeCodeInfo));
try (FileWriter writer = new FileWriter(cachedItem, true)) {
for (String line : lines) {
writer.write(line);
writer.write(Character.LINE_SEPARATOR);
}
return;
} catch (IOException e) {
throw shouldNotReachHere("Invalid CAnnotation Processor Cache item:" + cachedItem.toString());
}
}
private void clearCache() {
try {
final Path cachePath = cache.toPath();
Files.walkFileTree(cachePath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
assert file.toString().endsWith(FILE_EXTENSION);
Files.delete(file);
return CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
if (!dir.equals(cachePath)) {
Files.delete(dir);
}
return CONTINUE;
}
});
} catch (IOException eio) {
throw shouldNotReachHere(eio);
}
}
}