teapot 0.3.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/teapot/config.rb CHANGED
@@ -26,10 +26,11 @@ require 'teapot/commands'
26
26
 
27
27
  module Teapot
28
28
  class Config
29
- class Record
30
- def initialize(config, klass, name, options = {})
29
+ include Dependency
30
+
31
+ class Package
32
+ def initialize(config, name, options = {})
31
33
  @config = config
32
- @klass = klass
33
34
 
34
35
  if options[:name]
35
36
  @name = options[:name]
@@ -47,60 +48,61 @@ module Teapot
47
48
  @global = Environment.new
48
49
  end
49
50
 
50
- attr :klass
51
51
  attr :name
52
52
  attr :uri
53
53
  attr :options
54
54
  attr :global
55
55
 
56
- def transient?
57
- @klass == FakePackage
56
+ def relative_url(base_uri)
57
+ source_uri = URI(@uri)
58
+
59
+ unless source_uri.absolute?
60
+ source_uri = base_uri + source_uri
61
+ end
62
+
63
+ # Git can't handle the default formatting that Ruby uses for file URIs.
64
+ if source_uri.scheme == "file"
65
+ source_uri = "file://" + source_uri.path
66
+ end
67
+
68
+ return source_uri
58
69
  end
59
70
 
60
71
  def local?
61
72
  @options.key? :local
62
73
  end
63
74
 
64
- def load(context)
65
- if @klass == FakePackage
66
- context.packages[@name] = @klass.new(@context, self, @name)
67
- else
68
- context.load(self)
69
- end
70
- end
71
-
72
75
  def loader_path
73
- "infusion.rb"
76
+ "teapot.rb"
74
77
  end
75
78
 
76
- def destination_path
77
- @config.base_path_for_record(self) + @name
79
+ def path
80
+ @config.packages_path + @name
78
81
  end
79
82
 
80
83
  def to_s
81
- "<#{@klass} #{@name}>"
82
- end
83
- end
84
-
85
- def base_path_for_record(record)
86
- if record.klass == Package
87
- packages_path
88
- elsif record.klass == Platform
89
- platforms_path
84
+ "<#{@name}>"
90
85
  end
91
86
  end
92
87
 
93
88
  def initialize(root, options = {})
94
89
  @root = Pathname.new(root)
95
90
  @options = options
96
-
91
+
97
92
  @packages = []
98
- @platforms = []
99
-
93
+
100
94
  @environment = Environment.new
101
95
  end
102
96
 
97
+ def name
98
+ :config
99
+ end
100
+
103
101
  attr :root
102
+ attr :packages
103
+ attr :options
104
+
105
+ attr :environment
104
106
 
105
107
  def packages_path
106
108
  @root + (@options[:packages_path] || "packages")
@@ -110,22 +112,6 @@ module Teapot
110
112
  @root + (@options[:platforms_path] || "platforms")
111
113
  end
112
114
 
113
- def build_path
114
- @root + (@options[:build_path] || "build")
115
- end
116
-
117
- def variant(*args, &block)
118
- name = @options[:variant] || 'debug'
119
-
120
- if block_given?
121
- if args.find{|arg| arg === name}
122
- yield
123
- end
124
- else
125
- name
126
- end
127
- end
128
-
129
115
  def host(*args, &block)
130
116
  name = @options[:host_platform] || RUBY_PLATFORM
131
117
 
@@ -140,28 +126,13 @@ module Teapot
140
126
 
141
127
  attr :options
142
128
  attr :packages
143
- attr :platforms
144
- attr :environment
145
129
 
146
130
  def source(path)
147
131
  @options[:source] = path
148
132
  end
149
133
 
150
- def records
151
- @packages + @platforms
152
- end
153
-
154
134
  def package(name, options = {})
155
- @packages << Record.new(self, Package, name, options)
156
- end
157
-
158
- def platform(name, options = {})
159
- options = {:environment => @environment}.merge(options)
160
- @platforms << Record.new(self, Platform, name, options)
161
- end
162
-
163
- def provides(name, options = {})
164
- @packages << Record.new(self, FakePackage, name, options)
135
+ @packages << Package.new(self, name, options)
165
136
  end
166
137
 
167
138
  def load(teapot_path)
@@ -180,8 +151,6 @@ module Teapot
180
151
  end
