Packaging and Deployment

Level: Advanced Module: Plugin Development 16 min read Lesson 34 of 47

Overview

  • What you’ll learn:
    • How Eclipse P2 provisioning works and how to create Feature projects and Update Site projects for distributing iDempiere plugins
    • How to build plugins with Maven/Tycho, set up continuous integration with GitHub Actions, and automate testing and deployment
    • How to manage plugin versioning, dependencies, Docker-based deployment, and release management for production environments
  • Prerequisites: Lesson 15 — Building Your First Plugin, Lesson 33 — Testing and Debugging Plugins, basic familiarity with Maven and Git
  • Estimated reading time: 24 minutes

Introduction

A plugin that exists only on a developer’s workstation provides no business value. To be useful, plugins must be packaged, versioned, tested automatically, and deployed reliably to production servers. iDempiere leverages Eclipse’s P2 provisioning system for plugin distribution — the same technology that manages plugins in the Eclipse IDE itself. Combined with Maven/Tycho for builds and CI/CD pipelines for automation, you can establish a professional software delivery process for your iDempiere customizations.

This lesson covers the complete journey from source code to production deployment: organizing plugins into features, building P2 update sites, automating builds with continuous integration, managing versions and dependencies, and deploying to production environments including Docker containers.

The P2 Repository Concept

Eclipse P2 (Provisioning Platform) is a framework for installing, updating, and managing Eclipse-based applications and their plugins. iDempiere is built on Eclipse Equinox (an OSGi implementation), which makes P2 its native plugin distribution mechanism.

How P2 Works

A P2 repository is a structured directory (local or hosted on a web server) containing:

  • Plugin JARs: The compiled OSGi bundles containing your code.
  • Feature JARs: Metadata that groups related plugins into installable units.
  • content.xml / content.jar: An index of all installable units (plugins and features) in the repository, including their versions and dependencies.
  • artifacts.xml / artifacts.jar: An index of the physical artifact files (JARs) and their download locations.

When you install a plugin from a P2 repository, the P2 engine resolves all dependencies, downloads the required artifacts, and configures the OSGi runtime to include the new bundles. This ensures that plugins are installed with all their prerequisites satisfied.

P2 vs. Manual JAR Deployment

While you can deploy plugins by manually copying JAR files to the plugins/ directory, P2 provides critical advantages:

  • Dependency resolution: P2 ensures all required bundles are present before installation.
  • Version management: P2 tracks installed versions and can upgrade, downgrade, or uninstall cleanly.
  • Atomic updates: P2 either installs everything successfully or rolls back, preventing partial installations.
  • Multiple repositories: You can configure multiple P2 repositories (iDempiere core + your custom plugins) and P2 resolves dependencies across all of them.

Feature Projects

A Feature project groups one or more plugins into a single installable unit. Think of it as a “product package” — users install the feature, and all included plugins are deployed together.

Creating a Feature Project

  1. In Eclipse, select File > New > Project > Plug-in Development > Feature Project.
  2. Name it following the convention: com.example.feature.
  3. On the “Referenced Plug-ins” page, select all plugins that belong to this feature.

The feature.xml File

The feature.xml file defines the feature’s metadata and contents:

<?xml version="1.0" encoding="UTF-8"?>
<feature
    id="com.example.feature"
    label="Example iDempiere Customization"
    version="1.0.0.qualifier"
    provider-name="Example Corp">

    <description>
        Custom business logic and reports for Example Corp's
        iDempiere deployment.
    </description>

    <copyright>
        Copyright (c) 2025 Example Corp. All rights reserved.
    </copyright>

    <license url="https://www.example.com/license">
        [License text here]
    </license>

    <!-- Required iDempiere features (dependencies) -->
    <requires>
        <import feature="org.adempiere.server" version="11.0.0" match="greaterOrEqual"/>
        <import feature="org.adempiere.ui.zk" version="11.0.0" match="greaterOrEqual"/>
    </requires>

    <!-- Plugins included in this feature -->
    <plugin
        id="com.example.model"
        download-size="0"
        install-size="0"
        version="0.0.0"
        unpack="false"/>

    <plugin
        id="com.example.process"
        download-size="0"
        install-size="0"
        version="0.0.0"
        unpack="false"/>

    <plugin
        id="com.example.reports"
        download-size="0"
        install-size="0"
        version="0.0.0"
        unpack="false"/>

    <plugin
        id="com.example.rest"
        download-size="0"
        install-size="0"
        version="0.0.0"
        unpack="false"/>

</feature>

Feature Organization Strategies

