Cmd line tool rulec to compile jar from prolog
Rulec takes rules.pl from the refs/meta/config branch and creates a jar file named rules-(sha1 of rules.pl).jar in (sitepath)/cache/rules. Generates temporary prolog, java src, and class files which are deleted afterwards. Change-Id: I385bce45c2261514683357c7a1b0fd9706d1c408
This commit is contained in:
committed by
Shawn O. Pearce
parent
1b8bbb0f78
commit
fa43ad2e63
@@ -24,6 +24,9 @@ link:pgm-ExportReviewNotes.html[ExportReviewNotes]::
|
||||
link:pgm-ScanTrackingIds.html[ScanTrackingIds]::
|
||||
Rescan all changes after configuring trackingids.
|
||||
|
||||
link:pgm-rulec.html[rulec]::
|
||||
Compiles prolog rules into jar files
|
||||
|
||||
version::
|
||||
Display the release version of Gerrit Code Review.
|
||||
|
||||
|
||||
48
Documentation/pgm-rulec.txt
Normal file
48
Documentation/pgm-rulec.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
rulec
|
||||
=====
|
||||
|
||||
NAME
|
||||
----
|
||||
rulec - Compiles prolog rules into jar files
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'java' -jar gerrit.war 'rulec' -d <SITE_PATH> --name <PROJECT_NAME>
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
Looks for a prolog rule file named rules.pl in the repository in the
|
||||
refs/meta/config branch. If it exists, creates a jar file named
|
||||
rules-`$sha1_of_rules.pl`.jar in `$site_path`/cache/rules. Caching needs
|
||||
to be enabled on the server.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
|
||||
-d::
|
||||
--site-path::
|
||||
Location of the gerrit.config file, and all other per-site
|
||||
configuration data, supporting libaries and log files.
|
||||
|
||||
--name::
|
||||
Number of threads to perform the scan work with. Defaults to
|
||||
twice the number of CPUs available.
|
||||
|
||||
CONTEXT
|
||||
-------
|
||||
This command can only be run on a server which has direct
|
||||
connectivity to the metadata database, and local access to the
|
||||
managed Git repositories.
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
To create a jar file for test/project:
|
||||
|
||||
====
|
||||
$ java -jar gerrit.war rulec -d site_path --name test/project
|
||||
====
|
||||
|
||||
GERRIT
|
||||
------
|
||||
Part of link:index.html[Gerrit Code Review]
|
||||
115
gerrit-pgm/src/main/java/com/google/gerrit/pgm/Rulec.java
Normal file
115
gerrit-pgm/src/main/java/com/google/gerrit/pgm/Rulec.java
Normal file
@@ -0,0 +1,115 @@
|
||||
// Copyright (C) 2011 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.pgm;
|
||||
|
||||
import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
|
||||
|
||||
import com.google.gerrit.lifecycle.LifecycleManager;
|
||||
import com.google.gerrit.pgm.util.SiteProgram;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
import com.google.gerrit.rules.PrologCompiler;
|
||||
import com.google.gerrit.server.config.FactoryModule;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
import com.googlecode.prolog_cafe.compiler.CompileException;
|
||||
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Gets rules.pl at refs/meta/config and compiles into jar file called
|
||||
* rules-(sha1 of rules.pl).jar in (site-path)/cache/rules
|
||||
*/
|
||||
public class Rulec extends SiteProgram {
|
||||
@Option(name = "--all", usage = "recompile all rules")
|
||||
private boolean all;
|
||||
|
||||
@Option(name = "--quiet", usage = "supress some messsages")
|
||||
private boolean quiet;
|
||||
|
||||
@Argument(index = 0, multiValued = true, metaVar = "PROJECT", usage = "project to compile rules for")
|
||||
private List<String> projectNames = new ArrayList<String>();
|
||||
|
||||
private Injector dbInjector;
|
||||
|
||||
private final LifecycleManager manager = new LifecycleManager();
|
||||
|
||||
@Inject
|
||||
private GitRepositoryManager gitManager;
|
||||
|
||||
@Inject
|
||||
private PrologCompiler.Factory jarFactory;
|
||||
|
||||
@Override
|
||||
public int run() throws Exception {
|
||||
dbInjector = createDbInjector(SINGLE_USER);
|
||||
manager.add(dbInjector);
|
||||
manager.start();
|
||||
dbInjector.createChildInjector(new FactoryModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
factory(PrologCompiler.Factory.class);
|
||||
}
|
||||
}).injectMembers(this);
|
||||
|
||||
LinkedHashSet<Project.NameKey> names = new LinkedHashSet<Project.NameKey>();
|
||||
for (String name : projectNames) {
|
||||
names.add(new Project.NameKey(name));
|
||||
}
|
||||
if (all) {
|
||||
names.addAll(gitManager.list());
|
||||
}
|
||||
|
||||
boolean error = false;
|
||||
for (Project.NameKey project : names) {
|
||||
Repository git = gitManager.openRepository(project);
|
||||
try {
|
||||
switch (jarFactory.create(git).call()) {
|
||||
case NO_RULES:
|
||||
if (!all || projectNames.contains(project.get())) {
|
||||
System.err.println("error: No rules.pl in " + project.get());
|
||||
error = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case COMPILED:
|
||||
if (!quiet) {
|
||||
System.out.format("Compiled %-60s ... SUCCESS", project.get());
|
||||
System.out.println();
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (CompileException err) {
|
||||
if (showStackTrace) {
|
||||
err.printStackTrace();
|
||||
} else {
|
||||
System.err.println("fatal: " + err.getMessage());
|
||||
}
|
||||
error = true;
|
||||
} finally {
|
||||
git.close();
|
||||
}
|
||||
}
|
||||
|
||||
return !error ? 0 : 1;
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ public abstract class AbstractProgram {
|
||||
private boolean running = true;
|
||||
|
||||
@Option(name = "--show-stack-trace", usage = "display stack trace on failure")
|
||||
private boolean showStackTrace;
|
||||
protected boolean showStackTrace;
|
||||
|
||||
@Option(name = "--help", usage = "display this help text", aliases = {"-h"})
|
||||
private boolean help;
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
// Copyright (C) 2011 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.rules;
|
||||
|
||||
import com.google.gerrit.common.Version;
|
||||
import com.google.gerrit.server.config.GerritServerConfig;
|
||||
import com.google.gerrit.server.config.SitePaths;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import com.googlecode.prolog_cafe.compiler.CompileException;
|
||||
import com.googlecode.prolog_cafe.compiler.Compiler;
|
||||
|
||||
import org.eclipse.jgit.errors.MissingObjectException;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.DiagnosticCollector;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
import javax.tools.ToolProvider;
|
||||
|
||||
/**
|
||||
* Helper class for Rulec: does the actual prolog -> java src -> class -> jar work
|
||||
* Finds rules.pl in refs/meta/config branch
|
||||
* Creates rules-(sha1 of rules.pl).jar in (site-path)/cache/rules
|
||||
*/
|
||||
public class PrologCompiler implements Callable<PrologCompiler.Status> {
|
||||
public interface Factory {
|
||||
PrologCompiler create(Repository git);
|
||||
}
|
||||
|
||||
public static enum Status {
|
||||
NO_RULES, COMPILED;
|
||||
}
|
||||
|
||||
private final File ruleDir;
|
||||
private final Repository git;
|
||||
|
||||
@Inject
|
||||
PrologCompiler(@GerritServerConfig Config config, SitePaths site,
|
||||
@Assisted Repository gitRepository) {
|
||||
File cacheDir = site.resolve(config.getString("cache", null, "directory"));
|
||||
ruleDir = cacheDir != null ? new File(cacheDir, "rules") : null;
|
||||
git = gitRepository;
|
||||
}
|
||||
|
||||
public Status call() throws IOException, CompileException {
|
||||
ObjectId metaConfig = git.resolve(GitRepositoryManager.REF_CONFIG);
|
||||
if (metaConfig == null) {
|
||||
return Status.NO_RULES;
|
||||
}
|
||||
|
||||
ObjectId rulesId = git.resolve(metaConfig.name() + ":rules.pl");
|
||||
if (rulesId == null) {
|
||||
return Status.NO_RULES;
|
||||
}
|
||||
|
||||
if (ruleDir == null) {
|
||||
throw new CompileException("Caching not enabled");
|
||||
}
|
||||
if (!ruleDir.isDirectory() && !ruleDir.mkdir()) {
|
||||
throw new IOException("Cannot create " + ruleDir);
|
||||
}
|
||||
|
||||
File tempDir = File.createTempFile("GerritCodeReview_", ".rulec");
|
||||
if (!tempDir.delete() || !tempDir.mkdir()) {
|
||||
throw new IOException("Cannot create " + tempDir);
|
||||
}
|
||||
try {
|
||||
// Try to make the directory accessible only by this process.
|
||||
// This may help to prevent leaking rule data to outsiders.
|
||||
tempDir.setReadable(true, true);
|
||||
tempDir.setWritable(true, true);
|
||||
tempDir.setExecutable(true, true);
|
||||
|
||||
compileProlog(rulesId, tempDir);
|
||||
compileJava(tempDir);
|
||||
|
||||
File jarFile = new File(ruleDir, "rules-" + rulesId.getName() + ".jar");
|
||||
List<String> classFiles = getRelativePaths(tempDir, ".class");
|
||||
createJar(jarFile, classFiles, tempDir, metaConfig, rulesId);
|
||||
|
||||
return Status.COMPILED;
|
||||
} finally {
|
||||
deleteAllFiles(tempDir);
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a copy of rules.pl and compiles it into Java sources. */
|
||||
private void compileProlog(ObjectId prolog, File tempDir)
|
||||
throws IOException, CompileException {
|
||||
File tempRules = copyToTempFile(prolog, tempDir);
|
||||
try {
|
||||
Compiler comp = new Compiler();
|
||||
comp.prologToJavaSource(tempRules.getPath(), tempDir.getPath());
|
||||
} finally {
|
||||
tempRules.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private File copyToTempFile(ObjectId blobId, File tempDir)
|
||||
throws IOException, FileNotFoundException, MissingObjectException {
|
||||
// Any leak of tmp caused by this method failing will be cleaned
|
||||
// up by our caller when tempDir is recursively deleted.
|
||||
File tmp = File.createTempFile("rules", ".pl", tempDir);
|
||||
FileOutputStream out = new FileOutputStream(tmp);
|
||||
try {
|
||||
git.open(blobId).copyTo(out);
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/** Compile java src into java .class files */
|
||||
private void compileJava(File tempDir) throws IOException, CompileException {
|
||||
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
if (compiler == null) {
|
||||
throw new CompileException("JDK required (running inside of JRE)");
|
||||
}
|
||||
|
||||
DiagnosticCollector<JavaFileObject> diagnostics =
|
||||
new DiagnosticCollector<JavaFileObject>();
|
||||
StandardJavaFileManager fileManager =
|
||||
compiler.getStandardFileManager(diagnostics, null, null);
|
||||
try {
|
||||
Iterable<? extends JavaFileObject> compilationUnits = fileManager
|
||||
.getJavaFileObjectsFromFiles(getAllFiles(tempDir, ".java"));
|
||||
ArrayList<String> options = new ArrayList<String>();
|
||||
String classpath = getMyClasspath();
|
||||
if (classpath != null) {
|
||||
options.add("-classpath");
|
||||
options.add(classpath);
|
||||
}
|
||||
options.add("-d");
|
||||
options.add(tempDir.getPath());
|
||||
JavaCompiler.CompilationTask task = compiler.getTask(
|
||||
null,
|
||||
fileManager,
|
||||
diagnostics,
|
||||
options,
|
||||
null,
|
||||
compilationUnits);
|
||||
if (!task.call()) {
|
||||
Locale myLocale = Locale.getDefault();
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.append("Cannot compile to Java bytecode:");
|
||||
for (Diagnostic<? extends JavaFileObject> err : diagnostics.getDiagnostics()) {
|
||||
msg.append('\n');
|
||||
msg.append(err.getKind());
|
||||
msg.append(": ");
|
||||
if (err.getSource() != null) {
|
||||
msg.append(err.getSource().getName());
|
||||
}
|
||||
msg.append(':');
|
||||
msg.append(err.getLineNumber());
|
||||
msg.append(": ");
|
||||
msg.append(err.getMessage(myLocale));
|
||||
}
|
||||
throw new CompileException(msg.toString());
|
||||
}
|
||||
} finally {
|
||||
fileManager.close();
|
||||
}
|
||||
}
|
||||
|
||||
private String getMyClasspath() {
|
||||
StringBuilder cp = new StringBuilder();
|
||||
appendClasspath(cp, getClass().getClassLoader());
|
||||
return 0 < cp.length() ? cp.toString() : null;
|
||||
}
|
||||
|
||||
private void appendClasspath(StringBuilder cp, ClassLoader classLoader) {
|
||||
if (classLoader.getParent() != null) {
|
||||
appendClasspath(cp, classLoader.getParent());
|
||||
}
|
||||
if (classLoader instanceof URLClassLoader) {
|
||||
for (URL url : ((URLClassLoader) classLoader).getURLs()) {
|
||||
if ("file".equals(url.getProtocol())) {
|
||||
if (0 < cp.length()) {
|
||||
cp.append(File.pathSeparatorChar);
|
||||
}
|
||||
cp.append(url.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Takes compiled prolog .class files, puts them into the jar file. */
|
||||
private void createJar(File archiveFile, List<String> toBeJared,
|
||||
File tempDir, ObjectId metaConfig, ObjectId rulesId) throws IOException {
|
||||
long now = System.currentTimeMillis();
|
||||
File tmpjar = File.createTempFile(".rulec_", ".jar", archiveFile.getParentFile());
|
||||
try {
|
||||
Manifest mf = new Manifest();
|
||||
mf.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
|
||||
mf.getMainAttributes().putValue("Built-by", "Gerrit Code Review " + Version.getVersion());
|
||||
if (git.getDirectory() != null) {
|
||||
mf.getMainAttributes().putValue("Source-Repository", git.getDirectory().getPath());
|
||||
}
|
||||
mf.getMainAttributes().putValue("Source-Commit", metaConfig.name());
|
||||
mf.getMainAttributes().putValue("Source-Blob", rulesId.name());
|
||||
|
||||
FileOutputStream stream = new FileOutputStream(tmpjar);
|
||||
JarOutputStream out = new JarOutputStream(stream, mf);
|
||||
byte buffer[] = new byte[10240];
|
||||
try {
|
||||
for (String path : toBeJared) {
|
||||
JarEntry jarAdd = new JarEntry(path);
|
||||
File f = new File(tempDir, path);
|
||||
jarAdd.setTime(now);
|
||||
out.putNextEntry(jarAdd);
|
||||
if (f.isFile()) {
|
||||
FileInputStream in = new FileInputStream(f);
|
||||
try {
|
||||
while (true) {
|
||||
int nRead = in.read(buffer, 0, buffer.length);
|
||||
if (nRead <= 0) {
|
||||
break;
|
||||
}
|
||||
out.write(buffer, 0, nRead);
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
out.closeEntry();
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
|
||||
if (!tmpjar.renameTo(archiveFile)) {
|
||||
throw new IOException("Cannot replace " + archiveFile);
|
||||
}
|
||||
} finally {
|
||||
tmpjar.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private List<File> getAllFiles(File dir, String extension) {
|
||||
ArrayList<File> fileList = new ArrayList<File>();
|
||||
getAllFiles(dir, extension, fileList);
|
||||
return fileList;
|
||||
}
|
||||
|
||||
private void getAllFiles(File dir, String extension, List<File> fileList) {
|
||||
for (File f : dir.listFiles()) {
|
||||
if (f.getName().endsWith(extension)) {
|
||||
fileList.add(f);
|
||||
}
|
||||
if (f.isDirectory()) {
|
||||
getAllFiles(f, extension, fileList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getRelativePaths(File dir, String extension) {
|
||||
ArrayList<String> pathList = new ArrayList<String>();
|
||||
getRelativePaths(dir, extension, "", pathList);
|
||||
return pathList;
|
||||
}
|
||||
|
||||
private void getRelativePaths(File dir, String extension, String path, List<String> pathList) {
|
||||
for (File f : dir.listFiles()) {
|
||||
if (f.getName().endsWith(extension)) {
|
||||
pathList.add(path + f.getName());
|
||||
}
|
||||
if (f.isDirectory()) {
|
||||
getRelativePaths(f, extension, path + f.getName() + "/", pathList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteAllFiles(File dir) {
|
||||
for (File f : dir.listFiles()) {
|
||||
if (f.isDirectory()) {
|
||||
deleteAllFiles(f);
|
||||
} else {
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
dir.delete();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user