teapot 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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