181
152
 
182
153
  def self.load_default(root = Dir.getwd, options = {})
183
- options.merge!(:variant => ENV['TEAPOT_VARIANT'])
184
-
185
154
  # Load project specific Teapot file
186
155
  load(root, options) do |config|
187
156
  user_path = File.expand_path("~/.Teapot")
@@ -21,56 +21,43 @@
21
21
  require 'pathname'
22
22
  require 'rainbow'
23
23
 
24
- require 'teapot/package'
25
- require 'teapot/platform'
24
+ require 'teapot/target'
26
25
 
27
26
  module Teapot
28
- INFUSION_VERSION = "0.2"
27
+ LOADER_VERSION = "0.5"
29
28
 
30
- class IncompatibleInfusion < StandardError
29
+ class IncompatibleTeapot < StandardError
31
30
  end
32
31
 
33
- class Infusion
34
- def initialize(context, record)
32
+ class Loader
33
+ def initialize(context, package)
35
34
  @context = context
36
- @record = record
35
+ @package = package
37
36
 
38
37
  @defined = []
39
38
  @version = nil
40
39
  end
41
40
 
42
- attr :record
41
+ attr :package
43
42
  attr :defined
44
43
  attr :version
45
44
 
46
45
  def required_version(version)
47
- if version <= INFUSION_VERSION
46
+ if version <= LOADER_VERSION
48
47
  @version = version
49
48
  else
50
- raise IncompatibleInfusion.new("Version #{version} more recent than #{INFUSION_VERSION}!")
49
+ raise IncompatibleTeapot.new("Version #{version} more recent than #{LOADER_VERSION}!")
51
50
  end
52
51
  end
53
52
 
54
- def define_package(*args, &block)
55
- package = Package.new(@context, @record, *args)
53
+ def define_target(*args, &block)
54
+ target = Target.new(@context, @package, *args)
56
55
 
57
- yield(package)
56
+ yield(target)
58
57
 
59
- @context.packages[package.name] = package
58
+ @context.targets[target.name] = target
60
59
 
61
- @defined << package
62
- end
63
-
64
- def define_platform(*args, &block)
65
- platform = Platform.new(@context, @record, *args)
66
-
67
- yield(platform)
68
-
69
- if platform.available?
70
- @context.platforms[platform.name] = platform
71
- end
72
-
73
- @defined << platform
60
+ @defined << target
74
61
  end
75
62
 
76
63
  def load(path)
@@ -82,25 +69,47 @@ module Teapot
82
69
  def initialize(config)
83
70
  @config = config
84
71
 
85
- @packages = {}
86
- @platforms = {}
72
+ @selection = nil
73
+
74
+ @targets = {config.name => config}
75
+
76
+ @dependencies = []
77
+ @selection = Set.new
87
78
  end
88
79
 
89
80
  attr :config
90
- attr :packages
91
- attr :platforms
81
+ attr :targets
92
82
 
93
- def load(record)
94
- infusion = Infusion.new(self, record)
83
+ def select(names)
84
+ names.each do |name|
85
+ if @targets.key? name
86
+ @selection << name
87
+ else
88
+ @dependencies << name
89
+ end
90
+ end
91
+ end
92
+
93
+ attr :dependencies
94
+ attr :selection
95
+
96
+ def direct_targets(ordered)
97
+ @dependencies.collect do |dependency|
98
+ ordered.find{|(package, _)| package.provides? dependency}
99
+ end.compact
100
+ end
101
+
102
+ def load(package)
103
+ loader = Loader.new(self, package)
95
104
 
96
- path = (record.destination_path + record.loader_path).to_s
97
- infusion.load(path)
105
+ path = (package.path + package.loader_path).to_s
106
+ loader.load(path)
98
107
 
99
- if infusion.version == nil
100
- raise IncompatibleInfusion.new("No version specified in #{path}!")
108
+ if loader.version == nil
109
+ raise IncompatibleTeapot.new("No version specified in #{path}!")
101
110
  end
102
111
 
103
- infusion.defined
112
+ loader.defined
104
113
  end
105
114
  end
106
115
  end
