|
<%= link_to project.name, :action => 'show', :id => project.name %>
<% recent_builds = project.last_five_builds %>
<%= recent_builds.first ? 'build ' : 'Never built' %>
<%= link_to image_tag('rss_light.gif'), :action => 'show', :id => project.name, :format => 'rss' %>
<% unless recent_builds.empty? %>
<% recent_builds.each do |build| %>
<% if build == recent_builds.first %>
<%= link_to_build_with_elapsed_time(project, build) %>
<% else %>
<%= link_to_build(project, build) %>
<% end %>
<% end %>
<% end %>
|
...
Asynchronous requests via form remote tag : projects/_project.rhtml
RJS Response : projects/index_js.rjs
Builder used for xml output : projects/index_cctray.rxml
Scripts and Daemon
The cruise command is a simple shell script that delegates to one of the server, add_project or builder scripts depending on the command line arguments.
server is just a simple wrapper around the rails builtin server script. It starts the dashboard rails app with mongrel.
add project creates a new project with options passed on the command line
builder builder runs a projects scheduler loop. There can be many builders running at once.
cruise executable script : cruise
add_project script : scripts/add_project
builder script : scripts/builder
The Model
The meat and gravy of the application is in the model (as it should be). There are three domain objects represented in the model -
Project, Build and SCM (source control manager). The Project has many responsibilities including configuration, persistence and the build system.
The project class delegates many of these reponsibilities to helper classes including BuilderStarter, BuilderStatus, PollingScheduler, ProjectConfigTracker,
and BuildSerializer. Even with this refactoring the Project class is still over 500+ lines, further refactoring using delegates or mixins would
definitely help the code readability.
Associations
Cruise control doesn't use Activerecord for persistence, instead it uses the filesystem. A project is stored as a directory in the
[cruise data]/projects directory, the project has a configuration file, a working copy of the source code, and a directory for each of the builds.
There are finder methods for Projects and Builds- these query the filesystem in order to locate the desired resource.
Project
build
builds
create_build
find_build
last_build
last_builds
last_complete_build
last_complete_build_status
last_five_builds
next_build
previous_build
Filesystem used to store associations : project.rb
Initialization
Projects
load_all
load_project
Project
configure
read
instantiate_plugins
load_and_remember
load_config
load_timestamp
On initialization the project loads the central configuration file and the local configuration file. The configuration files are written
in ruby and are expected to use Project.configure to set configuration attributes on the project. There is a clever trick here in setting a
class variable to the current project on initialisation, this provides a means of accessing the current project in the configuration file
Initialisation method stores project reference : project.rb
Example project configuration file : cruise_config.rb
The Build System
Project
build_command
build_command=
build_if_necessary
build_if_requested
build_necessary?
build_requested?
build_requested_flag_file
build_without_serialization
builder_error_message
builder_state_and_activity
error_message
log_changeset
request_build
save_timestamp
scheduler
scheduler=
BuilderStarter
begin_builder
path_to_cruise
run_builders_at_startup=
start_builders
BuildSerializer
serialize
serialize
timeout
wait
The controller can start a build by calling request_build on a project. This method delegates starting the
build to the BuilderStarter class. The build is started asynchronously by creating a new process which executes
the builder script. The build request is stored in a flag file so that the build process is aware of the request.
Asynchronous build request : build_starter.rb
The builder script runs the project's scheduler loop, which calls build_if_necessary or build_if_requested to start a build.
The build is started with the build method, which in turn delegates to build_without_serialization (serialization is performed by a
BuildSerializer if required).
Scheduler Loop : polling_scheduler.rb
A Build object is created which controls the build and provides access to and persistence for results. The run method creates
the appropriate environment and runs the project build command or rake task.
Build's run method : build.rb
Triggers
Project
build_necessary?
triggered_by
triggered_by=
ChangeInSourceControlTrigger
build_necessary?
SuccessfulBuildTrigger
build_necessary?
build_necessary? delegates to the projects triggers. Triggers are simple objects which respond to build_necessary?.
Cruise control ships with two triggers ChangeInSourceControlTrigger and SuccessfulBuildTrigger.
build_necessary? method : project.rb
ChangeInSourceControlTrigger : change_in_source_control_trigger.rb
Plugins
Project
plugin
add_plugin
method_missing
notify
plugins
respond_to?
BuildReaper
build_finished
EmailNotifier
build_finished
build_fixed
BuilderStatus
build_finished
build_initiated
build_loop_failed
build_requested
polling_source_control
queued
sleeping
timed_out
ProjectLogger
build_finished
build_loop_failed
build_started
new_revisions_detected
no_new_revisions_detected
polling_source_control
sleeping
Each project has a set of plugins which are notified of events in the build sequence. A plugin can respond to
these events by defining a method of the same name as the event. The set of possible events is dynamic and events are despatched from several places
including the project build methods, and the triggers. Some of the events include
configuration_modified
build_initiated
build_started
build_finished
build_broken
build_fixed
build_requested
build_loop_failed
sleeping
notify method : project.rb
E-mail notifier plugin : email_notifier.rb
Source Control
Project
source_control
update_project_to_revision
Subversion
check_externals
checkout
clean_checkout
latest_revision
up_to_date?
update
The build system interacts with the source control using three methods
latest_revision(project), revisions_since(project, revision_number) and update(project, revision = nil).
The add project script also requires a checkout(revision = nil) method.
Cruise Control currently ships with only a Subversion source control manager, but git and mercurial support are in the works and are available
from the projects git repository.
update_to_latest_revision method : project.rb
Subversion client : subversion.rb