For large customizations, consider splitting into multiple features:

  • com.example.core.feature: Model classes, validators, and core business logic used by all other components.
  • com.example.ui.feature: UI customizations, dashboard gadgets, and custom forms (depends on core).
  • com.example.reports.feature: JasperReports and report processes (depends on core).
  • com.example.rest.feature: Custom REST API endpoints (depends on core).

This separation allows deploying only the components needed for a specific environment (e.g., the REST feature might only be deployed on the API server, not on the user-facing server).

Update Site Project

An Update Site project builds the P2 repository from your features and plugins.

Creating an Update Site Project

  1. In Eclipse, select File > New > Project > Plug-in Development > Update Site Project.
  2. Name it: com.example.p2.
  3. Open the site.xml file and add your features.

The site.xml File

<?xml version="1.0" encoding="UTF-8"?>
<site>
    <feature url="features/com.example.feature_1.0.0.qualifier.jar"
             id="com.example.feature"
             version="1.0.0.qualifier">
        <category name="example-customization"/>
    </feature>

    <category-def name="example-customization"
                  label="Example Corp Customization">
        <description>Custom plugins for Example Corp deployment</description>
    </category-def>
</site>

Building the Update Site Manually

In Eclipse, open the site.xml editor and click Build All. Eclipse compiles all plugins and features, and generates the P2 repository artifacts in the update site project directory. The result is a directory structure like:

com.example.p2/
  features/
    com.example.feature_1.0.0.202501150900.jar
  plugins/
    com.example.model_1.0.0.202501150900.jar
    com.example.process_1.0.0.202501150900.jar
    com.example.reports_1.0.0.202501150900.jar
    com.example.rest_1.0.0.202501150900.jar
  content.jar
  artifacts.jar

Building with Maven/Tycho

Manual Eclipse builds are fine for development, but automated builds require Maven with the Tycho plugin. Tycho teaches Maven to understand Eclipse plugin projects, feature projects, and P2 repositories.

Parent POM

Create a parent POM at the root of your project repository:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
    http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>com.example.parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <tycho.version>4.0.4</tycho.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <idempiere.target>https://download.idempiere.org/11/p2/</idempiere.target>
    </properties>

    <modules>
        <module>com.example.model</module>
        <module>com.example.process</module>
        <module>com.example.reports</module>
        <module>com.example.rest</module>
        <module>com.example.model.test</module>
        <module>com.example.feature</module>
        <module>com.example.p2</module>
    </modules>

    <repositories>
        <repository>
            <id>idempiere</id>
            <url>${idempiere.target}</url>
            <layout>p2</layout>
        </repository>
    </repositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.eclipse.tycho</groupId>
                <artifactId>tycho-maven-plugin</artifactId>
                <version>${tycho.version}</version>
                <extensions>true</extensions>
            </plugin>
            <plugin>
                <groupId>org.eclipse.tycho</groupId>
                <artifactId>target-platform-configuration</artifactId>
                <version>${tycho.version}</version>
                <configuration>
                    <environments>
                        <environment>
                            <os>linux</os>
                            <ws>gtk</ws>
                            <arch>x86_64</arch>
                        </environment>
                    </environments>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Plugin POM

Each plugin project gets a minimal POM that inherits from the parent:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
    http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>com.example.parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>com.example.model</artifactId>
    <packaging>eclipse-plugin</packaging>
</project>

Feature POM

<project>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>com.example.parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <artifactId>com.example.feature</artifactId>
    <packaging>eclipse-feature</packaging>
</project>

Update Site POM

<project>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>com.example.parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <artifactId>com.example.p2</artifactId>
    <packaging>eclipse-repository</packaging>
</project>

Building

Run the build from the root directory:

# Full build including tests
mvn clean verify

# Build without tests (faster)
mvn clean package -DskipTests

# Build and deploy to a local repository
mvn clean install

The P2 repository is generated in com.example.p2/target/repository/.

Continuous Integration Setup

Automating your build, test, and deployment pipeline ensures consistent quality and reduces manual errors.

GitHub Actions Workflow

Create a CI workflow at .github/workflows/build.yml:

name: Build and Test iDempiere Plugin

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_USER: adempiere
          POSTGRES_PASSWORD: adempiere
          POSTGRES_DB: idempiere
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
          cache: 'maven'

      - name: Build with Maven
        run: mvn clean verify -B

      - name: Upload P2 Repository
        if: github.ref == 'refs/heads/main'
        uses: actions/upload-artifact@v4
        with:
          name: p2-repository
          path: com.example.p2/target/repository/

      - name: Upload Test Results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: '**/target/surefire-reports/*.xml'

Automated Testing in CI

