Friday, October 15, 2010

Power of Buckminster's advisor nodes

Yesterday while working on updating GMF plugins with new model I had some changes in my workspace that produced compilation errors. I did not have time to fix the errors by the end of the day but with the changes I had, I did not want to leave them in my workspace without checking them into SVN. You know what they say "If it's not in version control, it does not exist." However, checking in these changes into SVN would mean that the build would break. Yes, I could have re-branches the changes, but did not have time for that either. What's the solution? Buckminster's advisor nodes! Here is what I did in my cquery:















I added three advisor nodes that set revision to last good revision of the plugins. That's it. During materialization three plugins with revision 63870 will be checked out and although latest revision has compilation errors, build will still work.

Friday, July 16, 2010

buckminster, svn provider and svn+ssh protocol

I've been struggling to make use of svn provider using svn+ssh protocol. After I setup ssh keys I would get errors such as:

ERROR [0000] : No suitable provider for component com.iwaysoftware.core.services:osgi.bundle was found in searchPath resources
ERROR [0000] : Rejecting provider svn({0}/dependencies/trunk/{1}): No component match was found
ERROR org.tigris.subversion.svnclientadapter.SVNClientException: org.tigris.subversion.javahl.ClientException: svn: Handshake failed, received: ''
ERROR [0000] : Rejecting provider svn({0}/iIT/trunk/features/{1}): No component match was found
ERROR org.tigris.subversion.svnclientadapter.SVNClientException: org.tigris.subversion.javahl.ClientException: svn: Handshake failed, received: ''

Fortunately I went to #eclipse-b3 on IRC and by luck jbarkanic was there who had the very same problem. With his help I was able to make this work by adding two lines in Hudson's Buckminster command section:

setpref org.eclipse.buckminster.core.maxParallelMaterializations=1
setpref org.eclipse.buckminster.core.maxParallelResolutions=1

It seems Buckminster does not work well in multi-threaded mode in some configurations, so forcing operation into single-threaded mode fixes the issue.

Thursday, June 10, 2010

Leveraging org.eclipse.ui.intro.samples extension point

Eclipse provides excellent samples framework that is part of 'Welcome' experience, unfortunately docs in the extension point state that This extension point is currently provided for internal Eclipse SDK usage only. It should not be used for anything else except Eclipse SDK examples. What this means is that:
  • Projects are downloaded from eclipse.org so you can't easily define alternate location of the zip files
  • The whole UI revolves around assumption that your project is an Eclipse plug-in or a project that contains some kind of code
Fortunately there is a way to manipulate all these features for your own needs.

Define location
As I stated above default url location for samples locations is eclipse.org which is defined as ShowSampleAction.UPDATE_SITE in org.eclipse.pde.internal.ui.samples. ShowSampleAction class is responsible for downloading zip file that is associated with the sample entry. All that is needed to make downloading sample from you own repository is to copy and modify ShowSampleAction with your own definitions of feature id, feature version and update site. All these constants are defined at the top of the class.

