teapot 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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