@@ -0,0 +1,182 @@
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'set'
22
+
23
+ require 'teapot/environment'
24
+
25
+ module Teapot
26
+ module Dependency
27
+ Provision = Struct.new(:value)
28
+ Alias = Struct.new(:dependencies)
29
+
30
+ def provides?(name)
31
+ provisions.key? name
32
+ end
33
+
34
+ def provides(name_or_aliases, &block)
35
+ if String === name_or_aliases || Symbol === name_or_aliases
36
+ name = name_or_aliases
37
+
38
+ if block_given?
39
+ provisions[name] = Provision.new(block)
40
+ else
41
+ provisions[name] = Provision.new(nil)
42
+ end
43
+ else
44
+ aliases = name_or_aliases
45
+
46
+ aliases.each do |(name, dependencies)|
47
+ provisions[name] = Alias.new(Array dependencies)
48
+ end
49
+ end
50
+ end
51
+
52
+ def provisions
53
+ @provisions ||= {}
54
+ end
55
+
56
+ def depends(name)
57
+ dependencies << name
58
+ end
59
+
60
+ def depends?(name)
61
+ dependencies.include? name
62
+ end
63
+
64
+ def dependencies
65
+ @dependencies ||= Set.new
66
+ end
67
+
68
+ class Chain
69
+ def initialize(selection, dependencies, providers)
70
+ # Explicitly selected targets which will be used when resolving ambiguity:
71
+ @selection = Set.new(selection)
72
+
73
+ # The list of dependencies that needs to be satisfied:
74
+ @dependencies = dependencies
75
+
76
+ # The available providers which match up to required dependencies:
77
+ @providers = providers
78
+
79
+ @resolved = Set.new
80
+ @ordered = []
81
+ @provisions = []
82
+ @unresolved = []
83
+ @conflicts = {}
84
+
85
+ @dependencies.each do |dependency|
86
+ expand(dependency, nil)
87
+ end
88
+ end
89
+
90
+ attr :selection
91
+ attr :dependencies
92
+ attr :providers
93
+
94
+ attr :resolved
95
+ attr :ordered
96
+ attr :provisions
97
+ attr :unresolved
98
+ attr :conflicts
99
+
100
+ private
101
+
102
+ def find_provider(dependency, parent)
103
+ # Mostly, only one package will satisfy the dependency...
104
+ viable_providers = @providers.select{|provider| provider.provides? dependency}
105
+
106
+ # puts "** Found #{viable_providers.collect(&:name).join(', ')} viable providers.".color(:magenta)
107
+
108
+ if viable_providers.size > 1
109
+ # ... however in some cases (typically where aliases are being used) an explicit selection must be made for the build to work correctly.
110
+ explicit_providers = viable_providers.select{|provider| @selection.include? provider.name}
111
+
112
+ # puts "** Filtering to #{explicit_providers.collect(&:name).join(', ')} explicit providers.".color(:magenta)
113
+
114
+ if explicit_providers.size == 0
115
+ # No provider was explicitly specified, thus we require explicit conflict resolution:
116
+ @conflicts[dependency] = viable_providers
117
+ return nil
118
+ elsif explicit_providers.size == 1
119
+ # The best outcome, a specific provider was named:
120
+ return explicit_providers.first
121
+ else
122
+ # Multiple providers were explicitly mentioned that satisfy the dependency.
123
+ @conflicts[dependency] = explicit_providers
124
+ return nil
125
+ end
126
+ else
127
+ return viable_providers.first
128
+ end
129
+ end
130
+
131
+ def expand(dependency, parent)
132
+ # puts "** Expanding #{dependency} from #{parent}".color(:magenta)
133
+
134
+ if @resolved.include? dependency
135
+ # puts "** Already resolved dependency!".color(:magenta)
136
+
137
+ return
138
+ end
139
+
140
+ provider = find_provider(dependency, parent)
141
+
142
+ if provider == nil
143
+ # puts "** Couldn't find provider -> unresolved".color(:magenta)
144
+ @unresolved << [dependency, parent]
145
+ return nil
146
+ end
147
+
148
+ provision = provider.provisions[dependency]
149
+
150
+ # We will now satisfy this dependency by satisfying any dependent dependencies, but we no longer need to revisit this one.
151
+ @resolved << dependency
152
+
153
+ if Alias === provision
154
+ # puts "** Resolving alias #{provision}".color(:magenta)
155
+
156
+ provision.dependencies.each do |dependency|
157
+ expand(dependency, provider)
158
+ end
159
+ elsif provision != nil
160
+ # puts "** Appending #{dependency} -> provisions".color(:magenta)
161
+ @provisions << provision
162
+ end
163
+
164
+ unless @resolved.include?(provider)
165
+ # We are now satisfying the provider by expanding all its own dependencies:
166
+ @resolved << provider
167
+
168
+ provider.dependencies.each do |dependency|
169
+ expand(dependency, provider)
170
+ end
171
+
172
+ # puts "** Appending #{dependency} -> ordered".color(:magenta)
173
+ @ordered << [provider, dependency]
174
+ end
175
+ end
176
+ end
177
+
178
+ def self.chain(selection, dependencies, providers)
179
+ Chain.new(selection, dependencies, providers)
180
+ end
181
+ end
182
+ end
@@ -49,6 +49,10 @@ module Teapot
49
49
  def replace(name)
