/*
* Copyright (c) 2012, the 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.pub;
import com.google.common.annotations.VisibleForTesting;
import com.google.dart.tools.core.DartCore;
import com.google.dart.tools.core.DartCoreDebug;
import com.google.dart.tools.core.MessageConsole;
import com.google.dart.tools.core.analysis.model.PubFolder;
import com.google.dart.tools.core.builder.BuildEvent;
import com.google.dart.tools.core.builder.BuildParticipant;
import com.google.dart.tools.core.builder.BuildVisitor;
import com.google.dart.tools.core.builder.CleanEvent;
import com.google.dart.tools.core.internal.builder.DartBuilder;
import com.google.dart.tools.core.utilities.yaml.PubYamlUtils;
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.IResourceDelta;
import org.eclipse.core.resources.IResourceProxy;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
/**
* This build participant has a higher priority and should be called by {@link DartBuilder} before
* the dart project is analyzed or build.dart is run. It will run pub install on any pubspec file
* that has been added or changed.
*
* @coverage dart.tools.core.pub
*/
public class PubBuildParticipant implements BuildParticipant, BuildVisitor {
/**
* Flag indicating whether the sources are being reanalyzed and pub does NOT need to be run.
*/
private boolean reanalyze = false;
/**
* The set of containers on which pub is currently running. Synchronize against this collection
* before accessing it.
*/
private static final HashSet<IContainer> currentContainers = new HashSet<IContainer>();
@VisibleForTesting
public static boolean isPubContainersEmpty() {
return currentContainers.isEmpty();
}
@Override
public void build(BuildEvent event, IProgressMonitor monitor) throws CoreException {
if (reanalyze) {
reanalyze = false;
} else {
event.traverse(this, false);
}
}
@Override
public void clean(CleanEvent event, IProgressMonitor monitor) {
reanalyze = true;
}
/**
* Find the pubspec associated with the specified resource, and if necessary run pub install
*/
public void runPubFor(IResource res, IProgressMonitor monitor) {
if (res == null) {
return;
}
IWorkspaceRoot root = res.getWorkspace().getRoot();
IContainer container = res.getType() == IResource.FILE ? res.getParent() : (IContainer) res;
while (container != root) {
IFile pubFile = container.getFile(new Path(DartCore.PUBSPEC_FILE_NAME));
if (pubFile.exists()) {
runPub(container, monitor);
return;
}
container = container.getParent();
}
}
@Override
public boolean visit(IResourceDelta delta, IProgressMonitor monitor) {
final IResource resource = delta.getResource();
if (resource.getType() == IResource.FILE) {
if (delta.getKind() == IResourceDelta.CHANGED) {
if (resource.getName().equals(DartCore.PUBSPEC_FILE_NAME)) {
runPub(resource.getParent(), monitor);
processPubspecContents(resource, resource.getProject(), monitor);
IResource lockFile = resource.getParent().findMember(DartCore.PUBSPEC_LOCK_FILE_NAME);
if (lockFile != null) {
processLockFileContents(lockFile, resource.getProject(), monitor);
}
}
if (resource.getName().equals(DartCore.PUBSPEC_LOCK_FILE_NAME)) {
processLockFileContents(resource, resource.getProject(), monitor);
}
}
}
return true;
}
@Override
public boolean visit(IResourceProxy proxy, IProgressMonitor monitor) {
if (proxy.getType() == IResource.FILE) {
if (proxy.getName().equals(DartCore.PUBSPEC_FILE_NAME)) {
runPub(proxy.requestResource().getParent(), monitor);
processPubspecContents(
proxy.requestResource(),
proxy.requestResource().getProject(),
monitor);
IResource lockFile = proxy.requestResource().getParent().findMember(
DartCore.PUBSPEC_LOCK_FILE_NAME);
if (lockFile != null) {
processLockFileContents(lockFile, proxy.requestResource().getProject(), monitor);
}
}
if (proxy.getName().equals(DartCore.PUBSPEC_LOCK_FILE_NAME)) {
processLockFileContents(
proxy.requestResource(),
proxy.requestResource().getProject(),
monitor);
}
}
return true;
}
/**
* Process the lockfile to extract the version information, and save the information in the
* resource property DartCore.PUB_PACKAGE_VERSION
*
* @param lockFile the pubspec.lock file
* @param project containing the pubspec.lock file
* @param monitor the progress monitor
*/
protected void processLockFileContents(IResource lockFile, IProject project,
IProgressMonitor monitor) {
Map<String, String> versionMap = PubYamlUtils.getPackageVersionMap(lockFile);
if (versionMap != null && !versionMap.isEmpty()) {
for (String key : versionMap.keySet()) {
IResource folder = lockFile.getParent().findMember(
DartCore.PACKAGES_DIRECTORY_NAME + "/" + key);
if (folder != null) {
try {
folder.setPersistentProperty(DartCore.PUB_PACKAGE_VERSION, versionMap.get(key));
} catch (CoreException e) {
DartCore.logError(e);
}
}
}
if (DartCoreDebug.ENABLE_ANALYSIS_SERVER) {
PubCacheManager_NEW.getInstance().updatePackagesList(0);
} else {
PubCacheManager_OLD.getInstance().updatePackagesList(0, versionMap);
}
PubManager.getInstance().notifyListeners(lockFile.getParent());
}
}
/**
* Process the pubspec file to extract name and dependencies and save in model (DartProjectImpl)
*
* @param pubspec the pubspec.yaml file
* @param project IProject project for the pubspec file
* @param monitor the progress monitor
*/
protected void processPubspecContents(IResource pubspec, IProject project,
IProgressMonitor monitor) {
if (!DartCoreDebug.ENABLE_ANALYSIS_SERVER) {
try {
PubFolder pubFolder = DartCore.getProjectManager().getPubFolder(pubspec);
if (pubFolder != null) {
pubFolder.invalidatePubspec();
}
} catch (CoreException e) {
DartCore.logError(e);
} catch (IOException e) {
DartCore.logError(e);
}
}
}
/**
* Execute the pub operation. This is overridden when testing this class to record the intent to
* run pub but prevent actually running pub.
*
* @param container the working directory in which pub should be run (not <code>null</code>)
* @param monitor the progress monitor (not <code>null</code>)
*/
protected void runPub(IContainer container, final IProgressMonitor monitor) {
// If pub is already running for this container, then wait until it finishes before returning
synchronized (currentContainers) {
if (!currentContainers.add(container)) {
while (currentContainers.contains(container)) {
try {
currentContainers.wait(1000);
} catch (InterruptedException e) {
//$FALL-THROUGH$
}
}
return;
}
}
try {
// Only run pub automatically if it is not already up to date
File dir = container.getLocation().toFile();
File pubFile = new File(dir, DartCore.PUBSPEC_FILE_NAME);
File lockFile = new File(dir, DartCore.PUBSPEC_LOCK_FILE_NAME);
File packagesDir = new File(dir, DartCore.PACKAGES_DIRECTORY_NAME);
if ((DartCoreDebug.NO_PUB_PACKAGES || packagesDir.exists()) && lockFile.exists()
&& lockFile.lastModified() >= pubFile.lastModified()) {
return;
}
// Run pub or notify the user that it needs to be run
if (DartCore.getPlugin().isAutoRunPubEnabled()) {
new RunPubJob(container, RunPubJob.INSTALL_COMMAND, true).run(monitor);
} else {
MessageConsole console = DartCore.getConsole();
console.printSeparator("");
console.println("Run Tools > Pub Get to install packages");
}
} finally {
// Ensure that currentContainers is updated
synchronized (currentContainers) {
currentContainers.remove(container);
currentContainers.notifyAll();
}
}
}
}