Skip to content

Latest commit

 

History

History
375 lines (300 loc) · 18.1 KB

Eclipse_RCP_Cookbook_p2.md

File metadata and controls

375 lines (300 loc) · 18.1 KB

Eclipse RCP Cookbook – The Topping Recipe (Add p2 update functionality)

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.

Ingredients

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

Preparation

Step 1: Update the Target Platform

  • 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
  • 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.

Step 2: Prepare the application plug-in

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
  • 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)
    • 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 the UpdateHandler in a later step, after the application model changes are done.
    • 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

Step 3: Update the Product Configuration

  • 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

Step 4: Implement the update handler

Performing a p2 update basically consists of three steps:

  1. Create the update operation that should be performed
  2. Check if there are available updates for the specified update operation
  3. 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.

4.1 Update handler preparation

  • Open the UpdateHandler class
  • Get the following values injected in UpdateHandler#execute()
    • IProvisioningAgent
      OSGi service needed for creating an update operation
    • UISynchronize
      Helper class for executing code in the UI thread
    • IWorkbench
      The current workbench. Will be needed to restart the application.
public class UpdateHandler {
 
    @Execute
    public void execute(
            IProvisioningAgent agent, 
            UISynchronize sync, 
            IWorkbench workbench) {
        ...
    }
 
}

4.2 Create the update operation

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.

4.3 Check if there are updates available

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.

4.4 Check if an update can be performed

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");
    }
}

4.5 Restart the application after the update is finished

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.

Step 5: Configure the repository location

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:

Step 6: Build the product

  • 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)

Step 7: Create an application update

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
  • 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();
            }
        }
  • 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 message Unqualified 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.

Taste

  • 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.