50
50
  @environment[name] = Replace.new(@environment[name])
51
51
  end
52
+
53
+ def append(name)
54
+ @environment[name] = Array(@environment[name])
55
+ end
52
56
  end
53
57
 
54
58
  def self.combine(*environments)
@@ -0,0 +1,94 @@
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'pathname'
22
+ require 'teapot/build'
23
+ require 'teapot/dependency'
24
+
25
+ module Teapot
26
+ class BuildError < StandardError
27
+ end
28
+
29
+ class Target
30
+ include Dependency
31
+
32
+ def initialize(context, package, name)
33
+ @context = context
34
+ @package = package
35
+
36
+ @name = name
37
+
38
+ @install = nil
39
+
40
+ @path = @package.path
41
+ end
42
+
43
+ attr :context
44
+ attr :package
45
+ attr :name
46
+
47
+ attr :path
48
+
49
+ def builder
50
+ Build.top(@path)
51
+ end
52
+
53
+ def install(&block)
54
+ @install = Proc.new(&block)
55
+ end
56
+
57
+ def install!(context, config = {})
58
+ return unless @install
59
+
60
+ chain = Dependency::chain(context.selection, dependencies, context.targets.values)
61
+
62
+ environments = []
63
+
64
+ # The base configuration environment:
65
+ environments << context.config.environment
66
+
67
+ # The dependencies environments':
68
+ environments += chain.provisions.collect do |provision|
69
+ Environment.new(&provision.value)
70
+ end
71
+
72
+ # Per-configuration package package environment:
73
+ environments << @package.options[:environment]
74
+
75
+ # Merge all the environments together:
76
+ environment = Environment.combine(*environments)
77
+
78
+ local_build = environment.merge do
79
+ default platforms_path context.config.platforms_path
80
+ default build_prefix {platforms_path + "cache/#{platform_name}-#{variant}"}
81
+ default install_prefix {platforms_path + "#{platform_name}-#{variant}"}
82
+
83
+ append buildflags {"-I#{install_prefix + "include"}"}
84
+ append linkflags {"-L#{install_prefix + "lib"}"}
85
+ end
86
+
87
+ @install.call(local_build)
88
+ end
89
+
90
+ def to_s
91
+ "<Target: #{@name}>"
92
+ end
93
+ end
94
+ end
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Teapot
22
- VERSION = "0.3.2"
22
+ VERSION = "0.5.0"
23
23
  end
data/teapot.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |gem|
21
21
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
22
22
  gem.require_paths = ["lib"]
23
23
 
24
- gem.add_dependency "rake"
25
24
  gem.add_dependency "rainbow"
26
25
  gem.add_dependency "rexec"
26
+ gem.add_dependency "trollop"
27
27
  end
@@ -0,0 +1,41 @@
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'pathname'
22
+ require 'test/unit'
23
+ require 'stringio'
24
+
25
+ require 'teapot/config'
26
+
27
+ class TestConfig < Test::Unit::TestCase
28
+ ROOT = Pathname.new(__FILE__).dirname
29
+
30
+ def test_config
31
+ config = Teapot::Config.new(ROOT)
32
+
33
+ config.instance_eval do
34
+ source "../infusions/"
35
+ package "png"
36
+ end
37
+
38
+ assert_equal "../infusions/", config.options[:source]
39
+ assert_equal 1, config.packages.size
40
+ end
41
+ end