Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

create a separate gem for the jenkins plugins ruby runtime support cl…

…asses
  • Loading branch information...
commit 8045bc0936b89249da4c9a959b08854ff54e5987 0 parents
Charles Lowell authored August 19, 2011

Showing 37 changed files with 1,270 additions and 0 deletions. Show diff stats Hide diff stats

  1. 6  .gitignore
  2. 4  Gemfile
  3. 19  Rakefile
  4. 28  jenkins-plugin-runtime.gemspec
  5. 15  lib/jenkins/launcher.rb
  6. 57  lib/jenkins/model.rb
  7. 37  lib/jenkins/model/action.rb
  8. 21  lib/jenkins/model/build.rb
  9. 52  lib/jenkins/model/descriptor.rb
  10. 47  lib/jenkins/model/listener.rb
  11. 105  lib/jenkins/plugin.rb
  12. 20  lib/jenkins/plugin/cli.rb
  13. 130  lib/jenkins/plugin/proxies.rb
  14. 50  lib/jenkins/plugin/proxies/build_wrapper.rb
  15. 22  lib/jenkins/plugin/proxies/builder.rb
  16. 76  lib/jenkins/plugin/proxy.rb
  17. 13  lib/jenkins/plugin/runtime.rb
  18. 5  lib/jenkins/plugin/version.rb
  19. 9  lib/jenkins/slaves/cloud.rb
  20. 37  lib/jenkins/tasks/build_wrapper.rb
  21. 33  lib/jenkins/tasks/builder.rb
  22. 8  spec/jenkins/launcher_spec.rb
  23. 36  spec/jenkins/model/action_spec.rb
  24. 8  spec/jenkins/model/build_spec.rb
  25. 31  spec/jenkins/model/listener_spec.rb
  26. 71  spec/jenkins/model_spec.rb
  27. 7  spec/jenkins/plugin_spec.rb
  28. 22  spec/jenkins/plugins/proxies/build_wrapper_spec.rb
  29. 24  spec/jenkins/plugins/proxies/builder_spec.rb
  30. 24  spec/jenkins/plugins/proxies/proxy_helper.rb
  31. 148  spec/jenkins/plugins/proxies_spec.rb
  32. 43  spec/jenkins/plugins/proxy_spec.rb
  33. 7  spec/jenkins/tasks/build_wrapper_spec.rb
  34. 8  spec/jenkins/tasks/builder_spec.rb
  35. 11  spec/spec_helper.rb
  36. 18  src/jenkins/ruby/DoDynamic.java
  37. 18  src/jenkins/ruby/Get.java
6  .gitignore
... ...
@@ -0,0 +1,6 @@
  1
+*.gem
  2
+.bundle
  3
+.idea
  4
+Gemfile.lock
  5
+pkg/*
  6
+target/
4  Gemfile
... ...
@@ -0,0 +1,4 @@
  1
+source "http://rubygems.org"
  2
+
  3
+# Specify your gem's dependencies in jenkins-plugins.gemspec
  4
+gemspec
19  Rakefile
... ...
@@ -0,0 +1,19 @@
  1
+require 'bundler'
  2
+Bundler::GemHelper.install_tasks
  3
+
  4
+require 'rspec/core/rake_task'
  5
+RSpec::Core::RakeTask.new(:spec)
  6
+
  7
+require 'jenkins/war'
  8
+Jenkins::War.classpath
  9
+ClassPath =  FileList[File.join(ENV['HOME'], '.jenkins', 'wars', Jenkins::War::VERSION, "**/*.jar")].to_a.join(':')
  10
+
  11
+desc "compile java source code"
  12
+task "compile" => "target" do
  13
+  puts command = "javac -classpath #{ClassPath} #{FileList['src/**/*.java']} -d target"
  14
+  system(command)
  15
+end
  16
+
  17
+require 'rake/clean'
  18
+directory "target"
  19
+CLEAN.include("target")
28  jenkins-plugin-runtime.gemspec
... ...
@@ -0,0 +1,28 @@
  1
+# -*- encoding: utf-8 -*-
  2
+$:.push File.expand_path("../lib", __FILE__)
  3
+require "jenkins/plugin/version"
  4
+
  5
+Gem::Specification.new do |s|
  6
+  s.name        = "jenkins-plugin-runtime"
  7
+  s.version     = Jenkins::Plugin::VERSION
  8
+  s.platform    = Gem::Platform::RUBY
  9
+  s.authors     = ["Charles Lowell"]
  10
+  s.email       = ["cowboyd@thefrontside.net"]
  11
+  s.homepage    = "http://github.com/cowboyd/jenkins-plugins.rb"
  12
+  s.summary     = %q{Runtime support libraries for Jenkins Ruby plugins}
  13
