Skip to content

Commit

Permalink
Merge pull request #1797 from mattbrictson/scm-plugin-class
Browse files Browse the repository at this point in the history
Change SCMs to inherit from new SCM::Plugin base class
  • Loading branch information
mattbrictson committed Oct 27, 2016
2 parents ff686ef + 856628d commit 6faef80
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 26 deletions.
15 changes: 8 additions & 7 deletions docs/documentation/advanced-features/custom-scm/index.markdown
Expand Up @@ -20,20 +20,20 @@ Capistrano checks out your application's source code. SCM plugins can be
packaged as Ruby gems and distributed to other users.

This document is a short guide to writing your own plugin. *It applies to
Capistrano 3.5.0 and newer.*
Capistrano 3.7.0 and newer.*

### 1. Write a Ruby class that extends Capistrano::Plugin
### 1. Write a Ruby class that extends Capistrano::SCM::Plugin

Let's say you want to create a "Foo" SCM. You'll need to write a plugin class,
like this:

```ruby
require "capistrano/plugin"
require "capistrano/scm/plugin"

# By convention, Capistrano plugins are placed in the
# Capistrano namespace. This is completely optional.
module Capistrano
class FooPlugin < ::Capistrano::Plugin
class FooPlugin < ::Capistrano::SCM::Plugin
def set_defaults
# Define any variables needed to configure the plugin.
# set_if_empty :myvar, "my-default-value"
Expand All @@ -42,11 +42,13 @@ module Capistrano
end
```

### 2. Implement the create_release task
### 2. Implement a create_release task

When the user runs `cap deploy`, your SCM is responsible for creating the
release directory and copying the application source code into it. You need to
do this using a `create_release` task that is namespaced to your plugin.
do this using a task that is registered to run after `deploy:new_release_path`.

By convention (not a requirement), this task is called `create_release`.

Inside your plugin class, use the `define_tasks` and `register_hooks` methods
like this:
Expand All @@ -56,7 +58,6 @@ def define_tasks
# The namespace can be whatever you want, but its best
# to choose a name that matches your plugin name.
namespace :foo do
# The task *must* be named `create_release`
task :create_release do
# Your code to create the release directory and copy
# the source code into it goes here.
Expand Down
4 changes: 4 additions & 0 deletions lib/capistrano/configuration.rb
Expand Up @@ -140,6 +140,10 @@ def install_plugin(plugin, load_hooks: true, load_immediately: false)
load_immediately: load_immediately)
end

def scm_plugin_installed?
installer.scm_installed?
end

def servers
@servers ||= Servers.new
end
Expand Down
11 changes: 11 additions & 0 deletions lib/capistrano/configuration/plugin_installer.rb
Expand Up @@ -26,6 +26,7 @@ def install(plugin, load_hooks: true, load_immediately: false)

plugin.define_tasks
plugin.register_hooks if load_hooks
@scm_installed ||= provides_scm?(plugin)

if load_immediately
plugin.set_defaults
Expand All @@ -35,6 +36,16 @@ def install(plugin, load_hooks: true, load_immediately: false)
end
end
end

def scm_installed?
@scm_installed
end

private

def provides_scm?(plugin)
plugin.respond_to?(:scm?) && plugin.scm?
end
end
end
end
12 changes: 3 additions & 9 deletions lib/capistrano/configuration/scm_resolver.rb
Expand Up @@ -29,7 +29,8 @@ def resolve
set(:scm, :git) if using_default_scm?

print_deprecation_warnings_if_applicable
return if scm_plugin_loaded?
# Note that `scm_plugin_installed?` comes from Capistrano::DSL
return if scm_plugin_installed?

if built_in_scm_name?
load_built_in_scm
Expand All @@ -47,13 +48,6 @@ def using_default_scm?
@using_default_scm = (fetch(:scm) == DEFAULT_GIT)
end

# This is somewhat of a hack, because there is no guarantee that a third-
# party SCM will necessarily implement the typical SCM tasks. But it works
# well enough for the built-in SCMs.
def scm_plugin_loaded?
Rake::Task.tasks.any? { |t| t.name =~ /^[^:]+:create_release/ }
end

def scm_name
fetch(:scm)
end
Expand Down Expand Up @@ -102,7 +96,7 @@ def register_legacy_scm_hooks

def print_deprecation_warnings_if_applicable
if using_default_scm?
warn_add_git_to_capfile unless scm_plugin_loaded?
warn_add_git_to_capfile unless scm_plugin_installed?
elsif built_in_scm_name?
warn_set_scm_is_deprecated
elsif third_party_scm_name?
Expand Down
2 changes: 1 addition & 1 deletion lib/capistrano/dsl/env.rb
Expand Up @@ -8,7 +8,7 @@ module Env
:configure_backend, :fetch, :set, :set_if_empty, :delete,
:ask, :role, :server, :primary, :validate, :append,
:remove, :dry_run?, :install_plugin, :any?, :is_question?,
:configure_scm
:configure_scm, :scm_plugin_installed?

