Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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

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