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 CHANGED
@@ -1,6 +1,28 @@
1
1
  = Bake Changelog
2
2
 
3
- == Version 0.1.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 target to build its products. For example, an executable target may depend on a the functionality available in a library and thus it must link against that library.
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
- === Options
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: undefined symbol '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.
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 '.:test_lib', 'test_exe/source1.cpp'
90
+ dep 'test_lib', 'test_exe/source1.cpp'
87
91
  end
88
92
 
89
- 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.
93
+ dep 'test_exe'
90
94
 
91
- Note also the funny way we've referenced the <tt>test_lib</tt> library. Every target reference is composed of two parts: the path to the target's project and the name of the target, separated by a colon. Since the <tt>test_lib</tt> library is in the same directory as the executable the path part of the reference is simply a '.'.
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 '.:test_lib', 'test_exe/source1.cpp'
110
+ dep 'test_lib', 'test_exe/source1.cpp'
107
111
  end
108
112
 
109
- 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>${projname}</tt> business?
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/${projname}'</tt>.
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
 
@@ -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.find(name)
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] ? :clean : :build
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.on_tail('-h', '--help', 'Show usage') do
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)
@@ -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
- vals = cfg.options[key]
28
- opts.concat(vals.collect { |val| value_of(val) }) if vals
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
- opt = default = nil
35
- found_opt = found_default = false
32
+ default = nil
33
+ found_default = false
36
34
  while cfg
37
- if cfg.requirements.has_key?(key)
38
- return value_of(cfg.requirements[key])
35
+ if cfg.options.has_key?(key)
36
+ return value_of(cfg.options[key])
39
37
  end
40
- if !found_opt
41
- if cfg.options.has_key?(key)
42
- opt = cfg.options[key]
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 found_opt
52
- return value_of(opt)
53
- elsif found_default
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 requirements.has_key?(key) ||
120
- options.has_key?(key) ||
92
+ return options.has_key?(key) ||
121
93
  defaults.has_key?(key)
122
94
  end
123
95
 
@@ -1,18 +1,28 @@
1
- require 'bake/string_utils.rb'
2
- require 'bake/addon.rb'
1
+ require 'bake/addon'
2
+ require 'bake/plugin'
3
3
 
4
4
  module Bake
5
5
  class Context
6
- attr_reader :current
6
+ include PluginManager
7
7
 
8
- def initialize
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
- addon = addon_class.new
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
- addon = addon_class.new
25
- @current.opt(:addons => addon_class.new)
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.get(:addons).each do |addon|
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,3 @@
1
+ Dir[File.join(File.dirname(__FILE__), 'extensions', '*.rb')].each do |file|
2
+ require 'bake/extensions/' + File.basename(file, '.rb')
3
+ end
@@ -0,0 +1,11 @@
1
+ class Class
2
+ # Returns true if +self+ inherits from +ancestor+
3
+ def inherits?(ancestor)
4
+ sup = self
5
+ while sup
6
+ return true if sup.equal?(ancestor)
7
+ sup = sup.superclass
8
+ end
9
+ return false
10
+ end
11
+ end
@@ -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)
@@ -20,4 +20,3 @@ class String
20
20
  return gsub(/([a-z0-9])([A-Z])/) { |val| $1 + '_' + $2 }.downcase
21
21
  end
22
22
  end
23
-
@@ -4,11 +4,16 @@ module Bake
4
4
  class FileTarget < Target
5
5
  attr_reader :path
6
6
 
7
- def initialize(parent, path)
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
-