def roles(*names)
env.roles_for(names.flatten)
Expand Down
5 changes: 2 additions & 3 deletions lib/capistrano/scm/git.rb
@@ -1,7 +1,6 @@
require "capistrano/plugin"
require "capistrano/scm"
require "capistrano/scm/plugin"

class Capistrano::SCM::Git < Capistrano::Plugin
class Capistrano::SCM::Git < Capistrano::SCM::Plugin
def set_defaults
set_if_empty :git_shallow_clone, false
set_if_empty :git_wrapper_path, lambda {
Expand Down
5 changes: 2 additions & 3 deletions lib/capistrano/scm/hg.rb
@@ -1,7 +1,6 @@
require "capistrano/plugin"
require "capistrano/scm"
require "capistrano/scm/plugin"

class Capistrano::SCM::Hg < Capistrano::Plugin
class Capistrano::SCM::Hg < Capistrano::SCM::Plugin
def register_hooks
after "deploy:new_release_path", "hg:create_release"
before "deploy:check", "hg:check"
Expand Down
13 changes: 13 additions & 0 deletions lib/capistrano/scm/plugin.rb
@@ -0,0 +1,13 @@
require "capistrano/plugin"
require "capistrano/scm"

# Base class for all built-in and third-party SCM plugins. Notice that this
# class doesn't really do anything other than provide an `scm?` predicate. This
# tells Capistrano that the plugin provides SCM functionality. All other plugin
# features are inherited from Capistrano::Plugin.
#
class Capistrano::SCM::Plugin < Capistrano::Plugin
def scm?
true
end
end
5 changes: 2 additions & 3 deletions lib/capistrano/scm/svn.rb
@@ -1,7 +1,6 @@
require "capistrano/plugin"
require "capistrano/scm"
require "capistrano/scm/plugin"

class Capistrano::SCM::Svn < Capistrano::Plugin
class Capistrano::SCM::Svn < Capistrano::SCM::Plugin
def register_hooks
after "deploy:new_release_path", "svn:create_release"
before "deploy:check", "svn:check"
Expand Down
98 changes: 98 additions & 0 deletions spec/lib/capistrano/configuration/plugin_installer_spec.rb
@@ -0,0 +1,98 @@
require "spec_helper"
require "capistrano/plugin"
require "capistrano/scm/plugin"

module Capistrano
class Configuration
class ExamplePlugin < Capistrano::Plugin
def set_defaults
set_if_empty :example_variable, "foo"
end

def define_tasks
task :example
task :example_prerequisite
end

def register_hooks
before :example, :example_prerequisite
end
end

class ExampleSCMPlugin < Capistrano::SCM::Plugin
end

describe PluginInstaller do
include Capistrano::DSL

let(:installer) { PluginInstaller.new }
let(:options) { {} }
let(:plugin) { ExamplePlugin.new }

before do
installer.install(plugin, **options)
end

after do
Rake::Task.clear
Capistrano::Configuration.reset!
end

context "installing plugin" do
it "defines tasks" do
expect(Rake::Task[:example]).to_not be_nil
expect(Rake::Task[:example_prerequisite]).to_not be_nil
end

it "registers hooks" do
task = Rake::Task[:example]
expect(task.prerequisites).to eq([:example_prerequisite])
end

it "sets defaults when load:defaults is invoked" do
expect(fetch(:example_variable)).to be_nil
invoke "load:defaults"
expect(fetch(:example_variable)).to eq("foo")
end

it "doesn't say an SCM is installed" do
expect(installer.scm_installed?).to be_falsey
end
end

context "installing plugin class" do
let(:plugin) { ExamplePlugin }

it "defines tasks" do
expect(Rake::Task[:example]).to_not be_nil
expect(Rake::Task[:example_prerequisite]).to_not be_nil
end
end

context "installing plugin without hooks" do
let(:options) { { load_hooks: false } }

it "doesn't register hooks" do
task = Rake::Task[:example]
expect(task.prerequisites).to be_empty
end
end

context "installing plugin and loading immediately" do
let(:options) { { load_immediately: true } }

it "sets defaults immediately" do
expect(fetch(:example_variable)).to eq("foo")
end
end

context "installing an SCM plugin" do
let(:plugin) { ExampleSCMPlugin }

it "says an SCM is installed" do
expect(installer.scm_installed?).to be_truthy
end
end
end
end
end

0 comments on commit 6faef80

Please sign in to comment.