/*
* Copyright 2014-present Facebook, Inc.
*
* 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.facebook.buck.gwt;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.jvm.java.JavaLibrary;
import com.facebook.buck.jvm.java.JavaRuntimeLauncher;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.rules.AbstractBuildRule;
import com.facebook.buck.rules.AddToRuleKey;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildableContext;
import com.facebook.buck.rules.ExplicitBuildTargetSourcePath;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.shell.ShellStep;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.fs.MakeCleanDirectoryStep;
import com.facebook.buck.step.fs.MkdirStep;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import java.io.File;
import java.nio.file.Path;
import java.util.List;
/**
* Buildable that produces a GWT application as a WAR file, which is a zip of the outputs produced
* by the GWT compiler.
*/
public class GwtBinary extends AbstractBuildRule {
/**
* Valid values for the GWT Compiler's {@code -style} flag. Acceptable values are defined in the
* GWT docs at http://bit.ly/1sclx5O.
*/
enum Style {
/** Named "obf" for "obfuscated". This is the default style. */
OBF,
PRETTY,
DETAILED,
;
}
private static final String GWT_COMPILER_CLASS = "com.google.gwt.dev.Compiler";
private final Path outputFile;
@AddToRuleKey private final ImmutableSortedSet<String> modules;
@AddToRuleKey private final JavaRuntimeLauncher javaRuntimeLauncher;
@AddToRuleKey private final ImmutableList<String> vmArgs;
@AddToRuleKey private final Style style;
@AddToRuleKey private final boolean draftCompile;
@AddToRuleKey private final int optimize;
@AddToRuleKey private final int localWorkers;
@AddToRuleKey private final boolean strict;
@AddToRuleKey private final ImmutableList<String> experimentalArgs;
// Deliberately not added to rule key
private final ImmutableSortedSet<SourcePath> gwtModuleJars;
/** @param modules The GWT modules to build with the GWT compiler. */
GwtBinary(
BuildRuleParams buildRuleParams,
ImmutableSortedSet<String> modules,
JavaRuntimeLauncher javaRuntimeLauncher,
List<String> vmArgs,
Style style,
boolean draftCompile,
int optimize,
int localWorkers,
boolean strict,
List<String> experimentalArgs,
ImmutableSortedSet<SourcePath> gwtModuleJars) {
super(buildRuleParams);
BuildTarget buildTarget = buildRuleParams.getBuildTarget();
this.outputFile =
BuildTargets.getGenPath(
buildRuleParams.getProjectFilesystem(),
buildTarget,
"__gwt_binary_%s__/" + buildTarget.getShortNameAndFlavorPostfix() + ".zip");
this.modules = modules;
Preconditions.checkArgument(
!modules.isEmpty(), "Must specify at least one module for %s.", buildTarget);
this.javaRuntimeLauncher = javaRuntimeLauncher;
this.vmArgs = ImmutableList.copyOf(vmArgs);
this.style = style;
this.draftCompile = draftCompile;
// No need for bounds-checking this int because GWT does it internally: http://bit.ly/1kFN5M7.
this.optimize = optimize;
Preconditions.checkArgument(
localWorkers > 0, "localWorkers must be greater than zero: %d", localWorkers);
this.localWorkers = localWorkers;
this.strict = strict;
this.experimentalArgs = ImmutableList.copyOf(experimentalArgs);
this.gwtModuleJars = gwtModuleJars;
}
@Override
public ImmutableList<Step> getBuildSteps(
BuildContext context, BuildableContext buildableContext) {
ImmutableList.Builder<Step> steps = ImmutableList.builder();
// Create a clean directory where the .zip file will be written.
Path workingDirectory =
context.getSourcePathResolver().getRelativePath(getSourcePathToOutput()).getParent();
ProjectFilesystem projectFilesystem = getProjectFilesystem();
steps.addAll(MakeCleanDirectoryStep.of(projectFilesystem, workingDirectory));
// Write the deploy files into a separate directory so that the generated .zip is smaller.
final Path deployDirectory = workingDirectory.resolve("deploy");
steps.add(MkdirStep.of(projectFilesystem, deployDirectory));
Step javaStep =
new ShellStep(projectFilesystem.getRootPath()) {
@Override
public String getShortName() {
return "gwt-compile";
}
@Override
protected ImmutableList<String> getShellCommandInternal(
ExecutionContext executionContext) {
ImmutableList.Builder<String> javaArgsBuilder = ImmutableList.builder();
javaArgsBuilder.add(javaRuntimeLauncher.getCommand());
javaArgsBuilder.add("-Dgwt.normalizeTimestamps=true");
javaArgsBuilder.addAll(vmArgs);
javaArgsBuilder.add(
"-classpath",
Joiner.on(File.pathSeparator)
.join(
Iterables.transform(
getClasspathEntries(context.getSourcePathResolver()),
getProjectFilesystem()::resolve)),
GWT_COMPILER_CLASS,
"-war",
context.getSourcePathResolver().getAbsolutePath(getSourcePathToOutput()).toString(),
"-style",
style.name(),
"-optimize",
String.valueOf(optimize),
"-localWorkers",
String.valueOf(localWorkers),
"-deploy",
getProjectFilesystem().resolve(deployDirectory).toString());
if (draftCompile) {
javaArgsBuilder.add("-draftCompile");
}
if (strict) {
javaArgsBuilder.add("-strict");
}
javaArgsBuilder.addAll(experimentalArgs);
javaArgsBuilder.addAll(modules);
final ImmutableList<String> javaArgs = javaArgsBuilder.build();
return javaArgs;
}
};
steps.add(javaStep);
buildableContext.recordArtifact(
context.getSourcePathResolver().getRelativePath(getSourcePathToOutput()));
return steps.build();
}
/** @return The {@code .zip} file produced by this rule. */
@Override
public SourcePath getSourcePathToOutput() {
return new ExplicitBuildTargetSourcePath(getBuildTarget(), outputFile);
}
/**
* The classpath entries needed to run {@code com.google.gwt.dev.Compiler} to build the module
* specified by {@link #modules}.
*/
@VisibleForTesting
Iterable<Path> getClasspathEntries(SourcePathResolver pathResolver) {
ImmutableSet.Builder<Path> classpathEntries = ImmutableSet.builder();
classpathEntries.addAll(pathResolver.getAllAbsolutePaths(gwtModuleJars));
for (BuildRule dep : getDeclaredDeps()) {
if (!(dep instanceof JavaLibrary)) {
continue;
}
JavaLibrary javaLibrary = (JavaLibrary) dep;
for (SourcePath path : javaLibrary.getOutputClasspaths()) {
classpathEntries.add(pathResolver.getAbsolutePath(path));
}
}
return classpathEntries.build();
}
}