/***************************************************************************** * Copyright (c) 2006-2013, Cloudsmith Inc. * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the copyright holder * listed above, as the Initial Contributor under such license. The text of * such license is available at www.eclipse.org. *****************************************************************************/ package org.eclipse.buckminster.core.materializer; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URI; import java.util.Map; import org.eclipse.buckminster.core.CorePlugin; import org.eclipse.buckminster.core.Messages; import org.eclipse.buckminster.core.RMContext; import org.eclipse.buckminster.core.actor.IPerformManager; import org.eclipse.buckminster.core.cspec.model.Attribute; import org.eclipse.buckminster.core.cspec.model.CSpec; import org.eclipse.buckminster.core.cspec.model.TopLevelAttribute; import org.eclipse.buckminster.core.ctype.IComponentType; import org.eclipse.buckminster.core.helpers.FileUtils; import org.eclipse.buckminster.core.metadata.IResolution; import org.eclipse.buckminster.core.metadata.ModelCache; import org.eclipse.buckminster.core.metadata.StorageManager; import org.eclipse.buckminster.core.metadata.WorkspaceInfo; import org.eclipse.buckminster.core.metadata.model.BOMNode; import org.eclipse.buckminster.core.metadata.model.Materialization; import org.eclipse.buckminster.core.metadata.model.Resolution; import org.eclipse.buckminster.core.metadata.model.WorkspaceBinding; import org.eclipse.buckminster.core.mspec.model.MaterializationSpec; import org.eclipse.buckminster.core.reader.CatalogReaderType; import org.eclipse.buckminster.core.reader.IReaderType; import org.eclipse.buckminster.core.reader.LocalReaderType; import org.eclipse.buckminster.core.reader.P2ReaderType; import org.eclipse.buckminster.core.resolver.LocalResolver; import org.eclipse.buckminster.runtime.Buckminster; import org.eclipse.buckminster.runtime.BuckminsterException; import org.eclipse.buckminster.runtime.MonitorUtils; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRoot; 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.Path; import org.eclipse.osgi.util.NLS; import org.eclipse.team.core.RepositoryProvider; public class WorkspaceMaterializer extends FileSystemMaterializer { private static Materialization getMaterialization(Resolution resolution) throws CoreException { Materialization mat = WorkspaceInfo.getMaterialization(resolution); if (mat != null) return mat; // We still want to bind stuff produced by the local reader // String readerTypeName = resolution.getProvider().getReaderTypeId(); if (!IReaderType.LOCAL.equals(readerTypeName)) // // From the platform. Don't bind this // return null; IReaderType localReaderType = CorePlugin.getDefault().getReaderType(readerTypeName); return new Materialization(localReaderType.getFixedLocation(resolution), resolution.getComponentIdentifier()); } private static boolean isSegmentPrefix(IPath self, IPath other) { String device = self.getDevice(); if (device != null && other.getDevice() != null && !device.equalsIgnoreCase(other.getDevice())) return false; if (self.isEmpty() || (self.isRoot() && other.isAbsolute())) return true; String[] segments = self.segments(); int len = segments.length; String[] otherSegments = other.segments(); if (len > otherSegments.length) return false; if (FileUtils.CASE_INSENSITIVE_FS) { for (int i = 0; i < len; i++) if (!segments[i].equalsIgnoreCase(otherSegments[i])) return false; } else { for (int i = 0; i < len; i++) if (!segments[i].equals(otherSegments[i])) return false; } return true; } private static void storeBelow(Resolution resolution, BOMNode node, StorageManager sm, boolean isBelow) throws CoreException { IResolution nodeRes = node.getResolution(); if (nodeRes == null) return; if (!isBelow) isBelow = nodeRes.equals(resolution); if (isBelow) { // Store the resolution unless it stems from the current target // platform // String readerType = resolution.getProvider().getReaderTypeId(); if (!IReaderType.ECLIPSE_PLATFORM.equals(readerType)) { resolution.store(sm); Materialization mat = getMaterialization(resolution); if (mat != null) mat.store(sm); } } for (BOMNode child : node.getChildren()) storeBelow(resolution, child, sm, isBelow); } private WorkspaceBinding createBindSpec(Resolution resolution, MaterializationContext context) throws CoreException { Materialization mat = getMaterialization(resolution); if (mat == null) return null; IPath wsRoot = context.getWorkspaceLocation(resolution); IPath wsRelativePath; IPath matLoc = mat.getComponentLocation(); IPath bmProjLoc = CorePlugin.getDefault().getBuckminsterProjectLocation(); if (matLoc.hasTrailingSeparator() && !bmProjLoc.isPrefixOf(matLoc)) { wsRelativePath = context.getMaterializationSpec().getResourcePath(resolution); if (wsRelativePath == null) // // Default to project. // wsRelativePath = Path.fromPortableString(getDefaultProjectName(context.getMaterializationSpec(), resolution)); } else { IPath localWsRoot = ResourcesPlugin.getWorkspace().getRoot().getLocation(); if (!FileUtils.pathEquals(wsRoot, localWsRoot)) { if (localWsRoot.isPrefixOf(bmProjLoc)) // // Switch ws root for the bmProject // bmProjLoc = wsRoot.append(bmProjLoc.removeFirstSegments(localWsRoot.segmentCount())); } if (bmProjLoc.isPrefixOf(matLoc)) wsRelativePath = matLoc.removeFirstSegments(bmProjLoc.segmentCount() - 1).setDevice(null); else // // This will become a link in the root of the .buckminster // project // wsRelativePath = new Path(CorePlugin.BUCKMINSTER_PROJECT).append(matLoc.lastSegment()); if (matLoc.hasTrailingSeparator()) wsRelativePath = wsRelativePath.addTrailingSeparator(); } return new WorkspaceBinding(matLoc, resolution, wsRoot, wsRelativePath, context.getBindingProperties()); } private void createExternalBinding(IPath wsRelativePath, WorkspaceBinding mat, IProgressMonitor monitor) throws CoreException, IOException { IPath locationPath = mat.getComponentLocation(); IWorkspace workspace = ResourcesPlugin.getWorkspace(); IWorkspaceRoot wsRoot = workspace.getRoot(); monitor.beginTask(null, 200); monitor.subTask(NLS.bind(Messages.Binding_0, locationPath)); try { String projName = wsRelativePath.segment(0); IPath projRelativePath = wsRelativePath.removeFirstSegments(1); // The directory in the materialization that corresponds to the // project // root can be found by comparing the tail of the materialization // with // the workspace relative location. // IPath locationProjRoot = null; int relSegs = projRelativePath.segmentCount(); int matSegs = locationPath.segmentCount(); if (matSegs >= relSegs) { if (locationPath.removeFirstSegments(matSegs - relSegs).setDevice(null).equals(projRelativePath)) locationProjRoot = locationPath.removeLastSegments(relSegs); } boolean useLink = false; if (locationProjRoot == null) { // The root of the project may contain links so we can still // resolve this // if (relSegs == 1) useLink = true; else throw BuckminsterException.fromMessage(NLS.bind(Messages.Unable_to_determine_project_root_when_binding_0_to_workspace_1, locationPath, wsRelativePath)); } IProject projectForBinding = wsRoot.getProject(projName); if (!projectForBinding.exists()) { // The project does not exist yet. Create it so that it appoints // the root // of the materialization or if a link is used, in the current // workspace. // if (useLink || FileUtils.pathEquals(locationProjRoot.removeLastSegments(1), wsRoot.getLocation())) projectForBinding.create(MonitorUtils.subMonitor(monitor, 50)); else { IProjectDescription desc = ResourcesPlugin.getWorkspace().newProjectDescription(projName); desc.setLocation(locationProjRoot); projectForBinding.create(desc, MonitorUtils.subMonitor(monitor, 50)); } projectForBinding.open(MonitorUtils.subMonitor(monitor, 50)); } else if (!projectForBinding.isOpen()) projectForBinding.open(MonitorUtils.subMonitor(monitor, 100)); else MonitorUtils.worked(monitor, 100); if (useLink) { if (locationPath.toFile().isDirectory()) { IFolder folder = projectForBinding.getFolder(projRelativePath); if (folder.exists()) { if (!(folder.isLinked() && FileUtils.pathEquals(folder.getRawLocation(), locationPath))) throw BuckminsterException.fromMessage(NLS.bind( Messages.Unable_to_create_folder_link_from_workspace_0_to_1_2_already_in_use, new Object[] { wsRelativePath, locationPath, projRelativePath })); MonitorUtils.worked(monitor, 50); } else folder.createLink(locationPath, 0, MonitorUtils.subMonitor(monitor, 50)); } else { IFile ifile = projectForBinding.getFile(projRelativePath); if (ifile.exists()) { if (!(ifile.isLinked() && FileUtils.pathEquals(ifile.getRawLocation(), locationPath))) throw BuckminsterException.fromMessage(NLS.bind( Messages.Unable_to_create_file_link_from_workspace_0_to_1_link_origin_2_already_in_use, new Object[] { wsRelativePath, locationPath, projRelativePath })); MonitorUtils.worked(monitor, 50); } else ifile.createLink(locationPath, 0, MonitorUtils.subMonitor(monitor, 50)); } } else MonitorUtils.worked(monitor, 50); // This resource now resides within the project but a refresh is // needed // projectForBinding.refreshLocal(IResource.DEPTH_INFINITE, MonitorUtils.subMonitor(monitor, 50)); IResource resource = projectForBinding.findMember(projRelativePath); if (resource == null) throw BuckminsterException.fromMessage(NLS.bind(Messages.Unable_to_obtain_resource_0_from_workspace_1, wsRelativePath, projRelativePath)); WorkspaceInfo.setComponentIdentifier(projectForBinding.findMember(projRelativePath), mat.getComponentIdentifier()); } finally { monitor.done(); } } private void createProjectBinding(String suggestedProjectName, WorkspaceBinding wb, RMContext context, IProgressMonitor monitor) throws CoreException, IOException { IWorkspace workspace = ResourcesPlugin.getWorkspace(); IWorkspaceRoot wsRoot = workspace.getRoot(); // Get the absolute path for the ProjectBinding // IPath locationPath = wb.getComponentLocation(); // Check that the source directory is present. // if (!locationPath.toFile().exists()) { MonitorUtils.complete(monitor); throw new FileNotFoundException(locationPath.toString()); } // Find the .project file and load the description // monitor.beginTask(null, 150); monitor.subTask(NLS.bind(Messages.Binding_0, suggestedProjectName)); IProjectDescription description; try { description = workspace.loadProjectDescription(locationPath.append(".project")); //$NON-NLS-1$ // If we find the name in an existing description, then that name is // of course prioritized. // suggestedProjectName = description.getName(); } catch (CoreException e) { description = null; } MonitorUtils.worked(monitor, 50); // Some special treatment is needed for projects that are rooted in the // workspace location // IPath wsRootPath = wsRoot.getLocation(); boolean isRootedInWorkspace = (wsRootPath.segmentCount() == locationPath.segmentCount() - 1 && isSegmentPrefix(wsRootPath, locationPath)); try { if (description == null) { if (isRootedInWorkspace) { // This is heading for disaster unless the last segment of // the locationPath // is in fact equal to the name of the project. For some // reason, Eclipse // stipulates that this has to be the case for this // particular physical // layout. // String forcedName = locationPath.lastSegment(); if (!forcedName.equals(suggestedProjectName)) throw new ProjectNameMismatchException(suggestedProjectName, forcedName); description = workspace.newProjectDescription(suggestedProjectName); } else { description = workspace.newProjectDescription(suggestedProjectName); description.setLocation(locationPath); } } IProject project = wsRoot.getProject(suggestedProjectName); if (!project.isOpen()) { if (!project.exists()) { IProject describedProject = wsRoot.getProject(description.getName()); if (describedProject.exists()) { URI describedLocation = describedProject.getLocationURI(); if (describedLocation.equals(description.getLocationURI())) { Buckminster.getLogger().warning( NLS.bind("Name of project {0} conflicts with previously bound name {1}", suggestedProjectName, //$NON-NLS-1$ description.getName())); project = describedProject; MonitorUtils.worked(monitor, 50); } else { // This is probably a feature that is named in a way // that conflicts with a bundle (or something // similar). // In any case, we need to bind both so we must // alter // the description here. // Buckminster.getLogger().warning( NLS.bind("Name of project {0} conflicts with name {1} found in .project file.", suggestedProjectName, //$NON-NLS-1$ description.getName())); description.setName(suggestedProjectName); project.create(description, MonitorUtils.subMonitor(monitor, 50)); } } else project.create(description, MonitorUtils.subMonitor(monitor, 50)); } project.open(0, MonitorUtils.subMonitor(monitor, 20)); } // Make sure the project is shared RepositoryProvider provider = RepositoryProvider.getProvider(project); if (provider != null) // It's shared all right. We have a team provider return; Resolution cr = wb.getResolution(StorageManager.getDefault()); IReaderType readerType = getMaterializationReaderType(cr); if (readerType instanceof LocalReaderType) { // This might not be the one that we want to use when sharing // the project. Look for known SCM metadata. This should of // course be done in a nicer way using extension points. // IReaderType scmReaderType = null; try { // Try to find a .git folder, either directly in the project // folder or in a folder somewhere in the parent chain of // directories IPath location = project.getLocation(); while (location.segmentCount() > 0) { IPath dotGit = location.append(".git"); //$NON-NLS-1$ if (dotGit.toFile().isDirectory()) { Buckminster.getLogger().debug("Project %s has a git repository at %s", project.getName(), location.toPortableString()); //$NON-NLS-1$ scmReaderType = CorePlugin.getDefault().getReaderType("git"); //$NON-NLS-1$ break; } location = location.removeLastSegments(1); } } catch (CoreException e) { } if (scmReaderType == null && project.getFolder(".svn").exists()) { //$NON-NLS-1$ try { scmReaderType = CorePlugin.getDefault().getReaderType("svn"); //$NON-NLS-1$ } catch (CoreException e) { } } if (scmReaderType == null && project.getFolder("CVS").exists()) { //$NON-NLS-1$ try { scmReaderType = CorePlugin.getDefault().getReaderType("cvs"); //$NON-NLS-1$ } catch (CoreException e) { } } if (scmReaderType == null) Buckminster.getLogger().debug("Unable to determine readerType for project %s. Assuming \"local\"", project.getName()); //$NON-NLS-1$ else readerType = scmReaderType; } readerType.shareProject(project, cr, context, MonitorUtils.subMonitor(monitor, 50)); WorkspaceInfo.setComponentIdentifier(project, cr.getCSpec().getComponentIdentifier()); MonitorUtils.worked(monitor, 30); } finally { monitor.done(); } } @Override protected IPath getArtifactLocation(MaterializationContext context, Resolution resolution) throws CoreException { IPath installLocation = context.getInstallLocation(resolution); IPath leafArtifact = context.getLeafArtifact(resolution); if (leafArtifact == null) installLocation = installLocation.addTrailingSeparator(); else { IReaderType readerType = getMaterializationReaderType(resolution); if (IReaderType.ECLIPSE_IMPORT.equals(readerType.getId())) installLocation = installLocation.append(resolution.getName()).addTrailingSeparator(); else installLocation = installLocation.append(leafArtifact); } return installLocation; } @Override public IPath getDefaultInstallRoot(MaterializationContext context, Resolution resolution) throws CoreException { IPath location = context.getWorkspaceLocation(resolution); IPath leaf = context.getLeafArtifact(resolution); // There are two conditions for putting this into the .buckminster // project // // 1. There is a leaf artifact that indicates that what we have here // is a file. // 2. The leaf artifact is null but the materialization will perform an // unpack. This normally means that an archive (zip or tar.gz) has a // root folder that isn't known until the unpack is complete. Such // a root cannot be used as a project at this point. // if (leaf == null) { if (context.getMaterializationSpec().isUnpack(resolution)) location = location.append(CorePlugin.BUCKMINSTER_PROJECT); } else if (!leaf.hasTrailingSeparator()) { IReaderType readerType = getMaterializationReaderType(resolution); if (!IReaderType.ECLIPSE_IMPORT.equals(readerType.getId())) location = location.append(CorePlugin.BUCKMINSTER_PROJECT); } return location; } private String getDefaultProjectName(MaterializationSpec mspec, Resolution resolution) throws CoreException { return mspec.getProjectName(resolution); } @Override public IReaderType getMaterializationReaderType(Resolution resolution) throws CoreException { // If this is a OSGi bundle in binary form, we must use the // "eclipse.import" // reader in order to materialize // IReaderType rt = super.getMaterializationReaderType(resolution); if (rt instanceof P2ReaderType) // // At present, materializing from a p2 repository into a workspace // can only mean one thing. Importing... // rt = CorePlugin.getDefault().getReaderType(IReaderType.ECLIPSE_IMPORT); if (rt instanceof CatalogReaderType) return rt; String ctId = resolution.getComponentTypeId(); if (IComponentType.OSGI_BUNDLE.equals(ctId) || IComponentType.ECLIPSE_FEATURE.equals(ctId)) rt = CorePlugin.getDefault().getReaderType(IReaderType.ECLIPSE_IMPORT); return rt; } public void installLocal(WorkspaceBinding wb, RMContext context, IProgressMonitor monitor) throws CoreException { monitor.beginTask(null, 200); try { StorageManager sm = StorageManager.getDefault(); monitor.subTask(NLS.bind(Messages.Binding_0, wb.getWorkspaceRelativePath())); Materialization mat = wb.getMaterialization(); mat.store(sm); MonitorUtils.worked(monitor, 10); wb = performPrebindAction(wb, context, MonitorUtils.subMonitor(monitor, 95)); IProgressMonitor subMonitor = MonitorUtils.subMonitor(monitor, 95); IPath wsRelativePath = wb.getWorkspaceRelativePath(); if (wsRelativePath.segmentCount() == 1) createProjectBinding(wsRelativePath.segment(0), wb, context, subMonitor); else createExternalBinding(wsRelativePath, wb, subMonitor); } catch (IOException e) { throw BuckminsterException.wrap(e); } finally { monitor.done(); } } @Override public void performInstallAction(Resolution resolution, MaterializationContext context, IProgressMonitor monitor) throws CoreException { try { WorkspaceBinding wb = createBindSpec(resolution, context); if (wb == null) { MonitorUtils.complete(monitor); return; } IPath wsRoot = wb.getWorkspaceRoot(); if (FileUtils.pathEquals(wsRoot, ResourcesPlugin.getWorkspace().getRoot().getLocation())) installLocal(wb, context, monitor); else { // Don't install in this workspace. Instead store it for later // installation // in the appointed workspace // ExternalDataArea dataArea = new ExternalDataArea(wsRoot, context.getMaterializationSpec().getConflictResolution(resolution)); StorageManager sm = new StorageManager(dataArea.getStateLocation(CorePlugin.getID()).toFile()); wb.store(sm); storeBelow(resolution, context.getBillOfMaterials(), sm, false); } } catch (CoreException e) { if (!context.isContinueOnError()) throw e; context.addRequestStatus(resolution.getRequest(), e.getStatus()); } } @Override public void performPostInstallAction(Resolution resolution, MaterializationContext context, IProgressMonitor monitor) throws CoreException { CSpec cspec = resolution.getCSpec(); Attribute postbindAttr = cspec.getPostbind(); if (postbindAttr != null) { try { CorePlugin.getPerformManager().perform(cspec, postbindAttr.getName(), context, false, false, monitor); } catch (CoreException e) { if (!context.isContinueOnError()) throw e; context.addRequestStatus(resolution.getRequest(), e.getStatus()); } } } private WorkspaceBinding performPrebindAction(WorkspaceBinding wb, RMContext context, IProgressMonitor monitor) throws CoreException { StorageManager sm = StorageManager.getDefault(); Resolution resolution = wb.getResolution(StorageManager.getDefault()); CSpec cspec = resolution.getCSpec(); try { IPerformManager performManager = CorePlugin.getPerformManager(); Attribute bindEntryPoint = cspec.getBindEntryPoint(); if (!(bindEntryPoint instanceof TopLevelAttribute)) { Attribute prebindAttr = cspec.getPrebind(); if (prebindAttr != null) performManager.perform(cspec, prebindAttr.getName(), context, false, false, monitor); else MonitorUtils.complete(monitor); return wb; } Map<String, ? extends Object> props = context.getProperties(resolution.getRequest()); IPath productPath = ((TopLevelAttribute) bindEntryPoint).getUniquePath(wb.getComponentLocation(), new ModelCache(props)); String bindingName = context.getBindingName(resolution, props); performManager.perform(cspec, bindEntryPoint.getName(), props, false, false, monitor); Resolution newRes = LocalResolver.fromPath(productPath, resolution.getName()); newRes = new Resolution(newRes.getCSpec(), resolution); newRes.store(sm); Materialization newMat = new Materialization(productPath.addTrailingSeparator(), newRes.getComponentIdentifier()); newMat.store(sm); return new WorkspaceBinding(newMat.getComponentLocation(), newRes, wb.getWorkspaceRoot(), new Path(bindingName), null); } catch (CoreException e) { if (!context.isContinueOnError()) throw e; context.addRequestStatus(resolution.getRequest(), e.getStatus()); return wb; } } }