/*
* Copyright 2012 Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.dart.tools.core.dart2js;
import com.google.dart.tools.core.DartCore;
import com.google.dart.tools.core.MessageConsole;
import com.google.dart.tools.core.internal.util.ResourceUtil;
import com.google.dart.tools.core.model.DartSdkManager;
import com.google.dart.tools.core.pub.IPackageRootProvider;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.util.NLS;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Launch the dart2js process and collect stdout, stderr, and exit code information.
*
* @coverage dart.tools.core.dart2js
*/
public class Dart2JSCompiler {
public static class CompilationResult {
private ProcessRunner runner;
private IPath outputPath;
CompilationResult(ProcessRunner runner, IPath outputPath) {
this.runner = runner;
this.outputPath = outputPath;
}
public String getAllOutput() {
StringBuilder builder = new StringBuilder();
if (!getStdOut().isEmpty()) {
builder.append(getStdOut().trim() + "\n");
}
if (!getStdErr().isEmpty()) {
builder.append(getStdErr());
}
return builder.toString().trim();
}
public int getExitCode() {
return runner.getExitCode();
}
public IPath getOutputPath() {
return outputPath;
}
public String getStdErr() {
return runner.getStdErr();
}
public String getStdOut() {
return runner.getStdOut();
}
@Override
public String toString() {
return "dart2js result=" + getExitCode();
}
}
/**
* A static utility method to handle the common use case for the Dart2JSCompiler class. Compile
* the given dart library, optionally poll the given monitor to check for user cancellation, and
* write any output to the given console.
*
* @param file
* @param monitor
* @param console
* @throws OperationCanceledException
*/
public static CompilationResult compileLibrary(IFile file, IProgressMonitor monitor,
final MessageConsole console) throws CoreException {
return compileLibrary(file, null, monitor, console);
}
/**
* A static utility method to handle the common use case for the Dart2JSCompiler class. Compile
* the given dart library with the specified flags, optionally poll the given monitor to check for
* user cancellation, and write any output to the given console.
*
* @param file
* @param compilerFlags
* @param monitor
* @param console
* @throws OperationCanceledException
*/
public static CompilationResult compileLibrary(IFile file, String[] compilerFlags,
IProgressMonitor monitor, final MessageConsole console) throws CoreException {
long startTime = System.currentTimeMillis();
IPath path = file.getLocation();
final IPath inputPath = file.getLocation();
final IPath outputPath = getJsAppArtifactPath(path);
Dart2JSCompiler compiler = new Dart2JSCompiler();
if (compilerFlags != null && compilerFlags.length > 0) {
console.printSeparator("Running dart2js " + printFlags(compilerFlags));
} else {
console.printSeparator("Running dart2js...");
}
try {
CompilationResult result = compiler.compile(
inputPath,
outputPath,
compilerFlags,
monitor,
console);
refreshResources(file);
displayCompilationResult(compiler, result, outputPath, startTime, console);
return result;
} catch (IOException ioe) {
throw new CoreException(new Status(IStatus.ERROR, DartCore.PLUGIN_ID, ioe.toString(), ioe));
}
}
/**
* Answer the JavaScript application file for the specified source.
*
* @param source the application source file (not <code>null</code>)
* @return the application file (may not exist)
*/
public static File getJsAppArtifactFile(IPath sourceLocation) {
return sourceLocation.addFileExtension(DartCore.EXTENSION_JS).toFile();
}
/**
* Answer the JavaScript application file for the specified source.
*
* @param source the application source file (not <code>null</code>)
* @return the application file (may not exist)
*/
public static File getJsAppArtifactFile(IResource source) {
return getJsAppArtifactFile(source.getLocation());
}
/**
* Answer the JavaScript application file path for the specified source.
*
* @param source the application source file (not <code>null</code>)
* @return the application file path (may not exist)
*/
public static IPath getJsAppArtifactPath(IPath libraryPath) {
return Path.fromOSString(getJsAppArtifactFile(libraryPath).getAbsolutePath());
}
private static void displayCompilationResult(Dart2JSCompiler compiler, CompilationResult result,
IPath outputPath, long startTime, MessageConsole console) {
StringBuilder builder = new StringBuilder();
if (!result.getStdOut().isEmpty()) {
builder.append(result.getStdOut().trim() + "\n");
}
if (!result.getStdErr().isEmpty()) {
builder.append(result.getStdErr().trim() + "\n");
}
if (result.getExitCode() == 0) {
long elapsed = System.currentTimeMillis() - startTime;
// Trim to 1/10th of a second.
elapsed = (elapsed / 100) * 100;
File outputFile = outputPath.toFile();
// Trim to 1/10th of a kb.
double fileLength = ((int) (((outputFile.length() + 1023) / 1024) * 10)) / 10;
String message = fileLength + "kb";
message += " written in " + (elapsed / 1000.0) + " seconds";
builder.append(NLS.bind("Wrote {0} [{1}]\n", outputFile.getPath(), message));
}
console.print(builder.toString());
}
private static String printFlags(String[] compilerFlags) {
StringBuffer sb = new StringBuffer();
for (String string : compilerFlags) {
sb.append(string).append(" ");
}
return sb.toString();
}
/**
* Dart2js creates java.io.Files; we need to tell the workspace about the new / changed resources.
*
* @param correspondingResource
* @throws CoreException
*/
private static void refreshResources(IResource resource) throws CoreException {
IContainer container;
if (resource == null) {
return;
}
if (resource instanceof IContainer) {
container = (IContainer) resource;
} else {
container = resource.getParent();
}
container.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
}
private IPackageRootProvider packageRootProvider;
private boolean suppressWarnings;
/**
* Create a new Dart2JSCompiler.
*/
public Dart2JSCompiler() {
this(IPackageRootProvider.DEFAULT);
}
public Dart2JSCompiler(IPackageRootProvider packageRootProvider) {
this.packageRootProvider = packageRootProvider;
}
/**
* Run dart2js as a process to compile the given input file to the given output file. If an
* IProgressMonitor is passed in, it is polled to see if the user cancelled the compile operation.
* The progress monitor is not used for any other purpose.
*
* @param inputPath
* @param outputPath
* @param monitor
* @param console
* @return
* @throws IOException
* @throws OperationCanceledException if the user cancelled the operation
*/
public CompilationResult compile(IPath inputPath, IPath outputPath, IProgressMonitor monitor,
MessageConsole console) throws IOException {
return compile(inputPath, outputPath, null, monitor, console);
}
/**
* Run dart2js as a process to compile the given input file to the given output file. If an
* IProgressMonitor is passed in, it is polled to see if the user cancelled the compile operation.
* The progress monitor is not used for any other purpose.
*
* @param inputPath
* @param outputPath
* @param compilerFlags
* @param monitor
* @param console
* @return
* @throws IOException
* @throws OperationCanceledException if the user cancelled the operation
*/
public CompilationResult compile(IPath inputPath, IPath outputPath, String[] compilerFlags,
IProgressMonitor monitor, MessageConsole console) throws IOException {
ProcessBuilder builder = new ProcessBuilder();
List<String> args = new ArrayList<String>();
args.add(DartSdkManager.getManager().getSdk().getDart2JsExecutable().getPath());
if (compilerFlags != null && compilerFlags.length > 0) {
args.addAll(Arrays.asList(compilerFlags));
}
args.addAll(getCompilerArguments(inputPath, outputPath));
builder.command(args);
builder.directory(DartSdkManager.getManager().getSdk().getDirectory());
builder.redirectErrorStream(true);
ProcessRunner runner = new ProcessRunner(builder);
runner.runSync(monitor);
// Echo command line used to launch only if there is an error executing
if (runner.getExitCode() != 0 && console != null) {
StringBuilder msg = new StringBuilder();
for (String arg : args) {
msg.append(arg);
msg.append(" ");
}
console.println(msg.toString().trim());
}
refreshParentFolder(outputPath);
return new CompilationResult(runner, outputPath);
}
public String getName() {
return "dart2js";
}
public boolean getSuppressWarnings() {
return suppressWarnings;
}
public boolean isAvailable() {
return DartSdkManager.getManager().hasSdk();
}
public void setSuppressWarnings(boolean suppressWarnings) {
this.suppressWarnings = suppressWarnings;
}
protected List<String> getCompilerArguments(IPath inputPath, IPath outputPath) {
List<String> args = new ArrayList<String>();
// See dartbug.com/7277.
if (suppressWarnings) {
args.add("--suppress-warnings");
}
File packageRoot = packageRootProvider.getPackageRoot(getProjectFor(inputPath));
if (packageRoot != null) {
args.add("--package-root=" + packageRoot.getPath());
}
args.add("--out=" + outputPath.toOSString());
args.add(inputPath.toOSString());
return args;
}
private IProject getProjectFor(IPath path) {
return ResourceUtil.getFile(path.toFile()).getProject();
}
private void refreshParentFolder(IPath outputPath) {
URI uri = outputPath.removeLastSegments(1).toFile().toURI();
IContainer[] containers = ResourcesPlugin.getWorkspace().getRoot().findContainersForLocationURI(
uri);
if (containers.length > 0) {
try {
containers[0].refreshLocal(1, new NullProgressMonitor());
} catch (CoreException e) {
}
}
}
}