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