teapot 0.6.0 → 0.7.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.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NWYwMzJlZTQ2ZGEzODQ4ODAwMzY1YWNlMDg2OWI0Mzg2MzFmODlhYQ==
5
+ data.tar.gz: !binary |-
6
+ ODk0NTJjMjllMDEzNWFmOWFmMGFlOWUyMjFhMjA0YTllYmQ2NGUzMg==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ Y2NhNTdkYzgzZTRkZTFjZmFiNGZiMjVlNzkzZDc0ZTYwZThmMzQ3OGJiMjAy
10
+ NWE3MGNlZjM3NTJjYmY5M2RmYjMxZGRhYzYxMWY4ZDgyYjA0ZGE4M2ExYTJl
11
+ NTkxODQyNGU1NDk3NWRmMDkzODQ4Y2I1YTljNTIxNWNmYTdjOGM=
12
+ data.tar.gz: !binary |-
13
+ NzBmODAzNjJiYjgzYjk2ZWI3OTc2NGJjZjY0YTM3MDk0NWFiYjI1OTA2ZjE4
14
+ NWExMGY0ZjAyZjNmZjMwMmZlMjIwMjU5ZDViNjU2N2M0OTkzN2RjMDUwMGNi
15
+ ODIyMWY5ZGYxMTczZTBlMzEyZDM0MTRlY2FjNWU4YTFmZWUxZTg=
data/README.md CHANGED
@@ -1,71 +1,43 @@
1
1
  # Teapot
2
2
 
3
- Teapot is a tool for managing complex cross-platform builds. It provides
4
- advanced dependency management via the Teapot file and is supported by
5
- the infusions ecosystem of packages and platform tooling.
3
+ Teapot is a decentralised build tool for managing complex cross-platform projects. It has many goals but it is primarily designed to improve the experience for developers trying to make cross-platform applications and libraries with a minimum of overhead.
4
+
5
+ - Provide useful feedback when dependencies are not met or errors are encountered.
6
+ - Decentralised dependency management allows use within private organisations without exposing code.
7
+ - Generators can simplify the construction of new projects as well as assist with the development of existing ones.
8
+ - The build subsystem provides a simple set of canonical operations for building libraries and executables to minimise configuration overhead.
6
9
 