public class ShowSampleAction extends Action implements IIntroAction {
private static final String SAMPLE_FEATURE_ID = "org.eclipse.sdk.samples"; //$NON-NLS-1$
private static final String SAMPLE_FEATURE_VERSION = "3.3.0"; //$NON-NLS-1$
private static final String UPDATE_SITE = "http://dev.eclipse.org/viewcvs/index.cgi/%7Echeckout%7E/pde-ui-home/samples/"; //$NON-NLS-1$


Customize download message
In addition you might want to change the message that appears in the dialog when user clicks on the sample link. This is done in ShowSampleAction.ensureSampleFeaturePresent() method


private boolean ensureSampleFeaturePresent() {
if (checkFeature())
return true;
// the feature is not present - ask to download
if (MessageDialog.openQuestion(PDEPlugin.getActiveWorkbenchShell(), PDEUIMessages.ShowSampleAction_msgTitle, PDEUIMessages.ShowSampleAction_msgDesc)) {
return downloadFeature();
}
return false;
}


Plugin' it in
So now that we have our custom action defined how do we tell framework about it? When defining your samples in org.eclipse.ui.intro extension point the xml file that creates samples configExtension is the place we are interested in. I am not going to go into detail of how to implement the extension point here as this is covered vastly elsewhere. So here are the contents of my samples xml file:






description







The part we are interested in is the url attribute of the link element, this is where our custom action is defined:


url="http://org.eclipse.ui.intro/runAction?pluginId=com.iwaysoftware.integration.tools.samples&class=com.iwaysoftware.integration.tools.samples.actions.ShowSampleAction&id=com.iwaysoftware.samples.scifi"


Notice definition of the pluginId and class, this is what makes your custom action execute when link in the samples page is clicked.

This takes care of downloading and importing the project part.

Tweaking UI
After project is imported into your workspace from samples page there are number of things that can happen in the framework which is controlled in the IntroURL.doExecute() method: a welcome view can be closed, browser opened or another action ran. Set a break point in the method and interact with samples page to see what happens. By default IntroURL.SHOW_STANDBY is executed, but how and who controls what action is executed? Why it's our friend ShowSampleAction! Take a look at ShowSampleAction.switchToSampleStandby() method.

private void switchToSampleStandby(SampleWizard wizard) {
StringBuffer url = new StringBuffer();
url.append("http://org.eclipse.ui.intro/showStandby?"); //$NON-NLS-1$
url.append("pluginId=com.iwaysoftware.integration.tools.samples"); //$NON-NLS-1$
url.append("&"); //$NON-NLS-1$
url.append("partId=com.iwaysoftware.samples.SamplesStandbyPart"); //$NON-NLS-1$
url.append("&"); //$NON-NLS-1$
url.append("input="); //$NON-NLS-1$
url.append(sampleId);
IIntroURL introURL = IntroURLFactory.createIntroURL(url.toString());
if (introURL != null) {
introURL.execute();
ensureProperContext(wizard);
}
}


This is where url is constructed and passed to IntroURL. So if you change http://org.eclipse.ui.intro/showStandby to http://org.eclipse.ui.intro/close after sample is imported no welcome view will be docked to the left of your Eclipse instance. As I mentioned you can explore all available actions in IntroURL class.

So why is this important? Because samples extension point pre-wired to work with java projects, which might not be the case as it is in our product. Our product is a collection of xml files that are manipulated by user via GEF editors. So when a project is imported into workspace a java-project-centric welcome view appears that is not necessarily what you want to happen in your case because there is no source to browser for example. One obvious thing you can do is to call close action in the ShowSampleAction and be done with it, but you can also customize the view for your own needs.

Customizing the view
The content of the welcome view page are loaded from a class that implements IStandbyContentPart, particularly org.eclipse.pde.internal.ui.samples.SampleStandbyContent. The content of the page is defined in the createPartControl() method which could be as simple as name and description of the sample or as complicated as launching run shortcut with predefined parameters. What is nice here is you are in total control of what happens in the view.
Once you define the view, how do you integrate your changes into the framework? It took some debugging time to find the answer - org.eclipse.ui.internal.intro.impl.parts.StandbyPart.showContentPart() is the place, specifically


IntroStandbyContentPart standbyPartContent = ExtensionPointManager
.getInst().getSharedConfigExtensionsManager()
.getStandbyPart(partId);


this is the extension point from which implementer of IStandbyContentPart is loaded


String standbyContentClassName = standbyPartContent.getClassName();
String pluginId = standbyPartContent.getPluginId();

Object standbyContentObject = ModelLoaderUtil.createClassInstance(
pluginId, standbyContentClassName);


And the place where magic happens is in extension point world, where sample defining xml is set:


point="org.eclipse.ui.intro.configExtension">
configId="org.eclipse.ui.intro.universalConfig"
content="intro/samplesExtensionContent.xml">

id="com.iwaysoftware.samples.SamplesStandbyPart"
class="com.iwaysoftware.integration.tools.samples.content.SamplesStandbyContent"
pluginId="com.iwaysoftware.integration.tools.samples"/>



As you can see our custom SamplesStandbyContent is defined and will be loaded by the extension point mechanism when it's time to display the view.

Disclaimer :)
Any or all information mentioned above could become useless since samples extension point and its supporting classes are internal, so if something changes there this can tumble like a house of cards, I hope it will not. Fingers crossed.

Blogger Syntax Highliter