diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java index 769396eca7..05992d4b40 100644 --- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java +++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java @@ -338,7 +338,7 @@ public class WebAppInitializer extends GuiceServletContextListener implements Fi new AbstractModule() { @Override protected void configure() { - bind(GerritOptions.class).toInstance(new GerritOptions(false, false, false)); + bind(GerritOptions.class).toInstance(new GerritOptions(false, false, "")); bind(GerritRuntime.class).toInstance(GerritRuntime.DAEMON); } }); diff --git a/java/com/google/gerrit/httpd/raw/BazelBuild.java b/java/com/google/gerrit/httpd/raw/BazelBuild.java deleted file mode 100644 index 7677e97458..0000000000 --- a/java/com/google/gerrit/httpd/raw/BazelBuild.java +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (C) 2016 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.httpd.raw; - -import static com.google.common.base.MoreObjects.firstNonNull; -import static java.nio.charset.StandardCharsets.UTF_8; - -import com.google.common.base.Joiner; -import com.google.common.escape.Escaper; -import com.google.common.flogger.FluentLogger; -import com.google.common.html.HtmlEscapers; -import com.google.common.io.ByteStreams; -import com.google.gerrit.launcher.GerritLauncher; -import com.google.gerrit.server.util.time.TimeUtil; -import com.google.gerrit.util.http.CacheHeaders; -import java.io.IOException; -import java.io.InputStream; -import java.io.InterruptedIOException; -import java.io.PrintWriter; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; -import javax.servlet.http.HttpServletResponse; -import org.eclipse.jgit.util.RawParseUtils; - -public class BazelBuild { - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - - private final Path sourceRoot; - - public BazelBuild(Path sourceRoot) { - this.sourceRoot = sourceRoot; - } - - // builds the given label. - public void build(Label label) throws IOException, BuildFailureException { - ProcessBuilder proc = newBuildProcess(label); - proc.directory(sourceRoot.toFile()).redirectErrorStream(true); - logger.atInfo().log("building %s", label.fullName()); - long start = TimeUtil.nowMs(); - Process rebuild = proc.start(); - byte[] out; - try (InputStream in = rebuild.getInputStream()) { - out = ByteStreams.toByteArray(in); - } finally { - rebuild.getOutputStream().close(); - } - - int status; - try { - status = rebuild.waitFor(); - } catch (InterruptedException e) { - String msg = "interrupted waiting for: " + Joiner.on(' ').join(proc.command()); - logger.atSevere().withCause(e).log(msg); - throw new InterruptedIOException(msg); - } - if (status != 0) { - logger.atWarning().log("build failed: %s", new String(out, UTF_8)); - throw new BuildFailureException(out); - } - - long time = TimeUtil.nowMs() - start; - logger.atInfo().log("UPDATED %s in %.3fs", label.fullName(), time / 1000.0); - } - - // Represents a label in bazel. - static class Label { - protected final String pkg; - protected final String name; - - public String fullName() { - return "//" + pkg + ":" + name; - } - - @Override - public String toString() { - return fullName(); - } - - // Label in Bazel style. - Label(String pkg, String name) { - this.name = name; - this.pkg = pkg; - } - } - - static class BuildFailureException extends Exception { - private static final long serialVersionUID = 1L; - - final byte[] why; - - BuildFailureException(byte[] why) { - this.why = why; - } - - public void display(String rule, HttpServletResponse res) throws IOException { - res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - res.setContentType("text/html"); - res.setCharacterEncoding(UTF_8.name()); - CacheHeaders.setNotCacheable(res); - - Escaper html = HtmlEscapers.htmlEscaper(); - try (PrintWriter w = res.getWriter()) { - w.write("BUILD FAILED"); - w.format("

%s FAILED

", html.escape(rule)); - w.write("
");
-        w.write(html.escape(RawParseUtils.decode(why)));
-        w.write("
"); - w.write(""); - } - } - } - - private ProcessBuilder newBuildProcess(Label label) throws IOException { - Properties properties = GerritLauncher.loadBuildProperties(sourceRoot.resolve(".bazel_path")); - String bazel = firstNonNull(properties.getProperty("bazel"), "bazel"); - List cmd = new ArrayList<>(); - cmd.add(bazel); - cmd.add("build"); - if (GerritLauncher.isJdk9OrLater()) { - String v = GerritLauncher.getJdkVersionPostJdk8(); - cmd.add("--host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java" + v); - cmd.add("--java_toolchain=@bazel_tools//tools/jdk:toolchain_java" + v); - } - cmd.add(label.fullName()); - ProcessBuilder proc = new ProcessBuilder(cmd); - if (properties.containsKey("PATH")) { - proc.environment().put("PATH", properties.getProperty("PATH")); - } - return proc; - } - - /** returns the root relative path to the artifact for the given label */ - public Path targetPath(Label l) { - return sourceRoot.resolve("bazel-bin").resolve(l.pkg).resolve(l.name); - } - - /** Label for the polygerrit component zip. */ - public Label polygerritComponents() { - return new Label("polygerrit-ui", "polygerrit_components.bower_components.zip"); - } - - /** Label for the fonts zip file. */ - public Label fontZipLabel() { - return new Label("polygerrit-ui", "fonts.zip"); - } -} diff --git a/java/com/google/gerrit/httpd/raw/BowerComponentsDevServlet.java b/java/com/google/gerrit/httpd/raw/BowerComponentsDevServlet.java deleted file mode 100644 index 1be304590d..0000000000 --- a/java/com/google/gerrit/httpd/raw/BowerComponentsDevServlet.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (C) 2015 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.httpd.raw; - -import com.google.common.cache.Cache; -import com.google.gerrit.launcher.GerritLauncher; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Objects; - -/* Bower component servlet only used in development mode */ -class BowerComponentsDevServlet extends ResourceServlet { - private static final long serialVersionUID = 1L; - - private final Path bowerComponents; - private final Path zip; - - BowerComponentsDevServlet(Cache cache, BazelBuild builder) throws IOException { - super(cache, true); - - Objects.requireNonNull(builder); - BazelBuild.Label label = builder.polygerritComponents(); - try { - builder.build(label); - } catch (BazelBuild.BuildFailureException e) { - throw new IOException(e); - } - - zip = builder.targetPath(label); - bowerComponents = GerritLauncher.newZipFileSystem(zip).getPath("/"); - } - - @Override - protected Path getResourcePath(String pathInfo) throws IOException { - return bowerComponents.resolve(pathInfo); - } -} diff --git a/java/com/google/gerrit/httpd/raw/FontsDevServlet.java b/java/com/google/gerrit/httpd/raw/FontsDevServlet.java deleted file mode 100644 index 68b0d8ceb1..0000000000 --- a/java/com/google/gerrit/httpd/raw/FontsDevServlet.java +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (C) 2016 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.httpd.raw; - -import com.google.common.cache.Cache; -import com.google.gerrit.launcher.GerritLauncher; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Objects; - -/* Font servlet only used in development mode */ -class FontsDevServlet extends ResourceServlet { - private static final long serialVersionUID = 1L; - - private final Path fonts; - - FontsDevServlet(Cache cache, BazelBuild builder) throws IOException { - super(cache, true); - Objects.requireNonNull(builder); - - BazelBuild.Label zipLabel = builder.fontZipLabel(); - try { - builder.build(zipLabel); - } catch (BazelBuild.BuildFailureException e) { - throw new IOException(e); - } - - Path zip = builder.targetPath(zipLabel); - Objects.requireNonNull(zip); - - fonts = GerritLauncher.newZipFileSystem(zip).getPath("/"); - } - - @Override - protected Path getResourcePath(String pathInfo) throws IOException { - return fonts.resolve(pathInfo); - } -} diff --git a/java/com/google/gerrit/httpd/raw/StaticModule.java b/java/com/google/gerrit/httpd/raw/StaticModule.java index 0d4c67e8ee..c91048c91f 100644 --- a/java/com/google/gerrit/httpd/raw/StaticModule.java +++ b/java/com/google/gerrit/httpd/raw/StaticModule.java @@ -14,7 +14,6 @@ package com.google.gerrit.httpd.raw; -import static com.google.common.base.Preconditions.checkArgument; import static java.nio.file.Files.exists; import static java.nio.file.Files.isReadable; @@ -222,7 +221,8 @@ public class StaticModule extends ServletModule { @CanonicalWebUrl @Nullable String canonicalUrl, @GerritServerConfig Config cfg, GerritApi gerritApi) { - String cdnPath = cfg.getString("gerrit", null, "cdnPath"); + String cdnPath = + options.useDevCdn() ? options.devCdn() : cfg.getString("gerrit", null, "cdnPath"); String faviconPath = cfg.getString("gerrit", null, "faviconPath"); return new IndexServlet(canonicalUrl, cdnPath, faviconPath, gerritApi); } @@ -233,29 +233,8 @@ public class StaticModule extends ServletModule { return new PolyGerritUiServlet(cache, polyGerritBasePath()); } - @Provides - @Singleton - BowerComponentsDevServlet getBowerComponentsServlet(@Named(CACHE) Cache cache) - throws IOException { - return getPaths().isDev() ? new BowerComponentsDevServlet(cache, getPaths().builder) : null; - } - - @Provides - @Singleton - FontsDevServlet getFontsServlet(@Named(CACHE) Cache cache) throws IOException { - return getPaths().isDev() ? new FontsDevServlet(cache, getPaths().builder) : null; - } - private Path polyGerritBasePath() { Paths p = getPaths(); - if (options.forcePolyGerritDev()) { - checkArgument( - p.sourceRoot != null, "no source root directory found for PolyGerrit developer mode"); - } - - if (p.isDev()) { - return p.sourceRoot.resolve("polygerrit-ui").resolve("app"); - } return p.warFs != null ? p.warFs.getPath("/polygerrit_ui") @@ -265,7 +244,6 @@ public class StaticModule extends ServletModule { private static class Paths { private final FileSystem warFs; - private final BazelBuild builder; private final Path sourceRoot; private final Path unpackedWar; private final boolean development; @@ -285,21 +263,19 @@ public class StaticModule extends ServletModule { launcherLoadedFrom.getParentFile().getParentFile().getParentFile().toURI()); sourceRoot = null; development = false; - builder = null; return; } warFs = getDistributionArchive(launcherLoadedFrom); if (warFs == null) { unpackedWar = makeWarTempDir(); development = true; - } else if (options.forcePolyGerritDev()) { + } else if (options.useDevCdn()) { unpackedWar = null; development = true; } else { unpackedWar = null; development = false; sourceRoot = null; - builder = null; return; } } catch (IOException e) { @@ -307,7 +283,6 @@ public class StaticModule extends ServletModule { } sourceRoot = getSourceRootOrNull(); - builder = new BazelBuild(sourceRoot); } private static Path getSourceRootOrNull() { @@ -376,21 +351,15 @@ public class StaticModule extends ServletModule { private final Paths paths; private final HttpServlet polyGerritIndex; private final PolyGerritUiServlet polygerritUI; - private final BowerComponentsDevServlet bowerComponentServlet; - private final FontsDevServlet fontServlet; @Inject PolyGerritFilter( Paths paths, @Named(POLYGERRIT_INDEX_SERVLET) HttpServlet polyGerritIndex, - PolyGerritUiServlet polygerritUI, - @Nullable BowerComponentsDevServlet bowerComponentServlet, - @Nullable FontsDevServlet fontServlet) { + PolyGerritUiServlet polygerritUI) { this.paths = paths; this.polyGerritIndex = polyGerritIndex; this.polygerritUI = polygerritUI; - this.bowerComponentServlet = bowerComponentServlet; - this.fontServlet = fontServlet; } @Override @@ -408,22 +377,6 @@ public class StaticModule extends ServletModule { GuiceFilterRequestWrapper reqWrapper = new GuiceFilterRequestWrapper(req); String path = pathInfo(req); - // Special case assets during development that are built by Bazel and not - // served out of the source tree. - // - // In the war case, these are either inlined, or live under - // /polygerrit_ui in the war file, so we can just treat them as normal - // assets. - if (paths.isDev()) { - if (path.startsWith("/bower_components/")) { - bowerComponentServlet.service(reqWrapper, res); - return; - } else if (path.startsWith("/fonts/")) { - fontServlet.service(reqWrapper, res); - return; - } - } - if (isPolyGerritIndex(path)) { polyGerritIndex.service(reqWrapper, res); return; diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java index 2e9ef2f0b4..8905ee5ea1 100644 --- a/java/com/google/gerrit/pgm/Daemon.java +++ b/java/com/google/gerrit/pgm/Daemon.java @@ -168,8 +168,18 @@ public class Daemon extends SiteProgram { @Option(name = "--headless", usage = "Don't start the UI frontend") private boolean headless; - @Option(name = "--polygerrit-dev", usage = "Force PolyGerrit UI for development") - private boolean polyGerritDev; + private String devCdn = ""; + + @Option(name = "--dev-cdn", usage = "Use specified cdn for serving static content.") + private void setDevCdn(String cdn) { + if (cdn == null) { + cdn = ""; + } + if (cdn.endsWith("/")) { + cdn = cdn.substring(0, cdn.length() - 1); + } + devCdn = cdn; + } @Option( name = "--init", @@ -463,8 +473,7 @@ public class Daemon extends SiteProgram { new AbstractModule() { @Override protected void configure() { - bind(GerritOptions.class) - .toInstance(new GerritOptions(headless, replica, polyGerritDev)); + bind(GerritOptions.class).toInstance(new GerritOptions(headless, replica, devCdn)); if (inMemoryTest) { bind(String.class) .annotatedWith(SecureStoreClassName.class) diff --git a/java/com/google/gerrit/server/config/GerritOptions.java b/java/com/google/gerrit/server/config/GerritOptions.java index 17b65c9f21..d9edf23d27 100644 --- a/java/com/google/gerrit/server/config/GerritOptions.java +++ b/java/com/google/gerrit/server/config/GerritOptions.java @@ -17,12 +17,12 @@ package com.google.gerrit.server.config; public class GerritOptions { private final boolean headless; private final boolean slave; - private final boolean forcePolyGerritDev; + private final String devCdn; - public GerritOptions(boolean headless, boolean slave, boolean forcePolyGerritDev) { + public GerritOptions(boolean headless, boolean slave, String devCdn) { this.headless = headless; this.slave = slave; - this.forcePolyGerritDev = forcePolyGerritDev; + this.devCdn = devCdn; } public boolean headless() { @@ -33,7 +33,11 @@ public class GerritOptions { return !slave; } - public boolean forcePolyGerritDev() { - return !headless && forcePolyGerritDev; + public String devCdn() { + return devCdn; + } + + public boolean useDevCdn() { + return !headless && devCdn.length() > 0; } } diff --git a/java/com/google/gerrit/testing/InMemoryModule.java b/java/com/google/gerrit/testing/InMemoryModule.java index fa9685e7fd..af9d8c3925 100644 --- a/java/com/google/gerrit/testing/InMemoryModule.java +++ b/java/com/google/gerrit/testing/InMemoryModule.java @@ -181,7 +181,7 @@ public class InMemoryModule extends FactoryModule { // support Path-based Configs, only FileBasedConfig. bind(Path.class).annotatedWith(SitePath.class).toInstance(Paths.get(".")); bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(cfg); - bind(GerritOptions.class).toInstance(new GerritOptions(false, false, false)); + bind(GerritOptions.class).toInstance(new GerritOptions(false, false, "")); bind(GitRepositoryManager.class).to(InMemoryRepositoryManager.class); bind(InMemoryRepositoryManager.class).in(SINGLETON); diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md index a1e960f1cb..acc5d0d445 100644 --- a/polygerrit-ui/README.md +++ b/polygerrit-ui/README.md @@ -39,12 +39,11 @@ npm install It may complain about a missing `typescript@2.3.4` peer dependency, which is harmless. -## Running locally against production data +## Serving files locally #### Go server -To test the local Polymer frontend against gerrit-review.googlesource.com -simply execute: +To test the local Polymer frontend against production data or a local test site execute: ```sh ./polygerrit-ui/run-server.sh @@ -53,10 +52,7 @@ simply execute: npm run start ``` -Then visit http://localhost:8081 - -This method is based on a -[simple hand-written Go webserver](https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/server.go). +These commands start the [simple hand-written Go webserver](https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/server.go). Mostly it just switches between serving files locally and proxying the real server based on the file name. It also does some basic response rewriting, e.g. it patches the `config/server/info` response with plugin information provided on @@ -66,6 +62,12 @@ the command line: ./polygerrit-ui/run-server.sh --plugins=plugins/my_plugin/static/my_plugin.js,plugins/my_plugin/static/my_plugin.html ``` +## Running locally against production data + +### Local website + +Start [Go server](#go-server) and then visit http://localhost:8081 + The biggest draw back of this method is that you cannot log in, so cannot test scenarios that require it. @@ -89,9 +91,11 @@ Set up a local test site once: 3. Optionally [populate](https://gerrit.googlesource.com/gerrit/+/master/contrib/populate-fixture-data.py) your test site with some test data. For running a locally built Gerrit war against your test instance use -[this command](https://gerrit-review.googlesource.com/Documentation/dev-readme.html#run_daemon), -and add the `--polygerrit-dev` option, if you want to serve the Polymer frontend -directly from the sources in `polygerrit_ui/app/` instead of from the war: +[this command](https://gerrit-review.googlesource.com/Documentation/dev-readme.html#run_daemon). + +If you want to serve the Polymer frontend directly from the sources in `polygerrit_ui/app/` instead of from the war: +1. Start [Go server](#go-server) +2. Add the `--dev-cdn` option: ```sh $(bazel info output_base)/external/local_jdk/bin/java \ @@ -99,9 +103,11 @@ $(bazel info output_base)/external/local_jdk/bin/java \ -jar bazel-bin/gerrit.war daemon \ -d $GERRIT_SITE \ --console-log \ - --polygerrit-dev + --dev-cdn http://localhost:8081 ``` +*NOTE* You can use any other cdn here, for example: https://cdn.googlesource.com/polygerrit_ui/678.0 + ## Running Tests This step requires the `web-component-tester` npm module. diff --git a/polygerrit-ui/server.go b/polygerrit-ui/server.go index ba52ce83c1..5b48785777 100644 --- a/polygerrit-ui/server.go +++ b/polygerrit-ui/server.go @@ -64,11 +64,12 @@ func main() { log.Fatal(err) } - http.Handle("/", http.FileServer(http.Dir("app"))) + http.Handle("/", addDevHeadersMiddleware(http.FileServer(http.Dir("app")))) http.Handle("/bower_components/", - http.FileServer(httpfs.New(zipfs.New(componentsArchive, "bower_components")))) + addDevHeadersMiddleware( + http.FileServer(httpfs.New(zipfs.New(componentsArchive, "bower_components"))))) http.Handle("/fonts/", - http.FileServer(httpfs.New(zipfs.New(fontsArchive, "fonts")))) + addDevHeadersMiddleware(http.FileServer(httpfs.New(zipfs.New(fontsArchive, "fonts"))))) http.HandleFunc("/index.html", handleIndex) http.HandleFunc("/changes/", handleProxy) @@ -92,6 +93,14 @@ func main() { log.Fatal(http.ListenAndServe(*port, &server{})) } +func addDevHeadersMiddleware(h http.Handler) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) { + writer.Header().Set("Access-Control-Allow-Origin", "*") + writer.Header().Set("Cache-Control", "public, max-age=10, must-revalidate") + h.ServeHTTP(writer, req) + }) +} + func openDataArchive(path string) (*zip.ReadCloser, error) { absBinPath, err := resourceBasePath() if err != nil {