Expose only extension-api to extensions
Unless a plugin declares "Gerrit-ApiType: plugin" in its manifest,
assume it is an extension and only make the gerrit-extension-api
available to it through the ClassLoader.
For non-plugins, do not make any Guice bindings available from the
server. This further restricts what an extension can see and do with
the system internals.
Change-Id: Ia38336c42786afb1419d64c06b0d908ae92a64d1
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 468e767..53683e2 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -1,61 +1,296 @@
Gerrit Code Review - Plugin Development
=======================================
-A plugin in gerrit is tightly coupled code that runs in the same
-JVM as gerrit. It has full access to all gerrit internals. Plugins
-are coupled to a specific major.minor gerrit version.
+A plugin in Gerrit is tightly coupled code that runs in the same
+JVM as Gerrit. It has full access to all server internals. Plugins
+are tightly coupled to a specific major.minor server version and
+may require source code changes to compile against a different
+server version.
+
+An extension in Gerrit runs inside of the same JVM as Gerrit
+in the same way as a plugin, but has limited visibility to the
+server's internals. The limited visiblity reduces the extension's
+dependencies, enabling it to be compatiable across a wider range
+of server versions.
+
+Most of this documentation refers to either type as a plugin.
Requirements
------------
-To start development, download the sample maven project, which downloads the
-following dependencies:
+To start development, download the sample maven project, which
+downloads the dependencies file that matches the war file to develop
+against. Dependencies are offered in two different formats:
-* gerrit-sdk.jar file that matches the war file to develop against
+gerrit-extension-api.jar::
+ A stable but thin interface. Suitable for extensions that need
+ to be notified of events, but do not require tight coupling to
+ the internals of Gerrit. Extensions built against this API can
+ expect to be binary compatible across a wide range of server
+ versions.
+gerrit-plugin-api.jar::
+ The complete internals of the Gerrit server, permitting a
+ plugin to tightly couple itself and provide additional
+ functionality that is not possible as an extension. Plugins
+ built against this API are expected to break at the source
+ code level between every major.minor Gerrit release. A plugin
+ that compiles against 2.5 will probably need source code level
+ changes to work with 2.6, 2.7, and so on.
Manifest
--------
-Plugins need to include the following data in the jar manifest file:
+Plugins may provide optional description information with standard
+manifest fields:
- Gerrit-Module = pkg.class
+====
+ Implementation-Title: Example plugin showing examples
+ Implementation-Version: 1.0
+ Implementation-Vendor: Example, Inc.
+ Implementation-URL: http://example.com/opensource/plugin-foo/
+====
-Optionally include:
+ApiType
+~~~~~~~
- Gerrit-ReloadMode = 'reload' (default) or 'restart'
+Plugins using the tightly coupled `gerrit-plugin-api.jar` must
+declare this API dependency in the manifest to gain access to server
+internals. If no Gerrit-ApiType is specified the stable `extension`
+API will be assumed. This may cause ClassNotFoundExceptions when
+loading a plugin that needs the plugin API.
-If the plugin holds an exclusive resource that must be released before loading
-the plugin again, ReloadMode must be set to 'restart'. Otherwise 'reload' is
-sufficient.
+====
+ Gerrit-ApiType: plugin
+====
+
+Explicit Registration
+~~~~~~~~~~~~~~~~~~~~~
+
+Plugins that use explicit Guice registration must name the Guice
+modules in the manifest. Up to three modules can be named in the
+manifest. Gerrit-Module supplies bindings to the core server;
+Gerrit-SshModule supplies SSH commands to the SSH server (if
+enabled); Gerrit-HttpModule supplies servlets and filters to the HTTP
+server (if enabled). If no modules are named automatic registration
+will be performed by scanning all classes in the plugin JAR for
+`@Listen` and `@Export("")` annotations.
+
+====
+ Gerrit-Module: tld.example.project.CoreModuleClassName
+ Gerrit-SshModule: tld.example.project.SshModuleClassName
+ Gerrit-HttpModule: tld.example.project.HttpModuleClassName
+====
+
+Reload Method
+~~~~~~~~~~~~~
+
+If a plugin holds an exclusive resource that must be released before
+loading the plugin again (for example listening on a network port or
+acquiring a file lock) the manifest must declare Gerrit-ReloadMode
+to be `restart`. Otherwise the preferred method of `reload` will
+be used, as it enables the server to hot-patch an updated plugin
+with no down time.
+
+====
+ Gerrit-ReloadMode: restart
+====
+
+In either mode ('restart' or 'reload') any plugin or extension can
+be updated without restarting the Gerrit server. The difference is
+how Gerrit handles the upgrade:
+
+restart::
+ The old plugin is completely stopped. All registrations of SSH
+ commands and HTTP servlets are removed. All registrations of any
+ extension points are removed. All registered LifecycleListeners
+ have their `stop()` method invoked in reverse order. The new
+ plugin is started, and registrations are made from the new
+ plugin. There is a brief window where neither the old nor the
+ new plugin is connected to the server. This means SSH commands
+ and HTTP servlets will return not found errors, and the plugin
+ will not be notified of events that occurred during the restart.
+
+reload::
+ The new plugin is started. Its LifecycleListeners are permitted
+ to perform their `start()` methods. All SSH and HTTP registrations
+ are atomically swapped out from the old plugin to the new plugin,
+ ensuring the server never returns a not found error. All extension
+ point listeners are atomically swapped out from the old plugin to
+ the new plugin, ensuring no events are missed (however some events
+ may still route to the old plugin if the swap wasn't complete yet).
+ The old plugin is stopped.
+
+Classpath
+---------
+
+Each plugin is loaded into its own ClassLoader, isolating plugins
+from each other. A plugin or extension inherits the Java runtime
+and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin)
+from the hosting server.
+
+Plugins are loaded from a single JAR file. If a plugin needs
+additional libraries, it must include those dependencies within
+its own JAR. Plugins built using Maven may be able to use the
+link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin]
+to package additional dependencies. Relocating (or renaming) classes
+should not be necessary due to the ClassLoader isolation.
SSH Commands
------------
-Plugins may provide commands that can be accessed through the SSH interface.
-These commands register themselves as a part of link:cmd-index.html[SSH Commands].
+Plugins may provide commands that can be accessed through the SSH
+interface (extensions do not have this option).
-Each of the plugin commands needs to extend SshCommand.
+Command implementations must extend the base class SshCommand:
-Any plugin which implements at least one ssh command needs to also provide a
-class which extends the PluginCommandModule in order to register the ssh
-command(s) in its configure method which must be overriden.
+====
+ import com.google.gerrit.sshd.SshCommand;
-Registering is done by calling:
+ class PrintHello extends SshCommand {
+ protected abstract void run() {
+ stdout.print("Hello\n");
+ }
+ }
+====
- command(String commandName).to(ClassName<? extends SshCommand> klass)
+If no Guice modules are declared in the manifest, SSH commands may
+use auto-registration by providing an @Export annotatation:
+
+====
+ import com.google.gerrit.extensions.annotations.Export;
+ import com.google.gerrit.sshd.SshCommand;
+
+ @Export("print")
+ class PrintHello extends SshCommand {
+ protected abstract void run() {
+ stdout.print("Hello\n");
+ }
+ }
+====
+
+If explicit registration is being used, a Guice module must be
+supplied to register the SSH command and declared in the manifest
+with the `Gerrit-SshModule` attribute:
+
+====
+ import com.google.gerrit.sshd.PluginCommandModule;
+
+ class MyCommands extends PluginCommandModule {
+ protected void configureCommands() {
+ command("print").to(PrintHello.class);
+ }
+ }
+====
+
+For a plugin installed as name `helloworld`, the command implemented
+by PrintHello class will be available to users as:
+
+----
+$ ssh -P 29418 review.example.com helloworld print
+----
+
+HTTP Servlets
+-------------
+
+Plugins or extensions may register additional HTTP servlets, and
+wrap them with HTTP filters.
+
+Servlets may use auto-registration to declare the URL they handle:
+
+====
+ import com.google.gerrit.extensions.annotations.Export;
+ import com.google.inject.Singleton;
+ import javax.servlet.http.HttpServlet;
+ import javax.servlet.http.HttpServletRequest;
+ import javax.servlet.http.HttpServletResponse;
+
+ @Export("/print")
+ @Singleton
+ class HelloServlet extends HttpServlet {
+ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
+ res.setContentType("text/plain");
+ res.setCharacterEncoding("UTF-8");
+ res.getWriter().write("Hello");
+ }
+ }
+====
+
+If explicit registration is being used, a Guice ServletModule must
+be supplied to register the HTTP servlets, and the module must be
+declared in the manifest with the `Gerrit-HttpModule` attribute:
+
+====
+ import com.google.inject.servlet.ServletModule;
+
+ class MyWebUrls extends ServletModule {
+ protected void configureServlets() {
+ serve("/print").with(HelloServlet.class);
+ }
+ }
+====
+
+For a plugin installed as name `helloworld`, the servlet implemented
+by HelloServlet class will be available to users as:
+
+----
+$ curl http://review.example.com/plugins/helloworld/print
+----
Documentation
-------------
-Place files into Documentation/ or static/ and package them into the plugin jar
-to access them in a browser via <canonicalWebURL>/plugins/<pluginName>/...
+If a plugin does not register a filter or servlet to handle URLs
+`/Documentation/*` or `/static/*`, the core Gerrit server will
+automatically export these resources over HTTP from the plugin JAR.
+
+Static resources under `static/` directory in the JAR will be
+available as `/plugins/helloworld/static/resource`.
+
+Documentation files under `Documentation/` directory in the JAR
+will be available as `/plugins/helloworld/Documentation/resource`.
+
+Documentation may be written in
+link:http://daringfireball.net/projects/markdown/[Markdown] style
+if the file name ends with `.md`. Gerrit will automatically convert
+Markdown to HTML if accessed with extension `.html`.
Deployment
----------
-Deploy plugins into <review_site>/plugins/. The file name in that directory will
-be the plugin name on the server.
+Compiled plugins and extensions can be deployed to a
+running Gerrit server using the SSH interface by any user with
+link:access-control.html#capability_administrateServer[Administrate Server]
+capability. Binaries can be specified in three different formats:
+
+* Absolute file path on the server's host. The server will copy
+ the plugin from this location to its own site path.
++
+----
+$ ssh -P 29418 localhost gerrit plugin install -n name $(pwd)/my-plugin.jar
+----
+
+* Valid URL, including any HTTP or FTP site reachable by the
+ server. The server will download the plugin and save a copy in
+ its own site path.
++
+----
+$ ssh -P 29418 localhost gerrit plugin install -n name http://build-server/output/our-plugin.jar
+----
+
+* As piped input to the plugin install command. The server will
+ copy input until EOF, and save a copy under its own site path.
++
+----
+$ ssh -P 29418 localhost gerrit plugin install -n name - <target/name-0.1.jar
+----
+
+Plugins can also be copied directly into the server's
+directory at `$site_path/plugins/$name.jar`. The name of
+the JAR file, minus the `.jar` extension, will be used as the
+plugin name. Unless disabled, servers periodically scan this
+directory for updated plugins. The time can be adjusted by
+link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
GERRIT
------
diff --git a/gerrit-extension-api/pom.xml b/gerrit-extension-api/pom.xml
index 0209f3f..ff672d5 100644
--- a/gerrit-extension-api/pom.xml
+++ b/gerrit-extension-api/pom.xml
@@ -56,6 +56,8 @@
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<createSourcesJar>true</createSourcesJar>
+ <shadedArtifactAttached>true</shadedArtifactAttached>
+ <shadedClassifierName>all</shadedClassifierName>
</configuration>
<executions>
<execution>
diff --git a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
index 61bb52f..9cca559 100644
--- a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
+++ b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
@@ -31,9 +31,10 @@
import java.net.URLClassLoader;
import java.security.CodeSource;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.Enumeration;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
@@ -196,7 +197,7 @@
throw e;
}
- final ArrayList<URL> jars = new ArrayList<URL>();
+ final SortedMap<String, URL> jars = new TreeMap<String, URL>();
try {
final ZipFile zf = new ZipFile(path);
try {
@@ -208,6 +209,7 @@
}
if (ze.getName().startsWith("WEB-INF/lib/")) {
+ String name = ze.getName().substring("WEB-INF/lib/".length());
final File tmp = createTempFile(safeName(ze), ".jar");
final FileOutputStream out = new FileOutputStream(tmp);
try {
@@ -224,7 +226,7 @@
} finally {
out.close();
}
- jars.add(tmp.toURI().toURL());
+ jars.put(name, tmp.toURI().toURL());
}
}
} finally {
@@ -237,13 +239,38 @@
if (jars.isEmpty()) {
return GerritLauncher.class.getClassLoader();
}
- Collections.sort(jars, new Comparator<URL>() {
- public int compare(URL o1, URL o2) {
- return o1.toString().compareTo(o2.toString());
- }
- });
- return new URLClassLoader(jars.toArray(new URL[jars.size()]));
+ // The extension API needs to be its own ClassLoader, along
+ // with a few of its dependencies. Try to construct this first.
+ List<URL> extapi = new ArrayList<URL>();
+ move(jars, "gerrit-extension-api-", extapi);
+ move(jars, "guice-", extapi);
+ move(jars, "javax.inject-1.jar", extapi);
+ move(jars, "aopalliance-1.0.jar", extapi);
+ move(jars, "guice-servlet-", extapi);
+ move(jars, "servlet-api-", extapi);
+
+ ClassLoader parent = ClassLoader.getSystemClassLoader();
+ if (!extapi.isEmpty()) {
+ parent = new URLClassLoader(
+ extapi.toArray(new URL[extapi.size()]),
+ parent);
+ }
+ return new URLClassLoader(
+ jars.values().toArray(new URL[jars.size()]),
+ parent);
+ }
+
+ private static void move(SortedMap<String, URL> jars,
+ String prefix,
+ List<URL> extapi) {
+ SortedMap<String, URL> matches = jars.tailMap(prefix);
+ if (!matches.isEmpty()) {
+ String first = matches.firstKey();
+ if (first.startsWith(prefix)) {
+ extapi.add(jars.remove(first));
+ }
+ }
}
private static String safeName(final ZipEntry ze) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
index 88f3fc3..6181b67 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
@@ -20,6 +20,7 @@
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle;
+import com.google.gerrit.extensions.systemstatus.ServerInformation;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
@@ -40,6 +41,10 @@
import javax.annotation.Nullable;
public class Plugin {
+ public static enum ApiType {
+ EXTENSION, PLUGIN;
+ }
+
static {
// Guice logs warnings about multiple injectors being created.
// Silence this in case HTTP plugins are used.
@@ -47,12 +52,26 @@
.setLevel(java.util.logging.Level.OFF);
}
+ static ApiType getApiType(Manifest manifest) throws InvalidPluginException {
+ Attributes main = manifest.getMainAttributes();
+ String v = main.getValue("Gerrit-ApiType");
+ if (Strings.isNullOrEmpty(v)
+ || ApiType.EXTENSION.name().equalsIgnoreCase(v)) {
+ return ApiType.EXTENSION;
+ } else if (ApiType.PLUGIN.name().equalsIgnoreCase(v)) {
+ return ApiType.PLUGIN;
+ } else {
+ throw new InvalidPluginException("Invalid Gerrit-ApiType: " + v);
+ }
+ }
+
private final String name;
private final File srcJar;
private final FileSnapshot snapshot;
private final JarFile jarFile;
private final Manifest manifest;
private final File dataDir;
+ private final ApiType apiType;
private final ClassLoader classLoader;
private Class<? extends Module> sysModule;
private Class<? extends Module> sshModule;
@@ -70,6 +89,7 @@
JarFile jarFile,
Manifest manifest,
File dataDir,
+ ApiType apiType,
ClassLoader classLoader,
@Nullable Class<? extends Module> sysModule,
@Nullable Class<? extends Module> sshModule,
@@ -80,6 +100,7 @@
this.jarFile = jarFile;
this.manifest = manifest;
this.dataDir = dataDir;
+ this.apiType = apiType;
this.classLoader = classLoader;
this.sysModule = sysModule;
this.sshModule = sshModule;
@@ -94,11 +115,16 @@
return name;
}
+ @Nullable
public String getVersion() {
Attributes main = manifest.getMainAttributes();
return main.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
}
+ public ApiType getApiType() {
+ return apiType;
+ }
+
boolean canReload() {
Attributes main = manifest.getMainAttributes();
String v = main.getValue("Gerrit-ReloadMode");
@@ -139,29 +165,33 @@
}
if (env.hasSshModule()) {
+ List<Module> modules = Lists.newLinkedList();
+ if (apiType == ApiType.PLUGIN) {
+ modules.add(env.getSshModule());
+ }
if (sshModule != null) {
- sshInjector = sysInjector.createChildInjector(
- env.getSshModule(),
- sysInjector.getInstance(sshModule));
+ modules.add(sysInjector.getInstance(sshModule));
+ sshInjector = sysInjector.createChildInjector(modules);
manager.add(sshInjector);
} else if (auto != null && auto.sshModule != null) {
- sshInjector = sysInjector.createChildInjector(
- env.getSshModule(),
- auto.sshModule);
+ modules.add(auto.sshModule);
+ sshInjector = sysInjector.createChildInjector(modules);
manager.add(sshInjector);
}
}
if (env.hasHttpModule()) {
+ List<Module> modules = Lists.newLinkedList();
+ if (apiType == ApiType.PLUGIN) {
+ modules.add(env.getHttpModule());
+ }
if (httpModule != null) {
- httpInjector = sysInjector.createChildInjector(
- env.getHttpModule(),
- sysInjector.getInstance(httpModule));
+ modules.add(sysInjector.getInstance(httpModule));
+ httpInjector = sysInjector.createChildInjector(modules);
manager.add(httpInjector);
} else if (auto != null && auto.httpModule != null) {
- httpInjector = sysInjector.createChildInjector(
- env.getHttpModule(),
- auto.httpModule);
+ modules.add(auto.httpModule);
+ httpInjector = sysInjector.createChildInjector(modules);
manager.add(httpInjector);
}
}
@@ -169,9 +199,19 @@
manager.start();
}
- private Injector newRootInjector(PluginGuiceEnvironment env) {
+ private Injector newRootInjector(final PluginGuiceEnvironment env) {
List<Module> modules = Lists.newArrayListWithCapacity(4);
modules.add(env.getSysModule());
+ if (apiType == ApiType.PLUGIN) {
+ modules.add(env.getSysModule());
+ } else {
+ modules.add(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(ServerInformation.class).toInstance(env.getServerInformation());
+ }
+ });
+ }
modules.add(new AbstractModule() {
@Override
protected void configure() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
index 4842edc..67d715f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -19,6 +19,7 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.systemstatus.ServerInformation;
import com.google.gerrit.server.config.ConfigUtil;
@@ -340,7 +341,7 @@
}
private Plugin loadPlugin(String name, File srcJar, FileSnapshot snapshot)
- throws IOException, ClassNotFoundException {
+ throws IOException, ClassNotFoundException, InvalidPluginException {
File tmp;
FileInputStream in = new FileInputStream(srcJar);
try {
@@ -353,13 +354,20 @@
boolean keep = false;
try {
Manifest manifest = jarFile.getManifest();
+ Plugin.ApiType type = Plugin.getApiType(manifest);
Attributes main = manifest.getMainAttributes();
String sysName = main.getValue("Gerrit-Module");
String sshName = main.getValue("Gerrit-SshModule");
String httpName = main.getValue("Gerrit-HttpModule");
+ if (!Strings.isNullOrEmpty(sshName) && type != Plugin.ApiType.PLUGIN) {
+ throw new InvalidPluginException(String.format(
+ "Using Gerrit-SshModule requires Gerrit-ApiType: %s",
+ Plugin.ApiType.PLUGIN));
+ }
+
URL[] urls = {tmp.toURI().toURL()};
- ClassLoader parentLoader = PluginLoader.class.getClassLoader();
+ ClassLoader parentLoader = parentFor(type);
ClassLoader pluginLoader = new URLClassLoader(urls, parentLoader);
cleanupHandles.put(
new CleanupHandle(tmp, jarFile, pluginLoader, cleanupQueue),
@@ -372,7 +380,7 @@
return new Plugin(name,
srcJar, snapshot,
jarFile, manifest,
- new File(dataDir, name), pluginLoader,
+ new File(dataDir, name), type, pluginLoader,
sysModule, sshModule, httpModule);
} finally {
if (!keep) {
@@ -381,6 +389,18 @@
}
}
+ private static ClassLoader parentFor(Plugin.ApiType type)
+ throws InvalidPluginException {
+ switch (type) {
+ case EXTENSION:
+ return PluginName.class.getClassLoader();
+ case PLUGIN:
+ return PluginLoader.class.getClassLoader();
+ default:
+ throw new InvalidPluginException("Unsupported ApiType " + type);
+ }
+ }
+
private static String tempNameFor(String name) {
SimpleDateFormat fmt = new SimpleDateFormat("yyMMdd_HHmm");
return "plugin_" + name + "_" + fmt.format(new Date()) + "_";
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/PluginCommandModule.java
similarity index 97%
rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginCommandModule.java
rename to gerrit-sshd/src/main/java/com/google/gerrit/sshd/PluginCommandModule.java
index 28d267c..4dbb8d7 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/PluginCommandModule.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.sshd.commands;
+package com.google.gerrit.sshd;
import com.google.common.base.Preconditions;
import com.google.gerrit.extensions.annotations.PluginName;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java
index b843893..03485f7 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java
@@ -69,6 +69,6 @@
@Override
public Module create() throws InvalidPluginException {
Preconditions.checkState(command != null, "pluginName must be provided");
- return this;
+ return !commands.isEmpty() ? this : null;
}
}
diff --git a/tools/deploy_api.sh b/tools/deploy_api.sh
index eda841f..caa286b 100755
--- a/tools/deploy_api.sh
+++ b/tools/deploy_api.sh
@@ -15,25 +15,44 @@
esac
URL=s3://gerrit-api@commondatastorage.googleapis.com/$type
-echo "Deploying API $VER to $URL"
-for module in gerrit-extension-api gerrit-plugin-api
-do
- mvn deploy:deploy-file \
- -DgroupId=com.google.gerrit \
- -DartifactId=$module \
- -Dversion=$VER \
- -Dpackaging=jar \
- -Dfile=$module/target/$module-$VER.jar \
- -DrepositoryId=gerrit-api-repository \
- -Durl=$URL
- mvn deploy:deploy-file \
- -DgroupId=com.google.gerrit \
- -DartifactId=$module \
- -Dversion=$VER \
- -Dpackaging=java-source \
- -Dfile=$module/target/$module-$VER-sources.jar \
- -Djava-source=false \
- -DrepositoryId=gerrit-api-repository \
- -Durl=$URL
-done
+echo "Deploying $type gerrit-extension-api $VER"
+mvn deploy:deploy-file \
+ -DgroupId=com.google.gerrit \
+ -DartifactId=gerrit-extension-api \
+ -Dversion=$VER \
+ -Dpackaging=jar \
+ -Dfile=$module/target/gerrit-extension-api-$VER-all.jar \
+ -DrepositoryId=gerrit-api-repository \
+ -Durl=$URL
+
+mvn deploy:deploy-file \
+ -DgroupId=com.google.gerrit \
+ -DartifactId=gerrit-extension-api \
+ -Dversion=$VER \
+ -Dpackaging=java-source \
+ -Dfile=$module/target/gerrit-extension-api-$VER-all-sources.jar \
+ -Djava-source=false \
+ -DrepositoryId=gerrit-api-repository \
+ -Durl=$URL
+
+
+echo "Deploying $type gerrit-plugin-api $VER"
+mvn deploy:deploy-file \
+ -DgroupId=com.google.gerrit \
+ -DartifactId=gerrit-plugin-api \
+ -Dversion=$VER \
+ -Dpackaging=jar \
+ -Dfile=$module/target/gerrit-plugin-api-$VER.jar \
+ -DrepositoryId=gerrit-api-repository \
+ -Durl=$URL
+
+mvn deploy:deploy-file \
+ -DgroupId=com.google.gerrit \
+ -DartifactId=gerrit-plugin-api \
+ -Dversion=$VER \
+ -Dpackaging=java-source \
+ -Dfile=$module/target/gerrit-plugin-api-$VER-sources.jar \
+ -Djava-source=false \
+ -DrepositoryId=gerrit-api-repository \
+ -Durl=$URL