Configure Tycho to run your JUnit tests during the build. For OSGi integration tests, use Tycho’s surefire plugin which launches a minimal OSGi runtime:

<!-- In the test plugin's pom.xml -->
<build>
    <plugins>
        <plugin>
            <groupId>org.eclipse.tycho</groupId>
            <artifactId>tycho-surefire-plugin</artifactId>
            <version>${tycho.version}</version>
            <configuration>
                <testRuntime>p2Installed</testRuntime>
                <argLine>-Xmx1g</argLine>
            </configuration>
        </plugin>
    </plugins>
</build>

Versioning Strategy

Adopt semantic versioning for your plugins to communicate the nature of changes clearly:

Semantic Versioning (Major.Minor.Patch.Qualifier)

  • Major (1.x.x): Breaking changes — removed or renamed APIs, incompatible database schema changes. Consumers of your plugin must update their code.
  • Minor (x.1.x): New features — new endpoints, new model validators, additional fields. Backward-compatible with existing consumers.
  • Patch (x.x.1): Bug fixes — corrected calculations, fixed null pointer exceptions, performance improvements. No API or behavior changes.
  • Qualifier: Build metadata — typically a timestamp (e.g., 1.2.3.202501150900) or SNAPSHOT for development builds.

Version Management Across Projects

Keep versions synchronized across your plugin, feature, and update site. Use the Tycho versions plugin to update versions consistently:

# Update all project versions to 1.2.0-SNAPSHOT
mvn org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion=1.2.0-SNAPSHOT

# Before release, set the release version
mvn org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion=1.2.0

Version Ranges in Dependencies

When your feature declares dependencies on iDempiere core, use appropriate version ranges:

<!-- In feature.xml -->
<requires>
    <!-- Compatible with any 11.x release -->
    <import feature="org.adempiere.server" version="11.0.0" match="compatible"/>
</requires>

<!-- In MANIFEST.MF -->
Require-Bundle: org.adempiere.base;bundle-version="[11.0.0,12.0.0)"

The range [11.0.0,12.0.0) means “any version from 11.0.0 (inclusive) up to but not including 12.0.0.” This allows your plugin to work with any patch or minor update of iDempiere 11 without requiring rebuilds.

Deploying via P2

Once you have a P2 repository, deploy plugins to iDempiere servers using the P2 director.

Using the P2 Director

The P2 director is a command-line application for installing features from P2 repositories:

# Install your feature from a local P2 repository
/opt/idempiere/idempiere -nosplash -application org.eclipse.equinox.p2.director \
  -repository file:///path/to/p2-repository/ \
  -installIU com.example.feature.feature.group \
  -destination /opt/idempiere \
  -profile DefaultProfile

# Install from a remote HTTP repository
/opt/idempiere/idempiere -nosplash -application org.eclipse.equinox.p2.director \
  -repository https://plugins.example.com/p2/ \
  -installIU com.example.feature.feature.group \
  -destination /opt/idempiere \
  -profile DefaultProfile

# Uninstall a feature
/opt/idempiere/idempiere -nosplash -application org.eclipse.equinox.p2.director \
  -uninstallIU com.example.feature.feature.group \
  -destination /opt/idempiere \
  -profile DefaultProfile

Hosting a P2 Repository

Serve your P2 repository via any HTTP server:

# Simple approach: host via nginx
server {
    listen 443 ssl;
    server_name plugins.example.com;

    location /p2/ {
        alias /var/www/p2-repository/;
        autoindex on;
    }
}

Upload the contents of com.example.p2/target/repository/ to the server, and the P2 director can install from the URL.

Docker-Based Deployment

Docker is increasingly popular for iDempiere deployments. Integrate your plugins into a custom Docker image:

Dockerfile for iDempiere with Custom Plugins

FROM idempiereofficial/idempiere:11

# Copy P2 repository into a temporary directory
COPY p2-repository/ /tmp/p2-repo/

# Install plugins using P2 director
RUN /opt/idempiere/idempiere -nosplash \
    -application org.eclipse.equinox.p2.director \
    -repository file:///tmp/p2-repo/ \
    -installIU com.example.feature.feature.group \
    -destination /opt/idempiere \
    -profile DefaultProfile \
    && rm -rf /tmp/p2-repo/

# Copy any additional configuration
COPY custom-config/ /opt/idempiere/custom-config/

Docker Compose for Development

