Jenkins : Tips for optional dependencies

Plugin statuses and behaviors

  • Tested in Jenkins 1.532.1.

Status

getPlugin("dependee") != null

getPlugin("dependee").getWrapper().isEnabled()

Load class from your plugin

Not installed

false

NPE

Fail

Installed without restart

true

true

Fail (JENKINS-19976)

Installed without restart before your plugin

true

true

Succeed

Installed

true

true

Succeed

Uninstalled and not restarted

true

true

Succeed

Disabled and not restarted

true

false

Succeed

Disabled

false

NPE

Fail since Jenkins 1.524

How to test them

You can test them in Manage Jenkins > Script Console as followings:

  • getPlugin("dependee") != null

    println(Jenkins.instance.getPlugin("pluginname-to-test"));
    
  • getPlugin("dependee").getWrapper().isEnabled()

    println(Jenkins.instance.getPlugin("pluginname-to-test").getWrapper().isEnabled());
    
  • Load class from your plugin

    println(Jenkins.instance.getPlugin("your-plugin-name").getWrapper().classLoader.loadClass("name-of-class-to-load"));
    

Codes cause class loading

When your plugin optionally depends on other plugins, you have to access classes in those plugins only when you have checked those plugins are installed.

In most cases, you can do that by testing plugin installations before executing codes that access those classes, as described in Dependencies among plugins#Optional dependencies.

However, there are cases your class causes loading other classes not when executing codes that access them, but when someone instantiates your class, or calls reflection methods for your class. Especially Descriptor#<init> calls Class#getMethod("getDescriptor") and it often causes those cases.

Example case

  • dependee-plugin and depender-plugin both depend on base-plugin.
  • depender-plugin optionally depends on dependee-plugin.
  • class Derived in dependee-plugin extends class Base in base-plugin.

contained in public method declarations

  • Classes in public method declarations will be loaded.
  • Any of following public method declarations cause Derived loaded when SomeClass is instantiated, or someone calls SomeClass#getMethod and so on. If dependee-plugin is not installed, it causes NoClassDefFoundError even though doSomething1() and doSomething2 are never executed.

    public class SomeClass
    {
      // They cause loading Derived even without execution.
      
      public Derived doSomething1()
      {
        // dosomething...
      }
      
      public void doSomething2(Derived arg0)
      {
        // dosomething...
      }
    }
    
  • You'd better not to use classes in optional depended plugins in any method declarations.
    • There might be cases even non-public (protected or private) method declarations cause class loading.

Upcasting

  • If there are codes upcasting to the base class, the derived class will be loaded.
  • Following code cause Derived loaded when SomeClass is instantiated, or someone calls SomeClass#getMethod and so on. If dependee-plugin is not installed, it causes NoClassDefFoundError even though doSomething() are never executed.

    public class SomeClass
    {
      public void doSomething()
      {
        Derived d = getDrived();
        Base b = (Base)d; // This causes loading Derived even without execution.
        ...
      }
    }
    
  • Following code also cause loading Derived.

    public class SomeClass
    {
      public void doSomething()
      {
        Derived d = getDrived();
        doSomethingImpl(d); // This causes loading Derived even without execution.
        ...
      }
      
      private void doSomethingImpl(Base b)
      {
        // do something
      }
    }
    
  • Following code also cause loading Derived if you compile it with JDK 1.6. This does not cause a problem when you compile it with JDK 1.7.

    public class SomeClass
    {
      public void doSomething()
      {
        Collection<Derived> dList = getDerivedList();
        for(Base b: dList) // this would be converted to for(Derived b: dList) in JDK1.6.
        {
          doSomethingImpl(d); // This causes loading Derived even without execution compiled with JDK1.6.
          ...
        }
      }
      
      private void doSomethingImpl(Base b)
      {
        // do something
      }
    }
    

Upcastings not causing class loading

  • Upcasting to Object does not cause class loading. Following code does not cause loading Derived.

    public class SomeClass
    {
      public void doSomething()
      {
        Derived d = getDrived();
        Object b = (Object)d; // This causes loading Derived even without execution.
        ...
      }
    }
    
  • Generic types are considered only when compiling, and ignored when execution. Following code does not cause loading Derived.

    public class SomeClass
    {
      public void doSomething()
      {
        Collection<Derived> dList = getDerivedList();
        Base b = (Base)dList.get(0);
      }
    }
    
  • Following code generates safe byte codes even with JDK 1.6.

    public class SomeClass
    {
      public void doSomething()
      {
        Collection<Derived> dList = getDerivedList();
        for(Iterator<Derived> it = dList.iterator(); it.hasNext(); )
        {
          Base b = it.next();
          ...
        }
      }
      
      private void doSomethingImpl(Base b)
      {
        // do something
      }
    }
    

Attachments:

SampleCase.png (image/png)
SampleCase.pptx (application/vnd.openxmlformats-officedocument.presentationml.presentation)