shanty 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/lib/shanty.rb +11 -21
- data/lib/shanty/cli.rb +35 -19
- data/lib/shanty/discoverer.rb +9 -3
- data/lib/shanty/discoverers/rubygem.rb +17 -0
- data/lib/shanty/discoverers/shantyfile.rb +9 -2
- data/lib/shanty/env.rb +51 -0
- data/lib/shanty/graph.rb +43 -32
- data/lib/shanty/mixins/acts_as_link_graph_node.rb +0 -10
- data/lib/shanty/mixins/callbacks.rb +20 -8
- data/lib/shanty/mutator.rb +10 -3
- data/lib/shanty/mutators/bundler.rb +13 -0
- data/lib/shanty/mutators/changed.rb +78 -0
- data/lib/shanty/plugin.rb +6 -13
- data/lib/shanty/project.rb +12 -4
- data/lib/shanty/project_template.rb +13 -9
- data/lib/shanty/projects/{ruby.rb → rubygem.rb} +1 -1
- data/lib/shanty/task_env.rb +24 -0
- data/lib/shanty/task_set.rb +45 -0
- data/lib/shanty/{tasks → task_sets}/basic.rb +16 -11
- metadata +24 -126
- data/lib/shanty/discoverers/gradle.rb +0 -18
- data/lib/shanty/global.rb +0 -49
- data/lib/shanty/mutators/git.rb +0 -24
- data/lib/shanty/task.rb +0 -45
- data/lib/shanty/vcs_range.rb +0 -31
- data/lib/shanty/vcs_ranges/local_git.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1dc7e7d6baa3b74a90fb389dee1d33fc8959af0
|
4
|
+
data.tar.gz: 597c9fa320428c48c3ca7e100048e7fdb50a982b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab037eae56ab017500c24a0b3e23e178763953736e43a115201851aefba6f38b009c77638281608bdb2c8181d035049ac1ef136a2fb1fe1fd2a99fa38acb1b66
|
7
|
+
data.tar.gz: 43122c363848e085b35dcbd76956a0e51127ff9ebf38bdeaac49b81138a4d42a1e78a9bd5859bd38d66d3383d27811254b0c152180b5a6b771fc9af176fdc905
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Shanty [![Build Status](https://travis-ci.org/shantytown/shanty.svg?branch=master)](https://travis-ci.org/shantytown/shanty)[![Coverage Status](https://coveralls.io/repos/shantytown/shanty/badge.png?branch=master)](https://coveralls.io/r/shantytown/shanty?branch=master)
|
1
|
+
# Shanty [![Build Status](https://travis-ci.org/shantytown/shanty.svg?branch=master)](https://travis-ci.org/shantytown/shanty) [![Coverage Status](https://coveralls.io/repos/shantytown/shanty/badge.png?branch=master)](https://coveralls.io/r/shantytown/shanty?branch=master)
|
2
2
|
|
3
3
|
**Shanty** is a project orchestration tool. It is designed to make it easy for you to build dependencies between projects, and execute tasks across this tree of relationships, regardless of what language or technology these projects use. The aim is consistency in the way you manage, build, test and deploy your projects.
|
4
4
|
|
@@ -31,7 +31,7 @@ We welcome any contribution, whether big or small! We're busy importing our plan
|
|
31
31
|
|
32
32
|
The MIT License (MIT)
|
33
33
|
|
34
|
-
Copyright (c)
|
34
|
+
Copyright (c) 2015 Chris Jansen, Nathan Kleyn
|
35
35
|
|
36
36
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
37
37
|
of this software and associated documentation files (the "Software"), to deal
|
data/lib/shanty.rb
CHANGED
@@ -4,23 +4,27 @@ require 'pry'
|
|
4
4
|
|
5
5
|
require 'shanty/cli'
|
6
6
|
require 'shanty/discoverers/shantyfile'
|
7
|
+
require 'shanty/discoverers/rubygem'
|
8
|
+
require 'shanty/env'
|
7
9
|
require 'shanty/graph'
|
8
|
-
require 'shanty/
|
9
|
-
require 'shanty/mutators/
|
10
|
-
require 'shanty/
|
10
|
+
require 'shanty/mutators/bundler'
|
11
|
+
require 'shanty/mutators/changed'
|
12
|
+
require 'shanty/plugins/rspec'
|
13
|
+
require 'shanty/plugins/rubocop'
|
14
|
+
require 'shanty/task_env'
|
15
|
+
require 'shanty/task_sets/basic'
|
11
16
|
|
12
17
|
module Shanty
|
13
18
|
# Main shanty class
|
14
19
|
class Shanty
|
20
|
+
# This is the root directory where the Shanty gem is located. Do not confuse this with the root of the repository
|
21
|
+
# in which Shanty is operating, which is available via the TaskEnv class.
|
15
22
|
GEM_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
16
23
|
|
17
24
|
def start!
|
18
25
|
setup_i18n
|
19
|
-
Cli.new(graph).run
|
20
|
-
end
|
21
26
|
|
22
|
-
|
23
|
-
@graph ||= construct_project_graph
|
27
|
+
Cli.new(TaskEnv.new(Env.new)).run
|
24
28
|
end
|
25
29
|
|
26
30
|
private
|
@@ -29,19 +33,5 @@ module Shanty
|
|
29
33
|
I18n.enforce_available_locales = true
|
30
34
|
I18n.load_path = Dir[File.join(GEM_ROOT, 'translations', '*.yml')]
|
31
35
|
end
|
32
|
-
|
33
|
-
def construct_project_graph
|
34
|
-
project_templates = Dir.chdir(Global.root) do
|
35
|
-
Discoverer.new.discover_all
|
36
|
-
end
|
37
|
-
|
38
|
-
projects = project_templates.map do |project_template|
|
39
|
-
project_template.type.new(project_template)
|
40
|
-
end
|
41
|
-
|
42
|
-
graph = Graph.new(projects)
|
43
|
-
|
44
|
-
Mutator.new.apply_mutations(graph)
|
45
|
-
end
|
46
36
|
end
|
47
37
|
end
|
data/lib/shanty/cli.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'commander'
|
2
2
|
require 'i18n'
|
3
|
-
require 'shanty/
|
3
|
+
require 'shanty/task_set'
|
4
4
|
|
5
5
|
module Shanty
|
6
6
|
# Public: Handle the CLI interface between the user and the registered tasks
|
@@ -8,13 +8,13 @@ module Shanty
|
|
8
8
|
class Cli
|
9
9
|
include Commander::Methods
|
10
10
|
|
11
|
-
def initialize(
|
12
|
-
@
|
11
|
+
def initialize(task_env)
|
12
|
+
@task_env = task_env
|
13
13
|
end
|
14
14
|
|
15
15
|
def tasks
|
16
|
-
|
17
|
-
acc.merge(
|
16
|
+
TaskSet.task_sets.reduce({}) do |acc, task_set|
|
17
|
+
acc.merge(task_set.tasks)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -36,28 +36,44 @@ module Shanty
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def setup_task(name, task)
|
39
|
-
command
|
39
|
+
command(name) do |c|
|
40
40
|
c.description = I18n.t(task[:desc], default: task[:desc])
|
41
|
+
c.syntax = task[:syntax]
|
42
|
+
add_options_to_command(task, c)
|
43
|
+
add_action_to_command(name, task, c)
|
44
|
+
end
|
45
|
+
end
|
41
46
|
|
42
|
-
|
43
|
-
|
44
|
-
|
47
|
+
def add_options_to_command(task, command)
|
48
|
+
task[:options].each do |option_name, option|
|
49
|
+
command.option(syntax_for_option(option_name, option), I18n.t(option[:desc], default: option[:desc]))
|
50
|
+
end
|
51
|
+
end
|
45
52
|
|
46
|
-
|
47
|
-
|
48
|
-
|
53
|
+
def add_action_to_command(name, task, command)
|
54
|
+
command.action do |args, options|
|
55
|
+
task = tasks[name]
|
56
|
+
options.default(Hash[defaults_for_options(task)])
|
57
|
+
execute_task(name, task, args, options)
|
49
58
|
end
|
50
59
|
end
|
51
60
|
|
52
|
-
def execute_task(name, args, options)
|
53
|
-
|
61
|
+
def execute_task(name, task, args, options)
|
62
|
+
# We use allocate here beccause we do not want this to blow up because the class defines a constructor.
|
63
|
+
# We cannot and do not support taskset classes needing constructors.
|
64
|
+
klass = task[:klass].allocate
|
65
|
+
arity = klass.method(name).arity
|
54
66
|
|
55
|
-
|
67
|
+
args.unshift(@task_env) if arity >= 2
|
68
|
+
args.unshift(options) if arity >= 1
|
69
|
+
|
70
|
+
klass.send(name, *args)
|
71
|
+
end
|
72
|
+
|
73
|
+
def defaults_for_options(task)
|
74
|
+
task[:options].map do |option_name, option|
|
56
75
|
[option_name, default_for_type(option)]
|
57
76
|
end
|
58
|
-
options.default(Hash[default_option_pairs])
|
59
|
-
|
60
|
-
task[:klass].new.send(name, options, @graph, *args)
|
61
77
|
end
|
62
78
|
|
63
79
|
def syntax_for_option(name, option)
|
@@ -65,7 +81,7 @@ module Shanty
|
|
65
81
|
when :boolean
|
66
82
|
"--#{name}"
|
67
83
|
else
|
68
|
-
"--#{name} #{option[:type].upcase}"
|
84
|
+
"--#{name} #{(option[:type] || 'string').upcase}"
|
69
85
|
end
|
70
86
|
|
71
87
|
option[:required] ? "[#{syntax}]" : syntax
|
data/lib/shanty/discoverer.rb
CHANGED
@@ -10,6 +10,12 @@ module Shanty
|
|
10
10
|
attr_reader :discoverers
|
11
11
|
end
|
12
12
|
|
13
|
+
attr_reader :env
|
14
|
+
|
15
|
+
def initialize(env)
|
16
|
+
@env = env
|
17
|
+
end
|
18
|
+
|
13
19
|
def self.inherited(discoverer)
|
14
20
|
Util.logger.debug("Detected project discoverer #{discoverer}")
|
15
21
|
@discoverers ||= []
|
@@ -18,14 +24,14 @@ module Shanty
|
|
18
24
|
|
19
25
|
def discover_all
|
20
26
|
self.class.discoverers.flat_map do |discoverer|
|
21
|
-
discoverer.new.discover
|
27
|
+
discoverer.new(env).discover
|
22
28
|
end
|
23
29
|
end
|
24
30
|
|
25
31
|
private
|
26
32
|
|
27
|
-
def
|
28
|
-
ProjectTemplate.new(*args)
|
33
|
+
def create_project_template(*args)
|
34
|
+
ProjectTemplate.new(Dir.pwd, *args).tap { |pt| yield pt }
|
29
35
|
end
|
30
36
|
end
|
31
37
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'shanty/discoverer'
|
2
|
+
require 'shanty/projects/rubygem'
|
3
|
+
|
4
|
+
module Shanty
|
5
|
+
# Public: Discoverer for Shantyfiles
|
6
|
+
# will create a a project for every Shantyfile it finds in
|
7
|
+
# a directory
|
8
|
+
class RubygemDiscoverer < Discoverer
|
9
|
+
def discover
|
10
|
+
Dir[File.join(env.root, '**', '*.gemspec')].map do |path|
|
11
|
+
create_project_template(File.absolute_path(File.dirname(path))) do |project_template|
|
12
|
+
project_template.type = RubygemProject
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -5,10 +5,17 @@ module Shanty
|
|
5
5
|
# Public: Discoverer for Shantyfiles
|
6
6
|
# will create a a project for every Shantyfile it finds in
|
7
7
|
# a directory
|
8
|
+
#
|
9
|
+
# Note that this does not execute the Shantyfile. That
|
10
|
+
# happens inside the ProjectTemplate as we may have projects
|
11
|
+
# discovered by other discoverers that still need
|
12
|
+
# customisation.
|
8
13
|
class ShantyfileDiscoverer < Discoverer
|
9
14
|
def discover
|
10
|
-
Dir['
|
11
|
-
|
15
|
+
Dir[File.join(env.root, '**', 'Shantyfile')].map do |path|
|
16
|
+
create_project_template(File.absolute_path(File.dirname(path))) do |project_template|
|
17
|
+
project_template.priority = -1
|
18
|
+
end
|
12
19
|
end
|
13
20
|
end
|
14
21
|
end
|
data/lib/shanty/env.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'deep_merge'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Shanty
|
5
|
+
#
|
6
|
+
class Env
|
7
|
+
CONFIG_FILE = '.shanty.yml'
|
8
|
+
DEFAULT_CONFIG = {}
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
Dir.chdir(root) do
|
12
|
+
(config['require'] || {}).each do |requirement|
|
13
|
+
requirement = "#{requirement}/**/*.rb" unless requirement.include?('.rb')
|
14
|
+
Dir[requirement].each { |f| require(File.join(root, f)) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def environment
|
20
|
+
@environment = ENV['SHANTY_ENV'] || 'local'
|
21
|
+
end
|
22
|
+
|
23
|
+
def root
|
24
|
+
@root ||= find_root
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def config
|
30
|
+
return @config unless @config.nil?
|
31
|
+
|
32
|
+
file_config = YAML.load_file("#{root}/#{CONFIG_FILE}") || {}
|
33
|
+
@config = DEFAULT_CONFIG.deep_merge!(file_config[environment])
|
34
|
+
end
|
35
|
+
|
36
|
+
def find_root
|
37
|
+
if root_dir.nil?
|
38
|
+
fail "Could not find a #{CONFIG_FILE} file in this or any parent directories. \
|
39
|
+
Please run `shanty init` in the directory you want to be the root of your project structure."
|
40
|
+
end
|
41
|
+
|
42
|
+
root_dir
|
43
|
+
end
|
44
|
+
|
45
|
+
def root_dir
|
46
|
+
Pathname.new(Dir.pwd).ascend do |d|
|
47
|
+
return d if d.join(CONFIG_FILE).exist?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/shanty/graph.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'algorithms'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'tsort'
|
2
4
|
|
3
5
|
module Shanty
|
4
6
|
# Public: Represents the link graph of projects in the repository. This class is
|
@@ -7,20 +9,22 @@ module Shanty
|
|
7
9
|
# projects need to be build by combining a Git diff with the dependency graph
|
8
10
|
# to resolve to a list of projects required to be built.
|
9
11
|
class Graph
|
10
|
-
|
12
|
+
extend Forwardable
|
13
|
+
include TSort, Enumerable
|
14
|
+
|
15
|
+
def_delegators :@projects, :<<, :length, :add, :remove
|
16
|
+
def_delegators :sorted_projects, :each, :values, :[]
|
11
17
|
|
12
18
|
# Public: Initialise a ProjectLinkGraph.
|
13
19
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
def initialize(
|
17
|
-
@projects = sort_projects(link_projects(projects))
|
18
|
-
|
20
|
+
# project_templates - An array of project templates to take, instantiate and
|
21
|
+
# link together into a graph structure of dependencies.
|
22
|
+
def initialize(project_templates)
|
19
23
|
@project_path_trie = Containers::Trie.new
|
24
|
+
@project_templates = project_templates
|
25
|
+
@projects = projects_by_path.values
|
20
26
|
|
21
|
-
|
22
|
-
@project_path_trie[project.path] = project
|
23
|
-
end
|
27
|
+
link_projects
|
24
28
|
end
|
25
29
|
|
26
30
|
# Public: All the changed projects in the current repository.
|
@@ -28,7 +32,7 @@ module Shanty
|
|
28
32
|
# Returns an Array of Project subclasses, one for each project in the
|
29
33
|
# repository.
|
30
34
|
def changed
|
31
|
-
|
35
|
+
select(&:changed?)
|
32
36
|
end
|
33
37
|
|
34
38
|
# Public: Returns a project, if any, with the given name.
|
@@ -37,7 +41,7 @@ module Shanty
|
|
37
41
|
#
|
38
42
|
# Returns an instance of a Project subclass if found, otherwise nil.
|
39
43
|
def by_name(name)
|
40
|
-
|
44
|
+
find { |project| project.name == name }
|
41
45
|
end
|
42
46
|
|
43
47
|
# Public: Returns all projects of the given types.
|
@@ -47,7 +51,7 @@ module Shanty
|
|
47
51
|
# Returns an Array of Project subclasses, one for each project in the
|
48
52
|
# repository.
|
49
53
|
def all_of_type(*types)
|
50
|
-
|
54
|
+
select { |project| types.include?(project.class) }
|
51
55
|
end
|
52
56
|
|
53
57
|
# Public: Returns all the changed projects of the given types.
|
@@ -85,6 +89,26 @@ module Shanty
|
|
85
89
|
|
86
90
|
private
|
87
91
|
|
92
|
+
def sorted_projects
|
93
|
+
@sorted_projects ||= tsort
|
94
|
+
end
|
95
|
+
|
96
|
+
def tsort_each_node
|
97
|
+
@projects.each { |p| yield p }
|
98
|
+
end
|
99
|
+
|
100
|
+
def tsort_each_child(project)
|
101
|
+
projects_by_path[project.path].parents.each { |p| yield p }
|
102
|
+
end
|
103
|
+
|
104
|
+
def projects_by_path
|
105
|
+
@projects_by_path ||= Hash[@project_templates.map do |project_template|
|
106
|
+
project = project_template.type.new(project_template)
|
107
|
+
@project_path_trie[project.path] = project
|
108
|
+
[project.path, project]
|
109
|
+
end]
|
110
|
+
end
|
111
|
+
|
88
112
|
# Private: Given a list of projects, construct the parent/child
|
89
113
|
# relationships between them given a list of their parents/children by name
|
90
114
|
# as defined on the project instances.
|
@@ -92,31 +116,18 @@ module Shanty
|
|
92
116
|
# projects - An array of Project subclasses to link together.
|
93
117
|
#
|
94
118
|
# Returns an Array of linked projects.
|
95
|
-
def link_projects
|
96
|
-
|
119
|
+
def link_projects
|
120
|
+
projects_by_path.each_value do |project|
|
121
|
+
project.parents_by_path.each do |parent_path|
|
122
|
+
parent_dependency = projects_by_path[parent_path]
|
123
|
+
if parent_dependency.nil?
|
124
|
+
fail("Cannot find project at path #{parent_path}, which was specified as a dependency for #{project}")
|
125
|
+
end
|
97
126
|
|
98
|
-
projects.each do |project|
|
99
|
-
parent_dependencies = project.parents_by_name.map { |parent_name| projects_by_name[parent_name] }.compact
|
100
|
-
|
101
|
-
parent_dependencies.each do |parent_dependency|
|
102
127
|
project.add_parent(parent_dependency)
|
103
128
|
parent_dependency.add_child(project)
|
104
129
|
end
|
105
130
|
end
|
106
|
-
|
107
|
-
projects
|
108
|
-
end
|
109
|
-
|
110
|
-
# Private: Given a list of Project subclasses, sort them by their distance
|
111
|
-
# from the root node (that is, the topmost node). This order is important
|
112
|
-
# because it matches the order in which things should be build to avoid
|
113
|
-
# missing dependencies.
|
114
|
-
#
|
115
|
-
# projects - An array of Project subclasses to sort.
|
116
|
-
#
|
117
|
-
# Returns a sorted Array of Project subclasses.
|
118
|
-
def sort_projects(projects)
|
119
|
-
projects.sort { |a, b| a.distance_from_root <=> b.distance_from_root }
|
120
131
|
end
|
121
132
|
end
|
122
133
|
end
|
@@ -66,16 +66,6 @@ module Shanty
|
|
66
66
|
def all_parents
|
67
67
|
parents + parents.map(&:all_parents).flatten
|
68
68
|
end
|
69
|
-
|
70
|
-
# Public: Calculate the maximum number of traverses that need to be made to
|
71
|
-
# reach the root from this node.
|
72
|
-
#
|
73
|
-
# Returns a Fixnum representing the traverses to the root. Note that a return
|
74
|
-
# value of 0 means this is the root (ie. it has no parents).
|
75
|
-
def distance_from_root
|
76
|
-
return 0 if parents.empty?
|
77
|
-
parents.map(&:distance_from_root).max + 1
|
78
|
-
end
|
79
69
|
end
|
80
70
|
end
|
81
71
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'hooks'
|
2
|
-
|
3
1
|
module Shanty
|
4
2
|
module Mixins
|
5
3
|
# A mixin to implement publish/subscribe style callbacks in the class that
|
@@ -15,24 +13,38 @@ module Shanty
|
|
15
13
|
# to call extend and include for this mixin. You'll see this idiom everywhere
|
16
14
|
# in the Ruby/Rails world, so we use it too.
|
17
15
|
def self.included(cls)
|
18
|
-
cls.include(Hooks)
|
19
16
|
cls.extend(ClassMethods)
|
20
17
|
end
|
21
18
|
|
22
19
|
# Common methods inherited by all classes
|
23
20
|
module ClassMethods
|
21
|
+
def class_callbacks
|
22
|
+
@class_callbacks ||= Hash.new { |h, k| h[k] = [] }
|
23
|
+
end
|
24
|
+
|
24
25
|
def subscribe(*names, sym)
|
25
26
|
names.each do |name|
|
26
|
-
|
27
|
-
send(name, sym)
|
27
|
+
class_callbacks[name] << sym
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
+
def callbacks
|
33
|
+
@callbacks ||= Hash.new { |h, k| h[k] = [] }
|
34
|
+
end
|
35
|
+
|
36
|
+
def subscribe(*names, sym)
|
37
|
+
names.each do |name|
|
38
|
+
callbacks[name] << sym
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
32
42
|
def publish(name, *args)
|
33
|
-
|
34
|
-
|
35
|
-
|
43
|
+
class_callback_methods = self.class.class_callbacks[name]
|
44
|
+
callback_methods = callbacks[name]
|
45
|
+
|
46
|
+
(class_callback_methods + callback_methods).each { |method| return false unless send(method, *args) }
|
47
|
+
true
|
36
48
|
end
|
37
49
|
end
|
38
50
|
end
|