7
10
  [![Build Status](https://secure.travis-ci.org/ioquatix/teapot.png)](http://travis-ci.org/ioquatix/teapot)
8
11
 
9
12
  ## Installation
10
13
 
11
- Add this line to your application's Gemfile:
12
-
13
- gem 'teapot'
14
-
15
- And then execute:
16
-
17
- $ bundle
18
-
19
- Or install it yourself as:
14
+ Ensure that you already have a working install of Ruby 1.9.3+
20
15
 
21
16
  $ gem install teapot
22
17
 
23
18
  ## Usage
24
19
 
25
- Create a Teapot file in the root directory of your project:
26
-
27
- source "https://github.com/infusions"
28
-
29
- host /linux/ do
30
- platform "linux"
31
- end
32
-
33
- host /darwin/ do
34
- platform "darwin-osx"
35
- end
36
-
37
- package "png"
38
- package "freetype"
39
- package "vorbis"
40
- package "ogg"
41
- package "jpeg"
42
-
43
- Then run
44
-
45
- $ teapot install
20
+ Teapot doesn't have a centralised package management system. As such, this example shows how to use an existing open source framework.
46
21
 
47
- This will download and compile all the selected packages into the `build` directory.
22
+ Firstly, create your project by running:
48
23
 
49
- ### CMake ###
24
+ $ teapot init "My Project" https://github.com/dream-framework project
25
+ $ cd my-project
50
26
 
51
- To use these packages in a CMake project, update your `CMakeLists.txt`:
27
+ In the resulting project directory that has been created, you can see the list of dependencies:
52
28
 
53
- list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/build/${TEAPOT_PLATFORM}/")
29
+ $ teapot list
54
30
 
55
- Then configure like so:
31
+ To build your project:
56
32
 
57
- cmake path/to/src -DTEAPOT_PLATFORM=linux
33
+ $ teapot build Application/MyProject variant-debug
58
34
 
59
- ### Xcode ###
35
+ When you build, you need to specify dependencies. If you haven't specified all dependencies, they will be suggested to you.
60
36
 
61
- To use these packages in an Xcode project, creating a custom `teapot.xcconfig` is recommended:
37
+ The resulting libraries will be framework dependent, but are typically located in
62
38
 
63
- TEAPOT_PLATFORM=darwin-osx
64
- TEAPOT_PREFIX_PATH=$(SRCROOT)/build/$(TEAPOT_PLATFORM)
65
-
66
- // Search paths:
67
- HEADER_SEARCH_PATHS=$(inherited) "$(TEAPOT_PREFIX_PATH)/include"
68
- LIBRARY_SEARCH_PATHS=$(inherited) "$(TEAPOT_PREFIX_PATH)/lib"
39
+ $ cd teapot/$PROJECT_NAME/platforms/$PLATFORM_NAME/bin/
40
+ $ ./$PROJECT_NAME
69
41
 
70
42
  ## Contributing
71
43
 
data/bin/teapot CHANGED
@@ -20,160 +20,99 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  # THE SOFTWARE.
22
22
 
23
- require 'teapot/config'
23
+ require 'teapot/controller'
24
+ require 'teapot/controller/build'
25
+ require 'teapot/controller/clean'
26
+ require 'teapot/controller/create'
27
+ require 'teapot/controller/fetch'
28
+ require 'teapot/controller/generate'
29
+ require 'teapot/controller/list'
24
30
 
25
- require 'uri'
26
- require 'rainbow'
27
- require 'fileutils'
28
31
  require 'benchmark'
29
- require 'yaml'
30
-
31
32
  require 'trollop'
32
33
 
33
34
  OPTIONS = Trollop::options do
34
35
  opt :only, "Only compiled direct dependencies."
36
+ opt :in, "Work in the given directory.", :type => :string
37
+
38
+ opt :configuration, "Specify a specific build configuration.", :type => :string, :default => Teapot::DEFAULT_CONFIGURATION_NAME
35
39
  end
36
40
 
41
+ def make_controller(root = nil)
42
+ root ||= OPTIONS[:in] || Dir.getwd
43
+ Teapot::Controller.new(root, OPTIONS)
44
+ end
45
+
46
+ # It would be nice to make this code a bit simpler, perhaps moving some parts of it to lib/teapot/application/{function}.rb
37
47
  module Application
38
- def self.fetch
39
- config = Teapot::Config.load_default
40
- context = Teapot::Context.new(config)
48
+ def self.clean
49
+ make_controller.clean
50
+ end
41
51
 
42
- base_uri = URI(config.options[:source].to_s)
52
+ def self.fetch
53
+ make_controller.fetch
54
+ end
43
55
 
44
- if base_uri.scheme == nil || base_uri.scheme == 'file'
45
- base_uri = URI "file://" + File.expand_path(base_uri.path, config.root) + "/"
46
- end
56
+ def self.build
57
+ targets = ARGV
47
58
 
48
- puts "Base URI: #{base_uri}".color(:cyan)
49
-
50
- config.packages.each do |package|
51
- destination_path = package.path
52
-
53
- if package.local?
54
- puts "Linking local #{package}...".color(:cyan)
55
-
56
- local_path = config.root + package.options[:local]
57
-
58
- # Make the top level directory if required:
59
- destination_path.dirname.mkpath
60
-
61
- unless destination_path.exist?
62
- destination_path.make_symlink(local_path)
63
- end
64
- else
65
- puts "Fetching #{package}...".color(:cyan)
66
-
67
- branch = package.options.fetch(:version, 'master')
68
-
69
- unless destination_path.exist?
70
- puts "Cloning package at path #{destination_path} ...".color(:cyan)
71
- destination_path.mkpath
72
-
73
- Teapot::Commands.run("git", "clone", package.relative_url(base_uri), destination_path, "--branch", branch)
74
-
75
- Dir.chdir(destination_path) do
76
- Teapot::Commands.run("git", "submodule", "update", "--init", "--recursive")
77
- end
78
- else
79
- puts "Updating package at path #{destination_path} ...".color(:cyan)
80
-
81
- Dir.chdir(destination_path) do
82
- Teapot::Commands.run("git", "fetch", "origin")
83
-
84
- Teapot::Commands.run("git", "checkout", branch)
85
-
86
- # Pull any changes, if you might get the error from above:
87
- # Your branch is behind 'origin/0.1' by 1 commit, and can be fast-forwarded.
88
- Teapot::Commands.run("git", "pull")
89
-
90
- Teapot::Commands.run("git", "submodule", "update", "--init", "--recursive")
91
- end
92
- end
93
- end
94
- end
95
-
96
- puts "Completed fetch successfully.".color(:green)
59
+ make_controller.build(targets)
97
60
  end
98
61
 
99
- def self.install(package_names = ARGV)
100
- config = Teapot::Config.load_default
101
- context = Teapot::Context.new(config)
62
+ def self.list
63
+ make_controller.list
64
+ end
102
65
 
103
- config.packages.each do |package|
104
- context.load(package)
105
- end
106
-
107
- context.select(package_names)
108
-
109
- chain = Teapot::Dependency::chain(context.selection, context.dependencies, context.targets.values)
110
-
111
- if chain.unresolved.size > 0
112
- puts "Unresolved dependencies:"
66
+ def self.create
67
+ project_name = ARGV.shift
68
+ project_directory = project_name.gsub(/\s+/, '-').downcase
69
+ source = ARGV.shift
70
+ packages = ARGV
113
71
 
114
- chain.unresolved.each do |(name, parent)|
115
- puts "#{parent} depends on #{name.inspect}".color(:red)
116
-
117
- conflicts = chain.conflicts[name]
118
-
119
- if conflicts
120
- conflicts.each do |conflict|
121
- puts " - provided by #{conflict.inspect}".color(:red)
122
- end
123
- end
124
- end
125
-
126
- abort "Cannot continue build due to unresolved dependencies!"
127
- end
128
-
129
- puts "Resolved: #{chain.resolved.inspect}".color(:magenta)
72
+ root = Pathname(Dir.getwd) + project_directory
130
73
 
131
- ordered = chain.ordered
132
-
133
- if OPTIONS[:only]
134
- ordered = context.direct_targets(ordered)
74
+ if root.exist?
75
+ abort "#{root} already exists!".color(:red)
135
76
  end
136
77
 
137
- ordered.each do |(target, dependency)|
138
- puts "Building #{target.name} for dependency #{dependency}...".color(:cyan)
78
+ # Make the path:
79
+ root.mkpath
139
80
 
140
- if target.respond_to? :install! and !ENV['TEAPOT_DRY']
141
- target.install!(context)
142
- end
81
+ Dir.chdir(root) do
82
+ Teapot::Commands.run("git", "init")
143
83
  end
84
+
85
+ controller = make_controller(root)
144
86
 
145
- puts "Completed build successfully.".color(:green)
87
+ controller.create(project_name, source, packages)
88
+ controller.generate('project', [project_name])
146
89
  end
147
90
 
148
- def self.list
149
- config = Teapot::Config.load_default
150
- context = Teapot::Context.new(config)
151
-
152
- config.packages.each do |package|
153
- targets = context.load(package)
154
-
155
- targets.each do |target|
156
- puts "Target #{target.name} from #{package.name}"
157
-
158
- target.dependencies.each do |name|
159
- puts " - depends on #{name.inspect}".color(:red)
160
- end
161
-
162
- target.provisions.each do |(name, provision)|
163
- if Teapot::Dependency::Alias === provision
164
- puts " - provides #{name.inspect} => #{provision.dependencies.inspect}".color(:green)
165
- else
166
- puts " - provides #{name.inspect}".color(:green)
167
- end
168
- end
169
- end
170
- end
91
+ def self.generate(arguments = ARGV)
92
+ make_controller.generate(arguments)
171
93
  end
172
94
  end
173
95
 
96
+ valid_actions = (Application.public_methods - Module.methods).collect(&:to_s)
97
+ action = ARGV.shift
98
+
99
+ # Check that the command was invoked correctly...
100
+ unless action and valid_actions.include?(action)
101
+ puts "You must specify an action from: #{valid_actions.join(', ')}".color(:red)
102
+ exit -1
103
+ end
104
+
174
105
  time = Benchmark.measure do
175
- action = ARGV.shift.to_sym
176
- Application.send(action) if Application.methods.include?(action)
106
+ begin
107
+ Application.send(action.to_sym)
108
+ rescue Teapot::NonexistantTeapotError => error
109
+ $stderr.puts error.message.color(:red)
110
+ rescue Teapot::IncompatibleTeapotError => error
111
+ $stderr.puts error.message.color(:red)
112
+ rescue Teapot::Commands::CommandError => error
113
+ $stderr.puts error.message.color(:red)
114
+ end
177
115
  end
178
116
 
179
- puts time.format("Elapsed Time: %r").color(:magenta)
117
+ $stdout.flush
118
+ $stderr.puts time.format("Elapsed Time: %r").color(:magenta)
@@ -32,20 +32,20 @@ module Teapot
32
32
  end
33
33
 
34
34
  module Helpers
35
- def install_directory(root, directory, *args)
35
+ def build_directory(root, directory, *args)
36
36
  target = Build.top(root)
37
37
 
38
38
  target.add_directory(directory)
39
39
 
40
- target.execute(:install, *args)
40
+ target.execute(:build, *args)
41
41
  end
42
42
 
43
- def install_external(root, directory, *args, &block)
43
+ def build_external(root, directory, *args, &block)
44
44
  target = Build.top(root)
45
45
 
46
46
  target << Targets::External.new(target, directory, &block)
47
47
 
48
- target.execute(:install, *args)
48
+ target.execute(:build, *args)
49
49
  end
50
50
  end
51
51
  end
@@ -33,7 +33,7 @@ module Teapot
33
33
  end
34
34
 
35
35
  def subdirectory
36
- "apps/#{@name}"
36
+ "Applications/#{@name}"
37
37
  end
38
38
 
39
39
  def << target
@@ -41,7 +41,7 @@ module Teapot
41
41
  @parent.root + @directory
42
42
  end
43
43
 
44
- def install(values)
44
+ def build(values)
45
45
  return unless @install
46
46
 
47
47
  source_path = @parent.root + @directory
@@ -51,7 +51,7 @@ module Teapot
51
51
  options[:subdirectory] || "./"
52
52
  end
53
53
 
54
- def install(environment)
54
+ def build(environment)
55
55
  prefix = install_prefix!(environment)
56
56
 
57
57
  if self.respond_to? :source_files
@@ -53,7 +53,7 @@ module Teapot
53
53
  return library_file
54
54
  end
55
55
 
56
- def build(environment)
56
+ def compile_and_link(environment)
57
57
  file_list = self.source_files(environment)
58
58
 
59
59
  pool = Commands::Pool.new
@@ -79,10 +79,10 @@ module Teapot
79
79
  end
80
80
  end
81
81
 
82
- def install(environment)
82
+ def build(environment)
83
83
  prefix = install_prefix!(environment)
84
84
 
85
- build(environment).each do |path|
85
+ compile_and_link(environment).each do |path|
86
86
  destination_path = prefix + subdirectory + path.basename
87
87
 
88
88
  destination_path.dirname.mkpath
@@ -48,7 +48,7 @@ module Teapot
48
48
  if system(*args)
49
49
  true
50
50
  else
51
- raise CommandError.new("Non-zero exit status!")
51
+ raise CommandError.new("Non-zero exit status: #{args.join(' ')}!")
52
52
  end
53
53
  end
54
54
 
@@ -0,0 +1,174 @@
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 'set'
23
+
24
+ require 'yaml/store'
25
+
26
+ require 'teapot/context'
27
+ require 'teapot/environment'
28
+ require 'teapot/commands'
29
+
30
+ require 'teapot/definition'
31
+
32
+ module Teapot
33
+ class Configuration < Definition
34
+ Import = Struct.new(:name, :options)
35
+
36
+ def initialize(context, package, name, packages = Set.new)
37
+ super context, package, name
38
+
39
+ @options = {}
40
+
41
+ @packages = packages
42
+ @imports = []
43
+
44
+ @visibility = :private
45
+ end
46
+
47
+ # Controls how the configuration is exposed in the context.
48
+ attr :visibility
49
+
50
+ def public?
51
+ @visibility == :public
52
+ end
53
+
54
+ def public!
55
+ @visibility = :public
56
+ end
57
+
58
+ # Options used to bind packages to this configuration.
59
+ attr :options
60
+
61
+ # A list of packages which are required by this configuration.
62
+ attr :packages
63
+
64
+ # A list of other configurations to include when materialising the list of packages.
65
+ attr :imports
66
+
67
+ # Specifies that this configuration depends on an external package of some sort.
68
+ def require(name, options = nil)
69
+ options = options ? @options.merge(options) : @options.dup
70
+
71
+ @packages << Package.new(packages_path + name.to_s, name, options)
72
+ end
73
+
74
+ # Specifies that this package will import additional configuration records from another definition.
75
+ def import(name)
76
+ @imports << Import.new(name, @options.dup)
77
+ end
78
+
79
+ # Require and import the named package.
80
+ def import!(name, options = nil)
81
+ require(name, options)
82
+ import(name)
83
+ end
84
+
85
+ # Create a group for configuration options which will be only be active within the group block.
86
+ def group
87
+ options = @options.dup
88
+
89
+ yield
90
+
91
+ @options = options
92
+ end
93
+
94
+ # Set a configuration option.
95
+ def []= key, value
96
+ @options[key] = value
97
+ end
98
+
99
+ # Get a configuration option.
100
+ def [] key
101
+ @options[key]
102
+ end
103
+
104
+ # The path where packages will be located when fetched.
105
+ def packages_path
106
+ context.root + "teapot/packages/#{name}"
107
+ end
108
+
109
+ # The path where built products will be installed.
110
+ def platforms_path
111
+ context.root + "teapot/platforms/#{name}"
112
+ end
113
+
114
+ def lock_path
115
+ context.root + "#{@name}-lock.yml"
116
+ end
117
+
118
+ def lock_store
119
+ @lock_store ||= YAML::Store.new(lock_path.to_s)
120
+ end
121
+
122
+ # Load all packages defined by this configuration.
123
+ def load_all
124
+ @packages.each do |package|
125
+ @context.load(package)
126
+ end
127
+ end
128
+
129
+ # Conceptually, a configuration belongs to a package. Primarily, a configuration lists dependent packages, but this also includes itself as the dependencies are purely target based, e.g. this configuration has access to any targets exposed by its own package.
130
+ def top!
131
+ @packages << @package
132
+ end
133
+
134
+ # Process all import directives and return a new configuration based on the current configuration. Import directives bring packages and other import directives from the specififed configuration definition.
135
+ def materialize
136
+ # Potentially no materialization is required:
137
+ return self if @imports.count == 0
138
+
139
+ # Before trying to materialize, we should load all possible packages:
140
+ @packages.each{|package| @context.load(package) rescue nil}
141
+
142
+ # Create a new configuration which will represent the materialised version:
143
+ configuration = self.class.new(@context, @package, @name, @packages.dup)
144
+
145
+ # Enumerate all imports and attempt to resolve the packages:
146
+ @imports.each do |import|
147
+ resolved_configuration = @context.configuration_named(import.name)
148
+
149
+ if resolved_configuration
150
+ configuration.append(resolved_configuration, import.options)
151
+ else
152
+ # It couldn't be resolved...
153
+ configuration.imports << import
154
+ end
155
+ end
156
+
157
+ return configuration
158
+ end
159
+
160
+ def append(configuration, options)
161
+ @packages += configuration.packages.collect do |package|
162
+ package.dup.tap{|package| package.options = options.merge(package.options)}
163
+ end
164
+
165
+ @imports += configuration.imports.collect do |import|
166
+ import.dup.tap{|import| import.options = options.merge(import.options)}
167
+ end
168
+ end
169
+
170
+ def to_s
171
+ "<#{self.class.name} #{@name.dump} visibility=#{@visibility}>"
172
+ end
173
+ end
174
+ end