+  s.description = %q{I'll think of a better description later, but if you're reading this, then I haven't}
  14
+
  15
+  s.rubyforge_project = "jenkins-plugin-runtime"
  16
+
  17
+  s.files         = `git ls-files`.split("\n")
  18
+  s.test_files    = `git ls-files -- {test,spec,features}/*`.split("\n")
  19
+  s.executables   = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
  20
+  s.require_paths = ["lib"]
  21
+
  22
+  s.add_dependency "json"
  23
+
  24
+  s.add_development_dependency "rspec", "~> 2.0"
  25
+  s.add_development_dependency "cucumber", "~> 1.0"
  26
+  s.add_development_dependency "jenkins-war"
  27
+
  28
+end
15  lib/jenkins/launcher.rb
... ...
@@ -0,0 +1,15 @@
  1
+
  2
+module Jenkins
  3
+
  4
+  # Launch processes on build slaves. No functionality is currently exposed
  5
+  class Launcher
  6
+    # the nantive hudson.Launcher object
  7
+    attr_reader :native
  8
+
  9
+    def initialize(native = nil)
  10
+      @native = native
  11
+    end
  12
+
  13
+    Plugin::Proxies.register self, Java.hudson.Launcher
  14
+  end
  15
+end
57  lib/jenkins/model.rb
... ...
@@ -0,0 +1,57 @@
  1
+
  2
+module Jenkins
  3
+  module Model
  4
+
  5
+    module Included
  6
+      def included(cls)
  7
+        super(cls)
  8
+        if cls.class == Module
  9
+          cls.extend(Included)
  10
+        else
  11
+          cls.extend(ClassMethods)
  12
+          cls.send(:include, InstanceMethods)
  13
+        end
  14
+      end
  15
+    end
  16
+    extend Included
  17
+
  18
+    module InstanceMethods
  19
+      # Get the display name of this Model. This value will be used as a default
  20
+      # whenever this model needs to be shown in the UI. If no display name has
  21
+      # been set, then it will use the Model's class name.
  22
+      #
  23
+      # @return [String] the display name
  24
+      def display_name
  25
+        self.class.display_name
  26
+      end
  27
+    end
  28
+
  29
+    module ClassMethods
  30
+
  31
+      # Set or get the display name of this Model Class.
  32
+      #
  33
+      # If `name` is not nil, then sets the display name.
  34
+      # @return [String] the display name
  35
+      def display_name(name = nil)
  36
+        name.nil? ? @display_name || self.name : @display_name = name.to_s
  37
+      end
  38
+
  39
+      # Mark a set of properties that should not be persisted as part of this Model's lifecycle.
  40
+      #
  41
+      # Jenkins supports transparent persistent
  42
+      def transient(*properties)
  43
+        properties.each do |p|
  44
+          transients[p.to_sym] = true
  45
+        end
  46
+      end
  47
+
  48
+      def transient?(property)
  49
+        transients.keys.member?(property.to_sym) || (superclass < Model && superclass.transient?(property))
  50
+      end
  51
+
  52
+      def transients
  53
+        @transients ||= {}
  54
+      end
  55
+    end
  56
+  end
  57
+end
37  lib/jenkins/model/action.rb
... ...
@@ -0,0 +1,37 @@
  1
+
  2
+module Jenkins
  3
+  module Model
  4
+    module Action
  5
+      include Model
  6
+
  7
+      module Included
  8
+        def included(cls)
  9
+          super(cls)
  10
+          if cls.class == Module
  11
+            cls.extend(Included)
  12
+          else
  13
+            cls.extend(ClassMethods)
  14
+            cls.send(:include, InstanceMethods)
  15
+          end
  16
+        end
  17
+      end
  18
+      extend Included
  19
+
  20
+      # def included(cls)
  21
+      #   super(cls)
  22
+      #   cls.send(:include)
  23
+      # end
  24
+      module InstanceMethods
  25
+        def icon
  26
+          self.class.icon
  27
+        end
  28
+      end
  29
+      # 
  30
+      module ClassMethods
  31
+        def icon(path = nil)
  32
+          path.nil? ? @path : @path = path.to_s
  33
+        end
  34
+      end
  35
+    end
  36
+  end
  37
+end
21  lib/jenkins/model/build.rb
... ...
@@ -0,0 +1,21 @@
  1
+
  2
+module Jenkins
  3
+  module Model
  4
+    ##
  5
+    # Represents a single build. In general, you won't need this
  6
+    #
  7
+    class Build
  8
+
  9
+      ##
  10
+      # the Hudson::Model::AbstractBuild represented by this build
  11
+      attr_reader :native
  12
+
  13
+      def initialize(native = nil)
  14
+        @native = native
  15
+      end
  16
+
  17
+      Jenkins::Plugin::Proxies.register self, Java.hudson.model.AbstractBuild
  18
+    end
  19
+
  20
+  end
  21
+end
52  lib/jenkins/model/descriptor.rb
... ...
@@ -0,0 +1,52 @@
  1
+
  2
+require 'json'
  3
+
  4
+module Jenkins
  5
+  module Model
  6
+    class Descriptor < Java.hudson.model.Descriptor
  7
+
  8
+      def initialize(name, impl, plugin, java_type)
  9
+        super(Java.org.jruby.RubyObject.java_class)
  10
+        @name, @impl, @plugin, @java_type = name, impl, plugin, java_type
  11
+      end
  12
+
  13
+      def getDisplayName
  14
+        @impl.display_name
  15
+      end
  16
+
  17
+      def getT()
  18
+        @java_type
  19
+      end
  20
+
  21
+      def getConfigPage
  22
+        "/ruby/plugin/views/#{@name}/config.jelly".tap do |path|
  23
+          puts "getConfigPage -> #{path}"
  24
+        end
  25
+      end
  26
+
  27
+      def getGlobalConfigPage
  28
+        "/ruby/plugin/views/#{@name}/global.jelly".tap do |path|
  29
+          puts "getGlobalConfigPage -> #{path}"
  30
+        end
  31
+      end
  32
+
  33
+      def newInstance(request, form)
  34
+        properties = JSON.parse(form.toString(2))
  35
+        properties.delete("kind")
  36
+        properties.delete("stapler-class")
  37
+        instance = @plugin.export(construct(properties))
  38
+        puts "instance created: #{instance}"
  39
+        return instance
  40
+      end
  41
+
  42
+      private
  43
+
  44
+      def construct(attrs)
  45
+        @impl.new(attrs)
  46
+      rescue ArgumentError
  47
+        @impl.new
  48
+      end
  49
+    end
  50
+
  51
+  end
  52
+end
47  lib/jenkins/model/listener.rb
... ...
@@ -0,0 +1,47 @@
  1
+
  2
+module Jenkins
  3
+  module Model
  4
+
  5
+    # Receive/Send events about a running task
  6
+    class Listener
  7
+
  8
+      # the underlying hudson.model.TaskListener object
  9
+      attr_reader :native
  10
+
  11
+      def initialize(native = nil)
  12
+        @native = native
  13
+      end
  14
+
  15
+      ##
  16
+      # Insert a clickable hyperlink into this tasks's output
  17
+      # @param [String] url the link target
  18
+      # @param [String] text the link content
  19
+      def hyperlink(url, text)
  20
+        @native.hyperlink(url, text)
  21
+      end
  22
+
  23
+      ##
  24
+      # Append a message to the task output.
  25
+      # @param [String] msg the message
  26
+      def log(msg)
  27
+        @native.getLogger().write(msg.to_s)
  28
+      end
  29
+
  30
+      ##
  31
+      # Append an error message to the task output.
  32
+      # @param [String] msg the error message
  33
+      def error(msg)
  34
+        @native.error(msg.to_s)
  35
+      end
  36
+
  37
+      ##
  38
+      # Append a fatal error message to the task output
  39
+      # @param [String] msg the fatal error message
  40
+      def fatal(msg)
  41
+        @native.fatalError(msg.to_s)
  42
+      end
  43
+
  44
+      Jenkins::Plugin::Proxies.register self, Java.hudson.util.AbstractTaskListener
  45
+    end
  46
+  end
  47
+end
105  lib/jenkins/plugin.rb
... ...
@@ -0,0 +1,105 @@
  1
+
  2
+require 'pathname'
  3
+
  4
+module Jenkins
  5
+  # Acts as the primary gateway between Ruby and Jenkins
  6
+  # There is one instance of this object for the entire
  7
+  # plugin
  8
+  #
  9
+  # On the Java side, it contains a reference to an instance
  10
+  # of RubyPlugin. These two objects talk to each other to
  11
+  # get things done.
  12
+  class Plugin
  13
+
  14
+    # A list of all the hudson.model.Descriptor objects
  15
+    # of which this plugin is aware *indexed by Wrapper class*
  16
+    #
  17
+    # This is used so that wrappers can always have a single place
  18
+    # to go when they are asked for a descriptor. That way, wrapper
  19
+    # instances can always return the descriptor associated with
  20
+    # their class.
  21
+    #
  22
+    # This may go away.
  23
+    attr_reader :descriptors
  24
+
  25
+    # the instance of jenkins.ruby.RubyPlugin with which this Plugin is associated
  26
+    attr_reader :peer
  27
+
  28
+    # Initializes this plugin by reading the models.rb
  29
+    # file. This is a manual registration process
  30
+    # Where ruby objects register themselves with the plugin
  31
+    # In the future, this process will be automatic, but
  32
+    # I haven't decided the best way to do this yet.
  33
+    #
  34
+    # @param [org.jenkinsci.ruby.RubyPlugin] java a native java RubyPlugin
  35
+    def initialize(java)
  36
+      @java = @peer = java
  37
+      @start = @stop = proc {}
  38
+      @descriptors = {}
  39
+      @proxies = Proxies.new(self)
  40
+      load_models
  41
+
  42
+       # load model definitions
  43
+       # TODO: auto-register them
  44
+       self.instance_eval @java.loadBootScript(), "models.rb"
  45
+    end
  46
+
  47
+    # unique identifier for this plugin in the Jenkins server
  48
+    def name
  49
+      @peer.getWrapper().getShortName()
  50
+    end
  51
+
  52
+    # Called once when Jenkins first initializes this plugin
  53
+    # currently does nothing, but plugin startup hooks would
  54
+    # go here.
  55
+    def start
  56
+      @start.call()
  57
+    end
  58
+
  59
+    # Called one by Jenkins (via RubyPlugin) when this plugin
  60
+    # is shut down. Currently this does nothing, but plugin
  61
+    # shutdown hooks would go here.
  62
+    def stop
  63
+      @stop.call()
  64
+    end
  65
+
  66
+    # Reflect an Java object coming from Jenkins into the context of this plugin
  67
+    # If the object is originally from the ruby plugin, and it was previously
  68
+    # exported, then it will unwrap it. Otherwise, it will just use the object
  69
+    # as a normal Java object.
  70
+    #
  71
+    # @param [Object] object the object to bring in from the outside
  72
+    # @return the best representation of that object for this plugin
  73
+    def import(object)
  74
+      @proxies.import object
  75
+    end
  76
+
  77
+    # Reflect a native Ruby object into its External Java form.
  78
+    #
  79
+    # Delegates to `Proxies` for the heavy lifting.
  80
+    #
  81
+    # @param [Object] object the object
  82
+    # @returns [java.lang.Object] the Java proxy
  83
+    def export(object)
  84
+      @proxies.export object
  85
+    end
  86
+
  87
+    # Link a plugin-local Ruby object to an external Java object.
  88
+    #
  89
+    # see 'Proxies#link`
  90
+    #
  91
+    # @param [Object] internal the object on the Ruby side of the link
  92
+    # @param [java.lang.Object] external the object on the Java side of the link
  93
+    def link(internal, external)
  94
+      @proxies.link internal, external
  95
+    end
  96
+
  97
+    def load_models
  98
+      puts "Trying to load models from "+@java.getScriptDir().getPath()
  99
+      for filename in Dir["#{@java.getScriptDir().getPath()}/models/**/*.rb"]
  100
+        puts "Loading "+filename
  101
+        load filename
  102
+      end
  103
+    end
  104
+  end
  105
+end
20  lib/jenkins/plugin/cli.rb
... ...
@@ -0,0 +1,20 @@
  1
+
  2
+require 'thor'
  3
+
  4
+module Jenkins
  5
+  module Plugins
  6
+    class CLI < Thor
  7
+
  8
+
  9
+      desc "init", "Create a new Jenkins plugin"
  10
+      def init
  11
+
  12
+      end
  13
+
  14
+      desc "gen", "generate extension boilerplate"
  15
+      def gen
  16
+
  17
+      end
  18
+    end
  19
+  end
  20
+end
130  lib/jenkins/plugin/proxies.rb
... ...
@@ -0,0 +1,130 @@
  1
+
  2
+
  3
+module Jenkins
  4
+  class Plugin
  5
+
  6
+    ExportError = Class.new(StandardError)
  7
+    ImportError = Class.new(StandardError)
  8
+
  9
+    # Maps JRuby objects part of the idomatic Ruby API
  10
+    # to a plain Java object representation and vice-versa.
  11
+    #
  12
+    # One of the pillars of Jenkins Ruby plugins is that writing
  13
+    # plugins must "feel" like native Ruby, and not merely like
  14
+    # scripting Java with Ruby. To this end, jenkins-plugins provides
  15
+    # a idiomatic Ruby API that sits on top of the native Jenkins
  16
+    # Java API with which plugin developers can interact.
  17
+    #
  18
+    # This has two consequences. Native Ruby objects authored as part
  19
+    # of the plugin must have a foreign (Java) representation with which
  20
+    # they can wander about the Jenkins universe and possibly interact
  21
+    # with other foreign objects and APIs. Also, Foreign objects
  22
+    # coming in from Jenkins at large should be wrapped, where possible
  23
+    # to present an idomatic interface..
  24
+    #
  25
+    # Finally, Native plugin that had been wrapped and are comping home
  26
+    # must be unwrapped from their external form.
  27
+    #
  28
+    # For all cases, we want to maintain referential integrety so that
  29
+    # the same object always uses the same external form, etc... so
  30
+    # there is one instance of the `Proxies` class per plugin which will
  31
+    # reuse mappings where possible.
  32
+    class Proxies
  33
+
  34
+      def initialize(plugin)
  35
+        @plugin = plugin
  36
+        @int2ext = java.util.WeakHashMap.new
  37
+        @ext2int = java.util.WeakHashMap.new
  38
+      end
  39
+
  40
+      # Reflect a foreign Java object into the context of this plugin.
  41
+      #
  42
+      # If the object is a native plugin object that had been previously
  43
+      # exported, then it will unwrapped.
  44
+      #
  45
+      # Otherwise, we try to choose the best idiomatic API object for
  46
+      # this foreign object
  47
+      #
  48
+      # @param [Object] object the object to bring in from the outside
  49
+      # @return the best representation of that object for this plugin
  50
+      def import(object)
  51
+        if ref = @ext2int[object]
  52
+          return ref.get() if ref.get()
  53
+        end
  54
+        cls = object.class
  55
+        while cls do
  56
+          if internal_class = @@extcls2intcls[cls]
  57
+            internal = internal_class.new(object)
  58
+            link(internal, object)
  59
+            return internal
  60
+          end
  61
+          cls = cls.superclass
  62
+        end
  63
+        raise ImportError, "unable to find suitable representation for #{object.inspect}"
  64
+      end
  65
+
  66
+      # Reflect a native Ruby object into its External Java form.
  67
+      #
  68
+      # Try to find a suitable form for this object and if one is found then decorate it.
  69
+      # @param [Object] object the ruby object that is being exported to Java
  70
+      # @return [java.lang.Object] the Java wrapper that provides an interface to `object`
  71
+      # @throw [ExportError] if no suitable Java representation can be found
  72
+      def export(object)
  73
+        if ref = @int2ext[object]
  74
+          return ref.get() if ref.get()
  75
+        end
  76
+
  77
+        cls = object.class
  78
+        while cls do
  79
+          if proxy_class = @@intcls2extcls[cls]
  80
+            proxy = proxy_class.new(@plugin, object)
  81
+            link(object, proxy)
  82
+            return proxy
  83
+          end
  84
+          cls = cls.superclass
  85
+        end
  86
+        raise ExportError, "unable to find suitable Java Proxy for #{object.inspect}"
  87
+      end
  88
+
  89
+      ##
  90
+      # Link a plugin-local Ruby object to an external Java object such that they will
  91
+      # be subsituted for one another when passing values back and forth between Jenkins
  92
+      # and this plugin. An example of this is associating the Ruby Jenkins::Launcher object
  93
+      # with an equivalent
  94
+      #
  95
+      # @param [Object] internal the object on the Ruby side of the link
  96
+      # @param [java.lang.Object] external the object on the Java side of the link
  97
+      def link(internal, external)
  98
+        @int2ext.put(internal, java.lang.ref.WeakReference.new(external))
  99
+        @ext2int.put(external, java.lang.ref.WeakReference.new(internal))
  100
+      end
  101
+
  102
+      ##
  103
+      # Associated the the Ruby class `internal_class` with the Java class `external_class`.
  104
+      #
  105
+      # Whenever a plugin is importing or exporting an object to the other side, it will first
  106
+      # see if there is an instance already linked. If not, it will try to create the other side
  107
+      # of the link by constructing it via reflection. `register` links two classes together so
  108
+      # that links can be built automatically.
  109
+      #
  110
+      # @param [Class] internal_class the Ruby class
  111
+      # @param [java.lang.Class] external_class the Java class on the othe side of this link.
  112
+      def self.register(internal_class, external_class)
  113
+        @@intcls2extcls[internal_class] = external_class
  114
+        @@extcls2intcls[external_class] = internal_class
  115
+      end
  116
+
  117
+      ##
  118
+      # Remove all class linkages. This is mainly for testing purposes.
  119
+      def self.clear
  120
+        @@intcls2extcls = {}
  121
+        @@extcls2intcls = {}
  122
+      end
  123
+      clear
  124
+    end
  125
+  end
  126
+end
  127
+
  128
+["build_wrapper", "builder"].each do |proxy|
  129
+  require "jenkins/plugin/proxies/#{proxy}"
  130
+end
50  lib/jenkins/plugin/proxies/build_wrapper.rb
... ...
@@ -0,0 +1,50 @@
  1
+
  2
+require 'jenkins/tasks/build_wrapper'
  3
+
  4
+module Jenkins
  5
+  class Plugin
  6
+    class Proxies
  7
+
  8
+      ##
  9
+      # Binds the Java hudson.tasks.BuildWrapper API to the idomatic
  10
+      # Ruby API Jenkins::Tasks::BuildWrapper
  11
+
  12
+      class BuildWrapper < Java.hudson.tasks.BuildWrapper
  13
+        include Java.jenkins.ruby.Get
  14
+        include Jenkins::Plugin::Proxy
  15
+
  16
+        def setUp(build, launcher, listener)
  17
+          env = {}
  18
+          @object.setup(import(build), import(launcher), import(listener), env)
  19
+          EnvironmentWrapper.new(self, @plugin, @object, env)
  20
+        end
  21
+
  22
+        def getDescriptor
  23
+          @plugin.descriptors[@object.class]
  24
+        end
  25
+
  26
+        def get(name)
  27
+          @object.respond_to?(name) ? @object.send(name) : nil
  28
+        end
  29
+
  30
+      end
  31
+
  32
+
  33
+      class EnvironmentWrapper < Java.hudson.tasks.BuildWrapper::Environment
  34
+
  35
+        def initialize(build_wrapper, plugin, impl, env)
  36
+          super(build_wrapper)
  37
+          @plugin = plugin
  38
+          @impl = impl
  39
+          @env = env
  40
+        end
  41
+
  42
+        def tearDown(build, listener)
  43
+          @impl.teardown(@plugin.import(build), @plugin.import(listener), @env)
  44
+        end
  45
+      end
  46
+
  47
+      register Jenkins::Tasks::BuildWrapper, BuildWrapper
  48
+    end
  49
+  end
  50
+end
22  lib/jenkins/plugin/proxies/builder.rb
... ...
@@ -0,0 +1,22 @@
  1
+
  2
+require 'jenkins/tasks/builder'
  3
+
  4
+module Jenkins
  5
+  class Plugin
  6
+    class Proxies
  7
+      class Builder < Java.hudson.tasks.Builder
  8
+        include Jenkins::Plugin::Proxy
  9
+
  10
+        def prebuild(build, launcher, listener)
  11
+          @object.prebuild(import(build), import(launcher), import(listener)) ? true : false
  12
+        end
  13
+
  14
+        def perform(build, launcher, listener)
  15
+          @object.perform(import(build), import(launcher), import(listener)) ? true : false
  16
+        end
  17
+
  18
+      end
  19
+      register Jenkins::Tasks::Builder, Builder
  20
+    end
  21
+  end
  22
+end
76  lib/jenkins/plugin/proxy.rb
... ...
@@ -0,0 +1,76 @@
  1
+
  2
+module Jenkins
  3
+  class Plugin
  4
+
  5
+    ##
  6
+    # The Jenkins Ruby API uses "proxies" which are Java subclasses of the native Jenkins
  7
+    # Java API. These proxies provide the mapping between the Java API and the idomatic
  8
+    # Ruby API. Sometimes these mappings can appear convoluted, but it is only so in order to make
  9
+    # the Ruby side as simple and clean as possible.
  10
+    #
  11
+    # This module provides common functionality for all proxies.
  12
+    module Proxy
  13
+      def self.included(mod)
  14
+        super
  15
+        mod.extend(Marshal)
  16
+        mod.send(:include, Unmarshal)
  17
+        mod.send(:include, Customs)
  18
+      end
  19
+
  20
+      # Every Proxy object has a reference to the plugin to which it belongs, as well as the
  21
+      # native Ruby object which it represents.
  22
+      #
  23
+      # @param [Jenkins::Plugin] plugin the plugin from whence this proxy object came
  24
+      # @param [Object] object the implementation to which this proxy will delegate.
  25
+      def initialize(plugin, object)
  26
+        super() if defined? super
  27
+        @plugin, @object = plugin, object
  28
+        @pluginid = @plugin.name
  29
+      end
  30
+
  31
+      # Make sure that proxy classes do not try to persist the plugin parameter.
  32
+      # when serializing this proxy to XStream. It will be reconstructed with
  33
+      # [Unmarshal#read_completed]
  34
+      module Marshal
  35
+
  36
+        # Tell XStream that we never want to persist the @plugin field
  37
+        # @param [String] field name of the field which xstream is enquiring about
  38
+        # @return [Boolean] true if this is the plugin field, otherwise delegate
  39
+        def transient?(field)
  40
+          field.to_s == "plugin" or (super if defined? super)
  41
+        end
  42
+      end
  43
+
  44
+      # Reanimates Proxy objects after their values have been unserialized from XStream
  45
+      module Unmarshal
  46
+
  47
+        # Once the proxy has been unmarshalled from XStream, re-find the plugin
  48
+        # that it is associated with, and use it to populate the @plugin field.
  49
+        # Also, make sure to associate this proxy with the object it represents
  50
+        # so that they remain referentially equivalent.
  51
+        def read_completed
  52
+          @plugin = Java.jenkins.model.Jenkins.getInstance().getPlugin(@pluginid).getRubyController()
  53
+          @plugin.link @object, self
  54
+        end
  55
+
  56
+      end
  57
+
  58
+      ##
  59
+      # Convenience methods for converting from Ruby API to Java API objects and back
  60
+      module Customs
  61
+
  62
+        ##
  63
+        # convert an external Java object into a Ruby friendly object
  64
+        def import(object)
  65
+          @plugin.import(object)
  66
+        end
  67
+
  68
+        ##
  69
+        # convert an internal Ruby object into a Java proxy that is free to roam about Jenkins-land
  70
+        def export(object)
  71
+          @plugin.export(object)
  72
+        end
  73
+      end
  74
+    end
  75
+  end
  76
+end
13  lib/jenkins/plugin/runtime.rb
... ...
@@ -0,0 +1,13 @@
  1
+require 'jenkins/plugin'
  2
+require 'jenkins/plugin/version'
  3
+require 'jenkins/plugin/proxy'
  4
+require 'jenkins/plugin/proxies'
  5
+require 'jenkins/model'
  6
+require 'jenkins/model/action'
  7
+require 'jenkins/model/build'
  8
+require 'jenkins/model/descriptor'
  9
+require 'jenkins/model/listener'
  10
+require 'jenkins/slaves/cloud'
  11
+require 'jenkins/tasks/builder'
  12
+require 'jenkins/tasks/build_wrapper'
  13
+require 'jenkins/launcher'
5  lib/jenkins/plugin/version.rb
... ...
@@ -0,0 +1,5 @@
  1
+module Jenkins
  2
+  class Plugin
  3
+    VERSION = "0.0.1"
  4
+  end
  5
+end
9  lib/jenkins/slaves/cloud.rb
... ...
@@ -0,0 +1,9 @@
  1
+
  2
+module Jenkins
  3
+  module Slaves
  4
+    class Cloud
  5
+      include Jenkins::Model
  6
+
  7
+    end
  8
+  end
  9
+end
37  lib/jenkins/tasks/build_wrapper.rb
... ...
@@ -0,0 +1,37 @@
  1
+
  2
+require 'jenkins/model'
  3
+
  4
+module Jenkins
  5
+  module Tasks
  6
+
  7
+    # Decorate a build with pre and post hooks.
  8
+    # {http://javadoc.jenkins-ci.org/hudson/tasks/BuildWrapper.html}
  9
+    class BuildWrapper
  10
+      include Jenkins::Model
  11
+
  12
+
  13
+      # Perform setup for a build
  14
+      #
  15
+      # invoked after checkout, but before any `Builder`s have been run
  16
+      # @param [Jenkins::Model::Build] build the build about to run
  17
+      # @param [Jenkins::Launcher] launcher a launcher for the orderly starting/stopping of processes.
  18
+      # @param [Jenkins::Model::Listener] listener channel for interacting with build output console
  19
+      # @param [Hash] env a place to store information needed by #teardown
  20
+      def setup(build, launcher, listener, env)
  21
+        listener.log "hello from a build wrapper"
  22
+      end
  23
+
  24
+      # Optionally perform optional teardown for a build
  25
+      #
  26
+      # invoked after a build has run for better or for worse. It's ok if subclasses
  27
+      # don't override this.
  28
+      #
  29
+      # @param [Jenkins::Model::Build] the build which has completed
  30
+      # @param [Jenkins::Model::Listener] listener channel for interacting with build output console
  31
+      # @param [Hash] env contains anything that #setup needs to tell #teardown about
  32
+      def teardown(build, listener, env)
  33
+
  34
+      end
  35
+    end
  36
+  end
  37
+end
33  lib/jenkins/tasks/builder.rb
... ...
@@ -0,0 +1,33 @@
  1
+
  2
+module Jenkins
  3
+  module Tasks
  4
+    ##
  5
+    # A single step in the entire build process
  6
+    class Builder
  7
+
  8
+      ##
  9
+      # Runs before the build begins
  10
+      #
  11
+      # @param [Jenkins::Model::Build] build the build which will begin
  12
+      # @param [Jenkins::Launcher] launcher the launcher that can run code on the node running this build
  13
+      # @param [Jenkins::Model::Listener] listener the listener for this build.
  14
+      # @return `true` if this build can continue or `false` if there was an error
  15
+      # and the build needs to be aborted
  16
+      def prebuild(build, launcher, listener)
  17
+
  18
+      end
  19
+
  20
+      ##
  21
+      # Runs the step over the given build and reports the progress to the listener.
  22
+      #
  23
+      # @param [Jenkins::Model::Build] build on which to run this step
  24
+      # @param [Jenkins::Launcher] launcher the launcher that can run code on the node running this build
  25
+      # @param [Jenkins::Model::Listener] listener the listener for this build.
  26
+      # return `true if this build can continue or `false` if there was an error
  27
+      # and the build needs to be aborted
  28
+      def perform(build, launcher, listener)
  29
+
  30
+      end
  31
+    end
  32
+  end
  33
+end
8  spec/jenkins/launcher_spec.rb
... ...
@@ -0,0 +1,8 @@
  1
+require 'spec_helper'
  2
+
  3
+describe Jenkins::Launcher do
  4
+
  5
+  it "can be instantiated" do
  6
+    Jenkins::Launcher.new
  7
+  end
  8
+end
36  spec/jenkins/model/action_spec.rb
... ...
@@ -0,0 +1,36 @@
  1
+require 'spec_helper'
  2
+require 'jenkins/model/action'
  3
+describe Jenkins::Model::Action do
  4
+
  5
+  it "has the same display_name semantics as Model" do
  6
+    a = new_action do
  7
+      display_name "CoolAction"
  8
+    end
  9
+    a.display_name.should eql "CoolAction"
  10
+  end
  11
+
  12
+  describe "its icon_file" do
  13
+    it "is nil by default" do
  14
+      new_action.icon.should be_nil
  15
+    end
  16
+    it "can be configured" do
  17
+      action = new_action do
  18
+        icon "foo.png"
  19
+      end
  20
+      action.new.icon.should == "foo.png"
  21
+    end
  22
+  end
  23
+
  24
+  describe "url_name" do
  25
+    it "can be configured"
  26
+  end
  27
+
  28
+  private
  29
+
  30
+  def new_action(&body)
  31
+    action = Class.new
  32
+    action.send(:include, Jenkins::Model::Action)
  33
+    action.class_eval(&body) if block_given?
  34
+    return action
  35
+  end
  36
+end
8  spec/jenkins/model/build_spec.rb
... ...
@@ -0,0 +1,8 @@
  1
+require 'spec_helper'
  2
+
  3
+describe Jenkins::Model::Build do
  4
+
  5
+  it "can be instantiated" do
  6
+    Jenkins::Model::Build.new(mock(Java.hudson.model.AbstractBuild))
  7
+  end
  8
+end
31  spec/jenkins/model/listener_spec.rb
... ...
@@ -0,0 +1,31 @@
  1
+require 'spec_helper'
  2
+
  3
+describe Jenkins::Model::Listener do
  4
+
  5
+  before do
  6
+    @output = java.io.ByteArrayOutputStream.new
  7
+    @java = Java.hudson.util.StreamTaskListener.new(@output)
  8
+    @listener = Jenkins::Model::Listener.new(@java)
  9
+  end
  10
+
  11
+  it "logs messages" do
  12
+    @listener.log('Hi')
  13
+    @output.toString.should eql "Hi"
  14
+  end
  15
+
  16
+  it "logs errors" do
  17
+    @listener.error('Oh no!')
  18
+    @output.toString.should match /^ERROR: Oh no!/
  19
+  end
  20
+
  21
+  it "logs fatal errors" do
  22
+    @listener.fatal('boom!')
  23
+    @output.toString.should match /^FATAL: boom!/
  24
+  end
  25
+
  26
+  it "logs hyperlinks" do
  27
+    @java.should_receive(:hyperlink).with("/foo/bar", "click here")
  28
+    @listener.hyperlink("/foo/bar", "click here")
  29
+  end
  30
+end
  31
+
71  spec/jenkins/model_spec.rb
... ...
@@ -0,0 +1,71 @@
  1
+require 'spec_helper'
  2
+
  3
+describe Jenkins::Model do
  4
+
  5
+  it "has a display name which is settable via the class, and accessable via the class and instance" do
  6
+    cls = new_model do
  7
+      display_name "One-Off Class"
  8
+    end
  9
+    cls.display_name.should eql "One-Off Class"
  10
+    cls.new.display_name.should eql "One-Off Class"
  11
+  end
  12
+  
  13
+  it "passes down display_name capabilities to subclasses" do
  14
+    parent = new_model
  15
+    child = Class.new(parent)
  16
+    child.class_eval do
  17
+      display_name "Child"
  18
+    end
  19
+    child.display_name.should eql "Child"
  20
+  end
  21
+  
  22
+  it "passes down display_name capabilities to submodules" do
  23
+    submodule = Module.new
  24
+    submodule.send(:include, Jenkins::Model)
  25
+    cls = Class.new
  26
+    cls.send(:include, submodule)
  27
+    cls.display_name "SubAwesome"
  28
+    cls.display_name.should eql "SubAwesome"
  29
+    cls.new.display_name.should eql "SubAwesome"
  30
+  end
  31
+  
  32
+  it "has a default display name of the class name" do
  33
+    cls = new_model do
  34
+      def self.name
  35
+        "AwesomeClass"
  36
+      end
  37
+    end
  38
+    cls.display_name.should eql "AwesomeClass"
  39
+  end
  40
+
  41
+  it "keeps a list of which of its properties are transient" do
  42
+    cls = new_model do
  43
+      transient :foo, :bar
  44
+    end
  45
+    cls.should be_transient(:foo)
  46
+    cls.should be_transient(:bar)
  47
+    cls.should_not be_transient(:baz)
  48
+  end
  49
+
  50
+  it "includes parent classes's transient properties, but doesn't affect the parent property list" do
  51
+    parent = new_model do
  52
+      transient :foo
  53
+    end
  54
+    child = Class.new(parent)
  55
+    child.class_eval do
  56
+      transient :bar
  57
+    end
  58
+    parent.should_not be_transient(:bar)
  59
+    child.should be_transient(:foo)
  60
+    child.should be_transient(:bar)
  61
+  end
  62
+
  63
+  private
  64
+  
  65
+  def new_model(&block)
  66
+    cls = Class.new
  67
+    cls.send(:include, Jenkins::Model)
  68
+    cls.class_eval(&block) if block_given?
  69
+    return cls
  70
+  end
  71
+end
7  spec/jenkins/plugin_spec.rb
... ...
@@ -0,0 +1,7 @@
  1
+require 'spec_helper'
  2
+
  3
+describe Jenkins::Plugin do
  4
+  it "is unbelievable that I don't have a spec for this class" do
  5
+    Jenkins::Plugin.new
  6
+  end
  7
+end
22  spec/jenkins/plugins/proxies/build_wrapper_spec.rb
... ...
@@ -0,0 +1,22 @@
  1
+
  2
+require 'spec_helper'
  3
+
  4
+describe Jenkins::Plugin::Proxies::BuildWrapper do
  5
+  include ProxyHelper
  6
+
  7
+  before do
  8
+    @object = mock(Jenkins::Tasks::BuildWrapper)
  9
+    @wrapper = Jenkins::Plugin::Proxies::BuildWrapper.new(@plugin, @object)
  10
+  end
  11
+
  12
+  it "passes in an env file which will be called to " do
  13
+    env = nil
  14
+    @object.should_receive(:setup).with(@build, @launcher, @listener, an_instance_of(Hash)) do |*args|
  15
+      env = args.last
  16
+    end
  17
+    environment = @wrapper.setUp(@jBuild, @jLauncher, @jListener)
  18
+
  19
+    @object.should_receive(:teardown).with(@build, @listener, env)
  20
+    environment.tearDown(@jBuild, @jListener)
  21
+  end
  22
+end
24  spec/jenkins/plugins/proxies/builder_spec.rb
... ...
@@ -0,0 +1,24 @@
  1
+require 'spec_helper'
  2
+
  3
+describe Jenkins::Plugin::Proxies::Builder do
  4
+  include ProxyHelper
  5
+
  6
+  before do
  7
+    @object = mock(Jenkins::Tasks::Builder)
  8
+    @builder = Jenkins::Plugin::Proxies::Builder.new(@plugin, @object)
  9
+  end
  10
+
  11
+  describe "prebuild" do
  12
+    it "calls through to its implementation" do
  13
+      @object.should_receive(:prebuild).with(@build, @launcher, @listener)
  14
+      @builder.prebuild(@jBuild, @jLauncher, @jListener)
  15
+    end
  16
+  end
  17
+
  18
+  describe "perform" do
  19
+    it "calls through to its implementation" do
  20
+      @object.should_receive(:perform).with(@build, @launcher, @listener)
  21
+      @builder.perform(@jBuild, @jLauncher, @jListener)
  22
+    end
  23
+  end
  24
+end
24  spec/jenkins/plugins/proxies/proxy_helper.rb
... ...
@@ -0,0 +1,24 @@
  1
+
  2
+module ProxyHelper
  3
+
  4
+  def self.included(mod)
  5
+    super
  6
+    mod.class_eval do
  7
+      before do
  8
+        @plugin = mock(Jenkins::Plugin, :name => "test-plugin")
  9
+
  10
+        @jBuild = mock(Java.hudson.model.AbstractBuild)
  11
+        @jLauncher = mock(Java.hudson.Launcher)
  12
+        @jListener = mock(Java.hudson.model.BuildListener)
  13
+
  14
+        @build = mock(Jenkins::Model::Build)
  15
+        @launcher = mock(Jenkins::Launcher)
  16
+        @listener = mock(Jenkins::Model::Listener)
  17
+
  18
+        @plugin.stub(:import).with(@jBuild).and_return(@build)
  19
+        @plugin.stub(:import).with(@jLauncher).and_return(@launcher)
  20
+        @plugin.stub(:import).with(@jListener).and_return(@listener)
  21
+      end
  22
+    end
  23
+  end
  24
+end
148  spec/jenkins/plugins/proxies_spec.rb
... ...
@@ -0,0 +1,148 @@
  1
+require 'spec_helper'
  2
+
  3
+describe Jenkins::Plugin::Proxies do
  4
+  Proxies = Jenkins::Plugin::Proxies
  5
+  before  do
  6
+    Proxies.clear
  7
+    @plugin = mock(Jenkins::Plugin)
  8
+    @proxies = Jenkins::Plugin::Proxies.new(@plugin)
  9
+  end
  10
+
  11
+  describe "exporting a native ruby object" do
  12
+
  13
+    before do
  14
+      @class = Class.new
  15
+      @object = @class.new
  16
+    end
  17
+
  18
+    describe "when no wrapper exists for it" do
  19
+
  20
+      describe "and there is a matching proxy class registered" do
  21
+        before do
  22
+          @proxy_class = Class.new
  23
+          @proxy_class.class_eval do
  24
+            attr_reader :plugin, :object
  25
+            def initialize(plugin, object)
  26
+              @plugin, @object = plugin, object
  27
+            end
  28
+          end
  29
+          Jenkins::Plugin::Proxies.register @class, @proxy_class
  30
+          @export = @proxies.export(@object)
  31
+        end
  32
+
  33
+        it "instantiates the proxy" do
  34
+          @export.should be_kind_of(@proxy_class)
  35
+        end
  36
+
  37
+        it "passes in the plugin and the wrapped object" do
  38
+          @export.plugin.should be(@plugin)
  39
+          @export.object.should be(@object)
  40
+        end
  41
+      end
  42
+
  43
+      describe "and there is not an appropriate proxy class registered for it" do
  44
+        it "raises an exception on import" do
  45
+          expect {@proxies.export(@object)}.should raise_error
  46
+        end
  47
+      end
  48
+    end
  49
+
  50
+    describe "when a wrapper has already existed" do
  51
+      before do
  52
+        @proxy = Object.new
  53
+        @proxies.link @object, @proxy
  54
+      end
  55