bake 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +23 -1
- data/CONCEPTS +2 -2
- data/TUTORIAL +14 -8
- data/lib/bake.rb +18 -6
- data/lib/bake/configuration.rb +16 -44
- data/lib/bake/context.rb +41 -9
- data/lib/bake/extensions.rb +3 -0
- data/lib/bake/extensions/class.rb +11 -0
- data/lib/bake/extensions/object.rb +22 -0
- data/lib/bake/{string_utils.rb → extensions/string.rb} +0 -1
- data/lib/bake/file_target.rb +8 -3
- data/lib/bake/plugin.rb +13 -36
- data/lib/bake/plugins/cpp.rb +111 -45
- data/lib/bake/plugins/cpp/darwin.rb +3 -4
- data/lib/bake/plugins/cpp/gcc.rb +5 -0
- data/lib/bake/plugins/cpp/gcc_toolset_base.rb +11 -28
- data/lib/bake/plugins/cpp/msvc.rb +10 -16
- data/lib/bake/plugins/cpp/qt.rb +29 -26
- data/lib/bake/plugins/cpp/toolset_base.rb +28 -70
- data/lib/bake/plugins/runner.rb +40 -0
- data/lib/bake/plugins/system.rb +6 -22
- data/lib/bake/project.rb +60 -19
- data/lib/bake/project_loader.rb +13 -4
- data/lib/bake/system_utils.rb +42 -0
- data/lib/bake/target.rb +100 -23
- data/lib/bake/toolset.rb +8 -1
- data/lib/bake_version.rb +1 -1
- data/test/test_bake.rb +2 -0
- data/test/test_configuration.rb +58 -0
- metadata +11 -8
- data/test/bake_test.rb +0 -5
- data/test/configuration_test.rb +0 -102
- data/test/context_test.rb +0 -94
- data/test/scheme_test.rb +0 -121
- data/test/target_test.rb +0 -93
data/CHANGELOG
CHANGED
@@ -1,6 +1,28 @@
|
|
1
1
|
= Bake Changelog
|
2
2
|
|
3
|
-
== Version 0.1.
|
3
|
+
== Version 0.1.2 (2007-05-17)
|
4
|
+
|
5
|
+
* Implemented Target#products which returns the set of output files generated by the target
|
6
|
+
* Using Target#products, implemented Target#stale? and Target#mtime in a general way
|
7
|
+
* Moved C++ include file detection into IncludeList class which is now a dependency of all Cpp::Source
|
8
|
+
* Created a Runner target class which can be used to run any commands, not just C++ executables
|
9
|
+
* Added simple circular dependency detection during build phase
|
10
|
+
* Implemented Target#to_s for more consistent error messages
|
11
|
+
* Dependency resolution now uses :search_projects property to resolve unqualified target references (which always includes the current project)
|
12
|
+
* Libraries and executables no longer re-link to changed dynamic libraries unless they are forced to by an altered header dependency
|
13
|
+
* Target subclass initialization is now done via the post_initialization method, making the initialization methods much simpler
|
14
|
+
* All target constructors now support a properties hash which initializes the given properties before post_initialization
|
15
|
+
* Added Project#map method which maps target names to fully qualified dir:name paths
|
16
|
+
* Implemented post-build dependencies which are built after their dependents are built (useful for unit tests)
|
17
|
+
* Fixed QT Moc support
|
18
|
+
* Trashed Configuration#req and associated "requirements" functionality as it proved not very useful
|
19
|
+
* Major overhaul of plugin support, plugins are now searched for in any directories given in :plugin_paths
|
20
|
+
* Moved all Ruby core class extensions into the bake/extensions directory
|
21
|
+
* Toolsets now use a +SystemUtils+ instance for doing all file and shell operations
|
22
|
+
* Introduced --dry-run and --verbose command line parameters
|
23
|
+
* Dumped all the old useless unit tests (they will be re-introduced for 2.0)
|
24
|
+
|
25
|
+
== Version 0.1.1 (2007-05-02)
|
4
26
|
|
5
27
|
* Created a plugin infrastructure whereby missing constants now result in a search of the plugins/ directory
|
6
28
|
* Implemented Addon module which enables adding commands to bakefile contexts via the import method
|
data/CONCEPTS
CHANGED
@@ -30,9 +30,9 @@ Targets are related to each other by two relations. The first is the parent-chil
|
|
30
30
|
|
31
31
|
Before a particular target is built, there are zero or more dependencies that must be built first. If any of the dependencies fails to build, the target will fail to be built.
|
32
32
|
|
33
|
-
Dependencies have two roles for a target. First, partially determine the order in which targets will be built as outlined above. Second, their products are used by dependent
|
33
|
+
Dependencies have two roles for a target. First, to partially determine the order in which targets will be built as outlined above. Second, their products are used by dependent targets to build its products. For example, an executable target may depend on the functionality available in a library and thus it must link against that library.
|
34
34
|
|
35
|
-
===
|
35
|
+
=== Properties
|
36
36
|
|
37
37
|
=== Requirements
|
38
38
|
|
data/TUTORIAL
CHANGED
@@ -11,6 +11,8 @@ Once you've installed Bake, you should be able to execute it by running the <tt>
|
|
11
11
|
dep 'main.cpp'
|
12
12
|
end
|
13
13
|
|
14
|
+
dep 'test'
|
15
|
+
|
14
16
|
The <tt>exe</tt> command specifies that we'd like to create an executable <b>target</b>. A target is any product that gets created as a result of the build process. There are many kinds of targets, such as <tt>lib</tt>s, or C++ libraries, etc. To make this example a little more concrete, let's also create <i>main.cpp</i>:
|
15
17
|
|
16
18
|
// main.cpp
|
@@ -27,7 +29,7 @@ Both these files should be in the same directory, call it <i>test/</i>. The layo
|
|
27
29
|
root.bake
|
28
30
|
main.cpp
|
29
31
|
|
30
|
-
If you run <tt>bake</tt> from the <i>test/</i> directory you should see output resembling "<tt>root.bake:1:
|
32
|
+
If you run <tt>bake</tt> from the <i>test/</i> directory you should see output resembling "<tt>root.bake:1: unknown command 'exe'</tt>". The reason for this is that we have not specified what <b>tooset</b> we will be using to build C++ targets. Bake supports a number of C++ toolsets (consult the Reference for full details) and each toolset should treat our test project identically, so which toolset you use should not matter. For the purposes of this example, I'll use the <tt>gcc</tt> toolset.
|
31
33
|
|
32
34
|
=== The <tt>using</tt> Directive
|
33
35
|
|
@@ -49,6 +51,8 @@ Unfortunately, the <i>test</i> executable will probably be accompanied by a numb
|
|
49
51
|
dep 'main.cpp'
|
50
52
|
end
|
51
53
|
|
54
|
+
dep 'test'
|
55
|
+
|
52
56
|
Note that property names are <b>symbols</b> preceded by a colon (:) whereas, in this case, the property value is a <b>string</b>. Property values can be any of a number of different types such as strings, booleans, numbers, etc.
|
53
57
|
|
54
58
|
Delete all the product files generated by the last build so that your directory structure looks like:
|
@@ -83,12 +87,12 @@ As a first try at managing this project, let's create a <i>root.bake</i> in the
|
|
83
87
|
end
|
84
88
|
|
85
89
|
exe 'test_exe' do
|
86
|
-
dep '
|
90
|
+
dep 'test_lib', 'test_exe/source1.cpp'
|
87
91
|
end
|
88
92
|
|
89
|
-
|
93
|
+
dep 'test_exe'
|
90
94
|
|
91
|
-
|
95
|
+
Here we have created a <tt>lib</tt> target called <tt>test_lib</tt> containing two source files, and an executable file that depends on the library and another source file. This is pretty simple and will probably seem to work, however, there's an insidious bug waiting to cause trouble when you least expect it. Note that there are two files called <i>source1.cpp</i>. In each case, a C++ object file called <i>source1.o</i> (or <i>source1.obj</i> on Windows) will be created in the <i>test2/</i> directory, the object file from <tt>test_exe</tt> will overwrite the object file from <tt>test_lib</tt>. This will not immediately cause a problem, since the library object file was added to the library before the executable object file was created but it will almost certainly cause problems later on.
|
92
96
|
|
93
97
|
=== More About Properties
|
94
98
|
|
@@ -103,18 +107,20 @@ The easiest way to avoid conflicts where output files overwrite eachother is to
|
|
103
107
|
|
104
108
|
exe 'test_exe' do
|
105
109
|
opt :name => 'test_exe'
|
106
|
-
dep '
|
110
|
+
dep 'test_lib', 'test_exe/source1.cpp'
|
107
111
|
end
|
108
112
|
|
109
|
-
|
113
|
+
dep 'test_exe'
|
114
|
+
|
115
|
+
If you run Bake now, the outputs from <tt>test_lib</tt> will end up in <i>test2/bin/test_lib/</i> and the outputs from <tt>test_exe</tt> will end up in <i>test2/bin/test_exe</i>. If you're confused, don't worry, we've taken two steps forward here. Not only is the value of the <tt>:outdir</tt> property not explicitly set on either target, but it's out in no-man's land, what gives? Furthermore, what's up with the <tt>${name}</tt> business?
|
110
116
|
|
111
117
|
The answer to the first question is related to an important fact about targets: they are tree-like. Every target (except the root target, which we'll get into later) has a single parent target, and many targets have child targets. So what target is the parent of <tt>test_lib</tt> and <tt>test_exe</tt>? An implicit <b>project</b> target that is created when <i>root.bake</i> is processed. Therefore, the <tt>opt</tt> command is being executed on the implicit project target and so <tt>:outdir</tt> is set for the project.
|
112
118
|
|
113
119
|
Furthermore, properties are inherited. So if a property is explicitly defined for a parent target but is not explicitly defined for the child, then the property for the child is the same as that for the parent. Properties can be overridden by explicitly using the <tt>opt</tt> command on the child, in which case the parent's property value will be ignored.
|
114
120
|
|
115
|
-
So in the example above, the <tt>:outdir</tt> property is inherited by the <tt>test_lib</tt> and <tt>test_exe</tt> targets as <tt>'bin/${
|
121
|
+
So in the example above, the <tt>:outdir</tt> property is inherited by the <tt>test_lib</tt> and <tt>test_exe</tt> targets as <tt>'bin/${name}'</tt>.
|
116
122
|
|
117
|
-
The answer to the second question is even simpler. String property values are dynamic. Any <tt>${...}</tt> tokens found in the string will be evaluated when the property is queried. In addition, it will be queried in the context of the target being queried, not the target where the property was originally defined. So when the GCC toolset builds <tt>test_lib</tt> and <tt>test_exe</tt>, it will query the <tt>:outdir</tt> property on each target and it will evaluate to <tt>'bin/test_lib'</tt> and <tt>'bin/test_exe'</tt> respectively.
|
123
|
+
The answer to the second question is even simpler. String property values are dynamic. Any <tt>${...}</tt> tokens found in the string will be evaluated when the property is queried. In addition, it will be queried in the context of the target being queried, not the target where the property was originally defined. So when the GCC toolset builds <tt>test_lib</tt> and <tt>test_exe</tt>, it will query the <tt>:outdir</tt> property on each target and it will evaluate to <tt>'bin/test_lib'</tt> and <tt>'bin/test_exe'</tt> respectively since we've defined the <tt>:name</tt> property for each appropriately.
|
118
124
|
|
119
125
|
=== Modular Bakefiles
|
120
126
|
|
data/lib/bake.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
require 'bake/extensions'
|
1
2
|
require 'bake/context'
|
2
3
|
require 'bake/project_loader'
|
3
|
-
require 'bake/string_utils'
|
4
4
|
require 'bake/plugin'
|
5
|
+
require 'bake/system_utils'
|
5
6
|
require 'bake_version'
|
6
7
|
require 'optparse'
|
7
8
|
|
@@ -22,8 +23,6 @@ module Bake
|
|
22
23
|
def initialize
|
23
24
|
raise "App instance already created" if @@instance
|
24
25
|
@@instance = self
|
25
|
-
@context = Context.new
|
26
|
-
@context.import(Plugins::System, Plugins::Macro)
|
27
26
|
end
|
28
27
|
|
29
28
|
def run(*args)
|
@@ -31,9 +30,14 @@ module Bake
|
|
31
30
|
props, targets = process_excess_args(*args)
|
32
31
|
props[:platform] = determine_platform
|
33
32
|
|
33
|
+
sys = SystemUtils.new(:verbose => options[:verbose],
|
34
|
+
:dry_run => options[:dry_run])
|
35
|
+
@context = Context.new(sys)
|
36
|
+
# must execute this in context so that plugins are loaded properly
|
37
|
+
@context.context_eval(nil, 'import System, Macro')
|
34
38
|
@project_loader = ProjectLoader.new(@context, Dir.pwd, props)
|
35
39
|
targets = targets.collect do |name|
|
36
|
-
target = @project_loader.invok_project.
|
40
|
+
target = @project_loader.invok_project.resolve(name)
|
37
41
|
if !target
|
38
42
|
raise CommandLineParseError,
|
39
43
|
"could not find target '#{name}'"
|
@@ -41,7 +45,7 @@ module Bake
|
|
41
45
|
target
|
42
46
|
end
|
43
47
|
|
44
|
-
method = options[:clean] ? :
|
48
|
+
method = options[:clean] ? :clean_r : :build_r
|
45
49
|
if targets.empty?
|
46
50
|
@project_loader.invok_project.send(method)
|
47
51
|
else
|
@@ -68,7 +72,13 @@ module Bake
|
|
68
72
|
opts.on('--clean', 'Clean the given targets') do |clean|
|
69
73
|
options[:clean] = clean
|
70
74
|
end
|
71
|
-
opts.
|
75
|
+
opts.on('-n', '--dry-run', 'Do not update targets') do |dr|
|
76
|
+
options[:dry_run] = dr
|
77
|
+
end
|
78
|
+
opts.on('--verbose', 'Verbose output') do |verbose|
|
79
|
+
options[:verbose] = verbose
|
80
|
+
end
|
81
|
+
opts.on_tail('-h', '--help', 'Display usage message') do
|
72
82
|
raise ProgramInfoRequested, opts
|
73
83
|
end
|
74
84
|
opts.on_tail('--version', 'Show version information') do
|
@@ -114,6 +124,7 @@ exit_code = 1
|
|
114
124
|
begin
|
115
125
|
app = Bake::App.new
|
116
126
|
app.run(*ARGV)
|
127
|
+
puts 'build successful'
|
117
128
|
exit_code = 0
|
118
129
|
rescue Bake::ProgramInfoRequested => e
|
119
130
|
puts e.message
|
@@ -122,6 +133,7 @@ rescue Bake::CommandLineParseError => e
|
|
122
133
|
puts e.message
|
123
134
|
rescue Exception => e
|
124
135
|
puts parse_backtrace(e.backtrace) + ': ' + e.message
|
136
|
+
puts 'build failed'
|
125
137
|
end
|
126
138
|
|
127
139
|
exit(exit_code)
|
data/lib/bake/configuration.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
module Bake
|
2
|
-
class PropertyNotFoundException < RuntimeError
|
3
|
-
end
|
4
|
-
|
5
|
-
class TemplateEvaluationException < RuntimeError
|
6
|
-
end
|
2
|
+
class PropertyNotFoundException < RuntimeError; end
|
3
|
+
class TemplateEvaluationException < RuntimeError; end
|
7
4
|
|
8
5
|
module Configuration
|
9
6
|
def has_prop?(key)
|
@@ -24,34 +21,29 @@ module Bake
|
|
24
21
|
cfg = self
|
25
22
|
opts = []
|
26
23
|
while cfg
|
27
|
-
|
28
|
-
|
24
|
+
if cfg.options.has_key?(key)
|
25
|
+
cfg.options[key].each { |val| opts << value_of(val) }
|
26
|
+
end
|
29
27
|
cfg = cfg.parent
|
30
28
|
end
|
31
29
|
return opts
|
32
30
|
else
|
33
31
|
cfg = self
|
34
|
-
|
35
|
-
|
32
|
+
default = nil
|
33
|
+
found_default = false
|
36
34
|
while cfg
|
37
|
-
if cfg.
|
38
|
-
return value_of(cfg.
|
35
|
+
if cfg.options.has_key?(key)
|
36
|
+
return value_of(cfg.options[key])
|
39
37
|
end
|
40
|
-
if !
|
41
|
-
|
42
|
-
|
43
|
-
found_opt = true
|
44
|
-
elsif !found_default && cfg.defaults.has_key?(key)
|
45
|
-
default = cfg.defaults[key]
|
46
|
-
found_default = true
|
47
|
-
end
|
38
|
+
if !found_default && cfg.defaults.has_key?(key)
|
39
|
+
default = cfg.defaults[key]
|
40
|
+
found_default = true
|
48
41
|
end
|
49
42
|
cfg = cfg.parent
|
50
43
|
end
|
51
|
-
if
|
52
|
-
|
53
|
-
|
54
|
-
return value_of(default)
|
44
|
+
if found_default
|
45
|
+
val = value_of(default)
|
46
|
+
return val
|
55
47
|
end
|
56
48
|
raise PropertyNotFoundException, "no such property '#{key}'"
|
57
49
|
end
|
@@ -81,21 +73,6 @@ module Bake
|
|
81
73
|
end
|
82
74
|
end
|
83
75
|
|
84
|
-
def req(prop)
|
85
|
-
key, val = key_val(prop)
|
86
|
-
raise 'no multivalue requirements' if multival(key)
|
87
|
-
cfg = self
|
88
|
-
while cfg
|
89
|
-
req = cfg.requirements[key]
|
90
|
-
if req
|
91
|
-
raise 'conflicting requirements' if req != val
|
92
|
-
break
|
93
|
-
end
|
94
|
-
cfg = cfg.parent
|
95
|
-
end
|
96
|
-
requirements[key] = val
|
97
|
-
end
|
98
|
-
|
99
76
|
def default(prop)
|
100
77
|
key, val = key_val(prop)
|
101
78
|
raise 'no multivalue defaults' if multival(key)
|
@@ -107,17 +84,12 @@ module Bake
|
|
107
84
|
return @options ||= {}
|
108
85
|
end
|
109
86
|
|
110
|
-
def requirements
|
111
|
-
return @requirements ||= {}
|
112
|
-
end
|
113
|
-
|
114
87
|
def defaults
|
115
88
|
return @defaults ||= {}
|
116
89
|
end
|
117
90
|
|
118
91
|
def has_prop_impl(key)
|
119
|
-
return
|
120
|
-
options.has_key?(key) ||
|
92
|
+
return options.has_key?(key) ||
|
121
93
|
defaults.has_key?(key)
|
122
94
|
end
|
123
95
|
|
data/lib/bake/context.rb
CHANGED
@@ -1,18 +1,28 @@
|
|
1
|
-
require 'bake/
|
2
|
-
require 'bake/
|
1
|
+
require 'bake/addon'
|
2
|
+
require 'bake/plugin'
|
3
3
|
|
4
4
|
module Bake
|
5
5
|
class Context
|
6
|
-
|
6
|
+
include PluginManager
|
7
7
|
|
8
|
-
|
8
|
+
DEFAULT_SEARCH_DIR = File.join(File.dirname(__FILE__), '..')
|
9
|
+
|
10
|
+
attr_reader :current, :default_toolset
|
11
|
+
|
12
|
+
def initialize(sys)
|
13
|
+
@sys = sys
|
9
14
|
@current = nil
|
10
|
-
@commands = {
|
15
|
+
@commands = {}
|
16
|
+
@default_toolset = Toolset.new(@sys)
|
11
17
|
end
|
12
18
|
|
13
19
|
def import(*addon_classes)
|
14
20
|
addon_classes.each do |addon_class|
|
15
|
-
|
21
|
+
if addon_class.inherits?(Toolset)
|
22
|
+
addon = addon_class.new(@sys)
|
23
|
+
else
|
24
|
+
addon = addon_class.new
|
25
|
+
end
|
16
26
|
addon.commands.each_pair do |name, block|
|
17
27
|
register(name, &block)
|
18
28
|
end
|
@@ -21,8 +31,12 @@ module Bake
|
|
21
31
|
|
22
32
|
def using(*addon_classes)
|
23
33
|
addon_classes.each do |addon_class|
|
24
|
-
|
25
|
-
|
34
|
+
if addon_class.inherits?(Toolset)
|
35
|
+
addon = addon_class.new(@sys)
|
36
|
+
else
|
37
|
+
addon = addon_class.new
|
38
|
+
end
|
39
|
+
@current.opt(:addons => addon)
|
26
40
|
end
|
27
41
|
end
|
28
42
|
|
@@ -31,6 +45,11 @@ module Bake
|
|
31
45
|
end
|
32
46
|
|
33
47
|
def context_eval(target, str = nil, file = nil, &block)
|
48
|
+
context = Thread.current[:bake_context]
|
49
|
+
if !context.nil? && !context.equal?(self)
|
50
|
+
raise 'multiple overlapping contexts detected'
|
51
|
+
end
|
52
|
+
Thread.current[:bake_context] = self
|
34
53
|
old = @current
|
35
54
|
begin
|
36
55
|
@current = target
|
@@ -41,9 +60,16 @@ module Bake
|
|
41
60
|
end
|
42
61
|
ensure
|
43
62
|
@current = old
|
63
|
+
Thread.current[:bake_context] = nil
|
44
64
|
end
|
45
65
|
end
|
46
66
|
|
67
|
+
def search_dirs
|
68
|
+
search_dirs = [ DEFAULT_SEARCH_DIR ]
|
69
|
+
return search_dirs if !@current
|
70
|
+
return search_dirs + @current[:plugin_paths]
|
71
|
+
end
|
72
|
+
|
47
73
|
def method_missing(name, *args, &block)
|
48
74
|
command = @commands[name]
|
49
75
|
return call(command, *args, &block) if command
|
@@ -51,7 +77,7 @@ module Bake
|
|
51
77
|
if @current.respond_to?(name)
|
52
78
|
return @current.send(name, *args, &block)
|
53
79
|
end
|
54
|
-
@current
|
80
|
+
@current[:addons].each do |addon|
|
55
81
|
command = addon.commands[name]
|
56
82
|
return call(command, *args, &block) if command
|
57
83
|
end
|
@@ -59,6 +85,12 @@ module Bake
|
|
59
85
|
raise "unknown command '#{name}'"
|
60
86
|
end
|
61
87
|
|
88
|
+
def self.const_missing(name)
|
89
|
+
context = Thread.current[:bake_context]
|
90
|
+
return super if !context
|
91
|
+
context.load_plugin("Bake::Plugins::#{name}")
|
92
|
+
end
|
93
|
+
|
62
94
|
private
|
63
95
|
def call(command, *args, &block)
|
64
96
|
args << block if block
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Object
|
2
|
+
# If +self+ is nil, returns a zero-length array, otherwise, if +self+
|
3
|
+
# responds to the +to_ary+ method, returns +to_ary+. Otherwise, returns
|
4
|
+
# an array of length one containing +self+.
|
5
|
+
def make_array
|
6
|
+
if nil?
|
7
|
+
return []
|
8
|
+
elsif respond_to?(:to_ary)
|
9
|
+
return to_ary
|
10
|
+
end
|
11
|
+
return [ self ]
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the singleton class for this object's class.
|
15
|
+
def metaclass
|
16
|
+
return class << self; self; end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
main = self
|
21
|
+
Object.metaclass.send(:define_method, :main_object) { main }
|
22
|
+
Object.metaclass.send(:public, :main_object)
|
data/lib/bake/file_target.rb
CHANGED
@@ -4,11 +4,16 @@ module Bake
|
|
4
4
|
class FileTarget < Target
|
5
5
|
attr_reader :path
|
6
6
|
|
7
|
-
def
|
8
|
-
super(parent, nil)
|
7
|
+
def post_initialize(path)
|
9
8
|
@path = path
|
10
9
|
@built = true
|
11
10
|
end
|
11
|
+
|
12
|
+
def mtimes
|
13
|
+
raise "file no longer exists '#{path}'" if !File.exists?(path)
|
14
|
+
return [ File.mtime(path) ]
|
15
|
+
end
|
16
|
+
|
17
|
+
alias :id :path
|
12
18
|
end
|
13
19
|
end
|
14
|
-
|