LCOV - code coverage report
Current view: top level - pgm/rules - PrologCompiler.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 0 144 0.0 %
Date: 2022-11-19 15:00:39 Functions: 0 15 0.0 %

          Line data    Source code
       1             : // Copyright (C) 2011 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.pgm.rules;
      16             : 
      17             : import com.google.gerrit.common.Nullable;
      18             : import com.google.gerrit.common.Version;
      19             : import com.google.gerrit.entities.RefNames;
      20             : import com.google.gerrit.server.config.GerritServerConfig;
      21             : import com.google.gerrit.server.config.SitePaths;
      22             : import com.google.gerrit.server.util.time.TimeUtil;
      23             : import com.google.inject.Inject;
      24             : import com.google.inject.assistedinject.Assisted;
      25             : import com.googlecode.prolog_cafe.compiler.Compiler;
      26             : import com.googlecode.prolog_cafe.exceptions.CompileException;
      27             : import java.io.File;
      28             : import java.io.FileNotFoundException;
      29             : import java.io.IOException;
      30             : import java.io.InputStream;
      31             : import java.io.OutputStream;
      32             : import java.net.URL;
      33             : import java.net.URLClassLoader;
      34             : import java.nio.file.Files;
      35             : import java.nio.file.Path;
      36             : import java.util.ArrayList;
      37             : import java.util.List;
      38             : import java.util.Locale;
      39             : import java.util.concurrent.Callable;
      40             : import java.util.jar.Attributes;
      41             : import java.util.jar.JarEntry;
      42             : import java.util.jar.JarOutputStream;
      43             : import java.util.jar.Manifest;
      44             : import javax.tools.Diagnostic;
      45             : import javax.tools.DiagnosticCollector;
      46             : import javax.tools.JavaCompiler;
      47             : import javax.tools.JavaFileObject;
      48             : import javax.tools.StandardJavaFileManager;
      49             : import javax.tools.ToolProvider;
      50             : import org.eclipse.jgit.errors.MissingObjectException;
      51             : import org.eclipse.jgit.lib.Config;
      52             : import org.eclipse.jgit.lib.ObjectId;
      53             : import org.eclipse.jgit.lib.Repository;
      54             : 
      55             : /**
      56             :  * Helper class for Rulec: does the actual prolog -> java src -> class -> jar work Finds rules.pl in
      57             :  * refs/meta/config branch Creates rules-(sha1 of rules.pl).jar in (site-path)/cache/rules
      58             :  */
      59             : public class PrologCompiler implements Callable<PrologCompiler.Status> {
      60             :   public interface Factory {
      61             :     PrologCompiler create(Repository git);
      62             :   }
      63             : 
      64           0 :   public enum Status {
      65           0 :     NO_RULES,
      66           0 :     COMPILED
      67             :   }
      68             : 
      69             :   private final Path ruleDir;
      70             :   private final Repository git;
      71             : 
      72             :   @Inject
      73             :   PrologCompiler(
      74           0 :       @GerritServerConfig Config config, SitePaths site, @Assisted Repository gitRepository) {
      75           0 :     Path cacheDir = site.resolve(config.getString("cache", null, "directory"));
      76           0 :     ruleDir = cacheDir != null ? cacheDir.resolve("rules") : null;
      77           0 :     git = gitRepository;
      78           0 :   }
      79             : 
      80             :   @Override
      81             :   public Status call() throws IOException, CompileException {
      82           0 :     ObjectId metaConfig = git.resolve(RefNames.REFS_CONFIG);
      83           0 :     if (metaConfig == null) {
      84           0 :       return Status.NO_RULES;
      85             :     }
      86             : 
      87           0 :     ObjectId rulesId = git.resolve(metaConfig.name() + ":rules.pl");
      88           0 :     if (rulesId == null) {
      89           0 :       return Status.NO_RULES;
      90             :     }
      91             : 
      92           0 :     if (ruleDir == null) {
      93           0 :       throw new CompileException("Caching not enabled");
      94             :     }
      95           0 :     Files.createDirectories(ruleDir);
      96             : 
      97           0 :     File tempDir = File.createTempFile("GerritCodeReview_", ".rulec");
      98           0 :     if (!tempDir.delete() || !tempDir.mkdir()) {
      99           0 :       throw new IOException("Cannot create " + tempDir);
     100             :     }
     101             :     try {
     102             :       // Try to make the directory accessible only by this process.
     103             :       // This may help to prevent leaking rule data to outsiders.
     104           0 :       tempDir.setReadable(true, true);
     105           0 :       tempDir.setWritable(true, true);
     106           0 :       tempDir.setExecutable(true, true);
     107             : 
     108           0 :       compileProlog(rulesId, tempDir);
     109           0 :       compileJava(tempDir);
     110             : 
     111           0 :       Path jarPath = ruleDir.resolve("rules-" + rulesId.getName() + ".jar");
     112           0 :       List<String> classFiles = getRelativePaths(tempDir, ".class");
     113           0 :       createJar(jarPath, classFiles, tempDir, metaConfig, rulesId);
     114             : 
     115           0 :       return Status.COMPILED;
     116             :     } finally {
     117           0 :       deleteAllFiles(tempDir);
     118             :     }
     119             :   }
     120             : 
     121             :   /** Creates a copy of rules.pl and compiles it into Java sources. */
     122             :   private void compileProlog(ObjectId prolog, File tempDir) throws IOException, CompileException {
     123           0 :     File tempRules = copyToTempFile(prolog, tempDir);
     124             :     try {
     125           0 :       Compiler comp = new Compiler();
     126           0 :       comp.prologToJavaSource(tempRules.getPath(), tempDir.getPath());
     127             :     } finally {
     128           0 :       tempRules.delete();
     129             :     }
     130           0 :   }
     131             : 
     132             :   private File copyToTempFile(ObjectId blobId, File tempDir)
     133             :       throws IOException, FileNotFoundException, MissingObjectException {
     134             :     // Any leak of tmp caused by this method failing will be cleaned
     135             :     // up by our caller when tempDir is recursively deleted.
     136           0 :     File tmp = File.createTempFile("rules", ".pl", tempDir);
     137           0 :     try (OutputStream out = Files.newOutputStream(tmp.toPath())) {
     138           0 :       git.open(blobId).copyTo(out);
     139             :     }
     140           0 :     return tmp;
     141             :   }
     142             : 
     143             :   /** Compile java src into java .class files */
     144             :   private void compileJava(File tempDir) throws IOException, CompileException {
     145           0 :     JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
     146           0 :     if (compiler == null) {
     147           0 :       throw new CompileException("JDK required (running inside of JRE)");
     148             :     }
     149             : 
     150           0 :     DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
     151           0 :     try (StandardJavaFileManager fileManager =
     152           0 :         compiler.getStandardFileManager(diagnostics, null, null)) {
     153           0 :       Iterable<? extends JavaFileObject> compilationUnits =
     154           0 :           fileManager.getJavaFileObjectsFromFiles(getAllFiles(tempDir, ".java"));
     155           0 :       ArrayList<String> options = new ArrayList<>();
     156           0 :       String classpath = getMyClasspath();
     157           0 :       if (classpath != null) {
     158           0 :         options.add("-classpath");
     159           0 :         options.add(classpath);
     160             :       }
     161           0 :       options.add("-d");
     162           0 :       options.add(tempDir.getPath());
     163           0 :       JavaCompiler.CompilationTask task =
     164           0 :           compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits);
     165           0 :       if (!task.call()) {
     166           0 :         Locale myLocale = Locale.getDefault();
     167           0 :         StringBuilder msg = new StringBuilder();
     168           0 :         msg.append("Cannot compile to Java bytecode:");
     169           0 :         for (Diagnostic<? extends JavaFileObject> err : diagnostics.getDiagnostics()) {
     170           0 :           msg.append('\n');
     171           0 :           msg.append(err.getKind());
     172           0 :           msg.append(": ");
     173           0 :           if (err.getSource() != null) {
     174           0 :             msg.append(err.getSource().getName());
     175             :           }
     176           0 :           msg.append(':');
     177           0 :           msg.append(err.getLineNumber());
     178           0 :           msg.append(": ");
     179           0 :           msg.append(err.getMessage(myLocale));
     180           0 :         }
     181           0 :         throw new CompileException(msg.toString());
     182             :       }
     183             :     }
     184           0 :   }
     185             : 
     186             :   @Nullable
     187             :   private String getMyClasspath() {
     188           0 :     StringBuilder cp = new StringBuilder();
     189           0 :     appendClasspath(cp, getClass().getClassLoader());
     190           0 :     return 0 < cp.length() ? cp.toString() : null;
     191             :   }
     192             : 
     193             :   private void appendClasspath(StringBuilder cp, ClassLoader classLoader) {
     194           0 :     if (classLoader.getParent() != null) {
     195           0 :       appendClasspath(cp, classLoader.getParent());
     196             :     }
     197           0 :     if (classLoader instanceof URLClassLoader) {
     198           0 :       for (URL url : ((URLClassLoader) classLoader).getURLs()) {
     199           0 :         if ("file".equals(url.getProtocol())) {
     200           0 :           if (0 < cp.length()) {
     201           0 :             cp.append(File.pathSeparatorChar);
     202             :           }
     203           0 :           cp.append(url.getPath());
     204             :         }
     205             :       }
     206             :     }
     207           0 :   }
     208             : 
     209             :   /** Takes compiled prolog .class files, puts them into the jar file. */
     210             :   private void createJar(
     211             :       Path archiveFile, List<String> toBeJared, File tempDir, ObjectId metaConfig, ObjectId rulesId)
     212             :       throws IOException {
     213           0 :     long now = TimeUtil.nowMs();
     214           0 :     Manifest mf = new Manifest();
     215           0 :     mf.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
     216           0 :     mf.getMainAttributes().putValue("Built-by", "Gerrit Code Review " + Version.getVersion());
     217           0 :     if (git.getDirectory() != null) {
     218           0 :       mf.getMainAttributes().putValue("Source-Repository", git.getDirectory().getPath());
     219             :     }
     220           0 :     mf.getMainAttributes().putValue("Source-Commit", metaConfig.name());
     221           0 :     mf.getMainAttributes().putValue("Source-Blob", rulesId.name());
     222             : 
     223           0 :     Path tmpjar = Files.createTempFile(archiveFile.getParent(), ".rulec_", ".jar");
     224           0 :     try (OutputStream stream = Files.newOutputStream(tmpjar);
     225           0 :         JarOutputStream out = new JarOutputStream(stream, mf)) {
     226           0 :       byte[] buffer = new byte[10240];
     227             :       // TODO: fixify this loop
     228           0 :       for (String path : toBeJared) {
     229           0 :         JarEntry jarAdd = new JarEntry(path);
     230           0 :         File f = new File(tempDir, path);
     231           0 :         jarAdd.setTime(now);
     232           0 :         out.putNextEntry(jarAdd);
     233           0 :         if (f.isFile()) {
     234           0 :           try (InputStream in = Files.newInputStream(f.toPath())) {
     235             :             while (true) {
     236           0 :               int nRead = in.read(buffer, 0, buffer.length);
     237           0 :               if (nRead <= 0) {
     238           0 :                 break;
     239             :               }
     240           0 :               out.write(buffer, 0, nRead);
     241           0 :             }
     242             :           }
     243             :         }
     244           0 :         out.closeEntry();
     245           0 :       }
     246             :     }
     247             : 
     248             :     try {
     249           0 :       Files.move(tmpjar, archiveFile);
     250           0 :     } catch (IOException e) {
     251           0 :       throw new IOException("Cannot replace " + archiveFile, e);
     252           0 :     }
     253           0 :   }
     254             : 
     255             :   private List<File> getAllFiles(File dir, String extension) throws IOException {
     256           0 :     ArrayList<File> fileList = new ArrayList<>();
     257           0 :     getAllFiles(dir, extension, fileList);
     258           0 :     return fileList;
     259             :   }
     260             : 
     261             :   private void getAllFiles(File dir, String extension, List<File> fileList) throws IOException {
     262           0 :     for (File f : listFiles(dir)) {
     263           0 :       if (f.getName().endsWith(extension)) {
     264           0 :         fileList.add(f);
     265             :       }
     266           0 :       if (f.isDirectory()) {
     267           0 :         getAllFiles(f, extension, fileList);
     268             :       }
     269             :     }
     270           0 :   }
     271             : 
     272             :   private List<String> getRelativePaths(File dir, String extension) throws IOException {
     273           0 :     ArrayList<String> pathList = new ArrayList<>();
     274           0 :     getRelativePaths(dir, extension, "", pathList);
     275           0 :     return pathList;
     276             :   }
     277             : 
     278             :   private static void getRelativePaths(
     279             :       File dir, String extension, String path, List<String> pathList) throws IOException {
     280           0 :     for (File f : listFiles(dir)) {
     281           0 :       if (f.getName().endsWith(extension)) {
     282           0 :         pathList.add(path + f.getName());
     283             :       }
     284           0 :       if (f.isDirectory()) {
     285           0 :         getRelativePaths(f, extension, path + f.getName() + "/", pathList);
     286             :       }
     287             :     }
     288           0 :   }
     289             : 
     290             :   private static void deleteAllFiles(File dir) throws IOException {
     291           0 :     for (File f : listFiles(dir)) {
     292           0 :       if (f.isDirectory()) {
     293           0 :         deleteAllFiles(f);
     294             :       } else {
     295           0 :         f.delete();
     296             :       }
     297             :     }
     298           0 :     dir.delete();
     299           0 :   }
     300             : 
     301             :   private static File[] listFiles(File dir) throws IOException {
     302           0 :     File[] files = dir.listFiles();
     303           0 :     if (files == null) {
     304           0 :       throw new IOException("Failed to list directory: " + dir);
     305             :     }
     306           0 :     return files;
     307             :   }
     308             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750