version: '3.8'
services:
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: adempiere
      POSTGRES_PASSWORD: adempiere
      POSTGRES_DB: idempiere
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  idempiere:
    build: .
    depends_on:
      - db
    environment:
      DB_HOST: db
      DB_PORT: 5432
      DB_NAME: idempiere
      DB_USER: adempiere
      DB_PASS: adempiere
    ports:
      - "8080:8080"
      - "8443:8443"
      - "4444:4444"   # Debug port
    volumes:
      - idempiere-data:/opt/idempiere/data

volumes:
  pgdata:
  idempiere-data:

Release Management

A disciplined release process ensures reliable deployments.

Release Workflow

  1. Feature development: Work on feature branches, merged to develop via pull requests with code review.
  2. Release preparation: Create a release branch from develop. Update version numbers (remove SNAPSHOT qualifier). Run full test suite.
  3. Build release artifacts: Build the P2 repository from the release branch. Tag the commit with the version number.
  4. Deploy to staging: Install the release on a staging environment. Run integration tests and user acceptance testing.
  5. Deploy to production: Install the release on production using the P2 director. Verify functionality.
  6. Post-release: Merge the release branch to main and back to develop. Bump version on develop to the next SNAPSHOT.

GitHub Actions Release Workflow

name: Release Plugin

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
          cache: 'maven'

      - name: Build release
        run: mvn clean verify -B

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v1
        with:
          files: |
            com.example.p2/target/com.example.p2-*.zip
          generate_release_notes: true

      - name: Deploy P2 repository
        run: |
          # Upload P2 repo to your hosting server
          rsync -avz com.example.p2/target/repository/ \
            deploy@plugins.example.com:/var/www/p2-repository/

Dependency Management Between Plugins

When multiple teams or projects develop iDempiere plugins, managing inter-plugin dependencies becomes important.

Declaring Dependencies

Use Require-Bundle for compile-time dependencies on specific bundles, and Import-Package for loose coupling:

# Tight coupling: depends on a specific bundle
Require-Bundle: com.example.core;bundle-version="[1.0.0,2.0.0)"

# Loose coupling: depends on the package, not the bundle
Import-Package: com.example.core.api;version="[1.0.0,2.0.0)"

Import-Package is generally preferred because it allows the implementation bundle to change without affecting consumers. Use Require-Bundle only when you need access to multiple packages from a specific bundle or when using the bundle’s extension points.

API Stability Guidelines

  • Export only API packages: In your MANIFEST.MF, only export packages that form your public API. Keep implementation classes in non-exported packages.
  • Use interfaces: Define service interfaces in API packages and implementations in internal packages. Consumers depend on the interfaces, not the implementations.
  • Document breaking changes: When you must make a breaking change, increment the major version and document migration steps in your changelog.

Practical Example: CI/CD Pipeline for an iDempiere Plugin

Here is a complete example tying everything together for a real-world workflow:

Repository Structure

my-idempiere-plugins/
  com.example.model/            # Core model classes and validators
    src/
    META-INF/MANIFEST.MF
    pom.xml
  com.example.process/          # Custom processes
    src/
    META-INF/MANIFEST.MF
    pom.xml
  com.example.rest/             # REST API endpoints
    src/
    META-INF/MANIFEST.MF
    pom.xml
  com.example.model.test/       # Test bundle
    src/
    META-INF/MANIFEST.MF
    pom.xml
  com.example.feature/          # Feature project
    feature.xml
    pom.xml
  com.example.p2/               # Update site
    category.xml
    pom.xml
  .github/
    workflows/
      build.yml                 # CI workflow
      release.yml               # Release workflow
  Dockerfile                    # Docker build
  docker-compose.yml            # Local dev environment
  pom.xml                       # Parent POM

Developer Workflow

  1. Developer creates a feature branch: git checkout -b feature/new-validation
  2. Developer writes code and tests locally, running mvn clean verify.
  3. Developer pushes the branch and creates a pull request.
  4. GitHub Actions automatically builds and runs tests on the PR.
  5. Code is reviewed and merged to develop.
  6. When ready for release, a maintainer tags the commit: git tag v1.2.0.
  7. The release workflow builds the P2 repository and creates a GitHub release.
  8. The operations team deploys to staging using the P2 director, then promotes to production.

Summary

You have now learned the complete lifecycle of iDempiere plugin distribution — from organizing plugins into features and building P2 repositories with Maven/Tycho, through automated CI/CD pipelines with GitHub Actions, to production deployment via P2 director and Docker. Combined with semantic versioning, dependency management, and disciplined release processes, these practices enable you to deliver iDempiere customizations with the same rigor as any professional software product. This concludes the Advanced module of the iDempiere learning curriculum. You now have the knowledge to architect, develop, test, and deploy production-grade iDempiere plugins and integrations.

You Missed