Sometimes when you are finished with your recipe, like your favourite cake, you notice that you missed something. Like a topping or a glaze. This can also happen with your Eclipse application. In that case you need a mechanism to update your Eclipse Application, that could be already delivered to your customer, so you can fix bugs or add functionality afterwards.
The Equinox p2 project provides a provisioning infrastructure that can be used to update or install features into an OSGi application.
This recipe will explain and show how to add an update mechanism to an Eclipse 4 application.
This recipe is based on the Eclipse RCP Cookbook – The Thermomix Recipe. To get started fast with this recipe, the recipe is prepared for you on GitHub .
To use the prepared recipe, import the project by cloning the Git repository:
- File → Import → Git → Projects from Git
- Click Next
- Select Clone URI
- Enter URI https://github.com/fipro78/e4-cookbook-basic-recipe.git
- Click Next
- Select the tycho branch
- Click Next
- Choose a directory where you want to store the checked out sources
- Select Import existing Eclipse projects
- Click Finish
Note:
If you see an error saying that the project org.fipro.eclipse.tutorial.app is missing the source folder, create the folder via
- Right click on the project → New → Folder
- Set Folder name to src
- Click Finish
-
Open the target definition org.fipro.eclipse.tutorial.target.target in the project org.fipro.eclipse.tutorial.target
-
Update the Software Site in the opened Target Definition Editor
- Alternative A
- Switch to the Source tab and add the following snippet to the editor
<target name="E4 Cookbook Target Platform" sequenceNumber="1568034040"> <locations> <location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit"> <unit id="org.eclipse.equinox.executable.feature.group" version="3.8.2400.v20240213-1244"/> <unit id="org.eclipse.sdk.feature.group" version="4.31.0.v20240229-1022"/> <unit id="org.eclipse.equinox.core.feature.feature.group" version="1.15.0.v20240214-0846"/> <unit id="org.eclipse.equinox.p2.core.feature.feature.group" version="1.7.100.v20240220-1431"/> <repository location="https://download.eclipse.org/releases/2024-03"/> </location> </locations> </target>
- Alternative B
- Select the Software Site https://download.eclipse.org/releases/2024-03 in the Locations section
- Select Edit...
- Disable Group by category
- Filter for Equinox
- Select Equinox Core Function
- Select Equinox p2, headless functionalities
- If the two previous entries are not selected anymore (4 items need to be selected):
- Filter for Eclipse
- Select Eclipse Platform Launcher Executables
- Select Eclipse Project SDK
- Click Finish
- Alternative A
-
Switch to the Definition tab
- Wait until the Target Definition is completely resolved (check the progress at the bottom right)
- Reload and activate the target platform by clicking Reload Target Platform in the upper right corner of the Target Definition Editor
The Target Definition should look similar to the following screenshot after all steps have been performed.
To keep it simple, an update handler will be added to the application plug-in that can be triggered via a menu entry in the main menu.
- Update the bundle dependencies
- Open the file META-INF/MANIFEST.MF in the project org.fipro.eclipse.tutorial.app
- Switch to the Dependencies tab
- Add the following bundles to the Required Plug-ins
org.eclipse.equinox.p2.core
org.eclipse.equinox.p2.engine
org.eclipse.equinox.p2.metadata.repository
org.eclipse.equinox.p2.operations
- Add the following bundles to the Required Plug-ins
- Update the application model
- Open the file Application.e4xmi in the project org.fipro.eclipse.tutorial.app
- Add a command
- Application → Commands → Add
- Set Name to Update
- Set ID to org.fipro.eclipse.tutorial.app.command.update
(will be done automatically on setting the Name)
- Application → Commands → Add
- Add a handler
- Application → Handlers → Add
- Set ID to org.fipro.eclipse.tutorial.app.handler.update
- Set the Command reference to org.fipro.eclipse.tutorial.app.command.update via the Find... dialog
- Create a handler implementation by clicking on the Class URI link
- Set Package to org.fipro.eclipse.tutorial.app.handler
- Set Name to UpdateHandler
- Click Finish
Note:
We will implement theUpdateHandler
in a later step, after the application model changes are done.
- Application → Handlers → Add
- Add a main menu to the application for making the update command accessible for a user
- Application → Windows → Trimmed Window
- Check Main Menu in the details view of the Trimmed Window
- Select the now visible Main Menu in the Application Model tree below the Trimmed Window
- Set ID to org.eclipse.ui.main.menu
- Add a Menu
- Set ID to org.eclipse.ui.file.menu
- Set Label to File
- Add a Handled Menu Item to the File menu
- Set the Label to Update
- Set the Command reference to the Update command via Find... dialog
- Save the changes to the application model
- Open the file org.fipro.eclipse.tutorial.app.product in the project org.fipro.eclipse.tutorial.product
- Switch to the Contents tab
- Add org.eclipse.equinox.p2.core.feature
- Click Add Required to add the ECF features required by the p2 core feature
- Switch to the Overview tab
- Ensure that a proper version is set to the Version field, e.g. 1.0.0.qualifier
Performing a p2 update basically consists of three steps:
- Create the update operation that should be performed
- Check if there are available updates for the specified update operation
- Perform the update by executing a provisioning job if updates are available
For a good user experience it is best practice to execute those operations in background threads to keep the UI responsive. To keep the focus on p2 updates in this recipe, I will not go into details of background processing via the Eclipse Jobs API here.
You should also consider giving feedback to the user about the update operation results between the steps. For example, if updates are available, the user should be asked whether to perform the update or not.
- Open the
UpdateHandler
class - Get the following values injected in
UpdateHandler#execute()
IProvisioningAgent
OSGi service needed for creating an update operationUISynchronize
Helper class for executing code in the UI threadIWorkbench
The current workbench. Will be needed to restart the application.
public class UpdateHandler {
@Execute
public void execute(
IProvisioningAgent agent,
UISynchronize sync,
IWorkbench workbench) {
...
}
}
p2 operations are performed via an UpdateOperation
instance. To create an UpdateOperation
, a ProvisioningSession
is needed, which can be created by providing an IProvisioningAgent
. As the IProvisioningAgent
is available as an OSGi service if the necessary bundle was started, it can be simply retrieved via dependency injection.
ProvisioningSession session = new ProvisioningSession(agent);
// update all user-visible installable units
UpdateOperation operation = new UpdateOperation(session);
Note:
The UpdateOperation
can also be configured to only update selected installable units.
Via UpdateOperation#resolveModal(IProgressMonitor)
you can check whether updates for the installable units are available or not. It will return an IStatus
which can be inspected to give feedback to the user.
IStatus status = operation.resolveModal(null);
if (status.getCode() == UpdateOperation.STATUS_NOTHING_TO_UPDATE) {
MessageDialog.openInformation(
null,
"Information",
"Nothing to update");
}
Note:
Using null
as IProgressMonitor
will simply avoid progress reporting. Use an appropriate value if progress reporting should be performed, e.g. via JFace ProgressMonitorDialog
.
Via UpdateOperation#getProvisioningJob(IProgressMonitor)
the provisioning job can be retrieved to perform the resolved operation. Since it can be null
under several circumstances, it needs to be checked prior to scheduling.
If a ProvisioningJob
could be created, it is possible to perform an update. For good UX ask the user whether the update should be performed or not. If yes, start the ProvisioningJob
. This can either be done via ProvisioningJob#runModal(IProgressMonitor)
if the job should be executed in the current thread, or ProvisioningJob#schedule()
if it should be executed asynchronously.
ProvisioningJob provisioningJob = operation.getProvisioningJob(null);
if (provisioningJob != null) {
sync.syncExec(new Runnable() {
@Override
public void run() {
boolean performUpdate = MessageDialog.openQuestion(
null,
"Updates available",
"There are updates available. Do you want to install them now?");
if (performUpdate) {
...
provisioningJob.schedule();
}
}
});
}
else {
if (operation.hasResolved()) {
MessageDialog.openError(
null,
"Error",
"Couldn't get provisioning job: " + operation.getResolutionResult());
}
else {
MessageDialog.openError(
null,
"Error",
"Couldn't resolve provisioning job");
}
}
After an update was performed, it is good practice to restart the application so the updates are applied correctly. This can be done using IWorkbench#restart()
.
boolean restart = MessageDialog.openQuestion(null,
"Updates installed, restart?",
"Updates have been installed successfully, do you want to restart?");
if (restart) {
workbench.restart();
}
Note:
If the ProvisioningJob
was executed asynchronously via ProvisioningJob#schedule()
, you need to perform this operation via IJobChangeListener
attached to the ProvisioningJob
.
Note:
If the update made changes to the application model, it is necessary to clear the persisted state. Otherwise the updates won’t be visible to the user. In the example application an update of the application model works because the -clearPersistedState
flag is set. Typically this is not the case for productive environments. At the time of writing this blog post, it is only possible to solve this via workarounds, e.g using the Tom Schindls RestartService
. Hopefully such a service will soon make it into the Eclipse platform itself!
The complete UpdateHandler
using a JFace ProgressMonitor
can be found on GitHub.
To perform update operations, it is necessary to configure the repositories to check against. You need to specify the artifact repositories, which contain the actual content being installed or managed, and the metadata repositories, which contain the installable units (IUs) that describe things that can be installed, the capabilities they provide, and the requirements they have.
Note:
The artifact and metadata repositories don’t need to be at the same locations, but typically they are.
It is possible to configure the repositories programmatically via
UpdateOperation#getProvisioningContext().setArtifactRepositories(URI[])
and
UpdateOperation#getProvisioningContext().setMetadataRepositories(URI[])
.
But the best practice is to configure them via p2.inf configuration files.
- Create the file p2.inf in the project org.fipro.eclipse.tutorial.product
- Add the following lines to configure the repository locations (e.g. C:/Development/tmp/repository)
Note:
${#58}
is the variable for ":"
instructions.configure=\
addRepository(type:0,location:file${#58}/C${#58}/Development/tmp/repository);\
addRepository(type:1,location:file${#58}/C${#58}/Development/tmp/repository/);
Further information on how to create and configure a p2.inf file can be found here:
-
Open a console
- Option A: the console / terminal of your OS
- Option B: the Terminal view of the Eclipse IDE
- Right click on one of the projects → Show in Local Terminal → Terminal
-
Ensure you are in the root folder of the project
-
Execute the following command
mvn clean verify
-
Copy the product from the subfolder for your environment below org.fipro.eclipse.tutorial.product/target/products/ to another directory (e.g. C:/Development/tmp/app)
We now create a small modification that we can install as an update to the previously created product.
-
Open the file Application.e4xmi in the project org.fipro.eclipse.tutorial.app
-
Add an exit command
- Application → Commands → Add
- Set Name to Exit
- Set ID to org.fipro.eclipse.tutorial.app.command.exit
- Application → Commands → Add
-
Add an exit handler
- Application → Handlers → Add
- Set ID to org.fipro.eclipse.tutorial.app.handler.exit
- Set the Command reference to org.fipro.eclipse.tutorial.app.command.exit via Find... dialog
- Create a handler implementation by clicking on the Class URI link
- Set Package to org.fipro.eclipse.tutorial.app.handler
- Set Name to ExitHandler
@Execute public void execute(IWorkbench workbench, Shell shell) { if (MessageDialog.openConfirm(shell, "Exit", "Do you want to exit?")) { workbench.close(); } }
- Application → Handlers → Add
-
Add a Handled Menu Item to the File menu
- Set the Label to Exit
- Set the Command reference to the Exit command via Find... dialog
-
Update versions
As we use pomless Tycho to build the product, the versions in the generated pom.xml files for the modules are derived from the parent pom.xml. This means the versions of the plugins, features and product need match to the version in the parent pom.xml file. This can be adjusted either by adding a pom.xml file in each project (which would kind of violate the idea behind pomless Tycho), or to configure the version additionally via build.properties as explained in Tycho Pomless - Useful Tips.For simplicity we update all versions, which can be done by simply executing the
tycho-versions-plugin
. -
Open a console
- Option A: the console / terminal of your OS
- Option B: the Terminal view of the Eclipse IDE
- Right click on one of the projects → Show in Local Terminal → Terminal
-
Ensure you are in the root folder of the project
-
Execute the following command to update the versions
mvn org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion=1.1.0.qualifier
-
Execute the following command to build the updated product and repository
mvn clean verify
Note:
If the build fails with the following error messageUnqualified OSGi version 1.0.0.qualifier must match unqualified Maven version 1.1.0-SNAPSHOT for SNAPSHOT builds
simply try to execute the build again. There seems to be a caching issue in Tycho that is responsible for that issue. -
Copy the created repository org.fipro.eclipse.tutorial.product/target/repository/ to another directory (e.g. C:/Development/tmp/repository)
Note:
The place should match the entry in the p2.inf file.
- Start the application that was exported first, e.g. via C:/Development/tmp/app/eclipse/eclipse.exe
- Execute the update via File → Update
- Check that an update is performed and that after the restart an Exit menu item is available in the File menu
Following the above steps, it is possible to add a simple update mechanism to an existing Eclipse 4-based application, using plain Equinox p2. With the exception of the JFace dialogs, the above approach can also be used in an e(fx)clipse application.