bake 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+ = Bake Changelog
2
+
3
+ == Version 0.1.1
4
+
5
+ * Created a plugin infrastructure whereby missing constants now result in a search of the plugins/ directory
6
+ * Implemented Addon module which enables adding commands to bakefile contexts via the import method
7
+ * Removed scheme concept in favour of addon plugin classes so all concre Toolset subclasses (e.g. Cpp::Gcc) are now addon plugins
8
+ * Context#macro and Context#glob were moved to addon classes named Macro and System respectively which are plugins that are automatically imported into bakefile contexts
9
+ * Targets no longer found by moving up the target tree, instead all paths are relative to the current project, that is, the project owning the current target
10
+ * Added some content to the CONCEPTS document
11
+ * Introduced Context#using method which is similar to import, except that commands are looked up from addons stored in the :addons property of the current target
12
+ * Removed Cpp::Library#src and Cpp::Executable#src in favor of listing file dependencies in the dep list
13
+ * Added FileTarget class that represents simple file dependencies
14
+ * Created Target#add_dep method that can be overridden to manage individual dependency additions (for example to insert a Cpp::Object between a FileTarget and main Cpp target)
15
+ * Child targets are no longer considered dependencies and so are not built automatically when their parents are built
16
+
data/CONCEPTS CHANGED
@@ -1,2 +1,54 @@
1
1
  = Concepts
2
2
 
3
+ == Bake's Philosophy
4
+
5
+ Bake is a Ruby based build system similar to Rake[http://rake.rubyforge.org] and Rant[http://rant.rubyforge.org]. Unlike either of these systems, however, there is a logical separation between the the components that are being built and the methods used to build them. This separation is important for large projects having many logical components such as executables and libraries, and also supporting many compilers or platforms.
6
+
7
+ In the good old days when Make was king, adding another supported platform to a project was usually a traumatic experience. The chief reason for the difficulty was almost always that the declaration of a component and the actions executed to build that component were tightly coupled. So the structure of the project was highly dependent on the content of the actions that build it.
8
+
9
+ The pitfalls of Make are readily apparent with C++ projects that support Microsoft Visual Studio and GCC. Simple projects are easy enough to accomodate in Make, but as soon as you need to support dynamic libraries and unit test runners and platform dependent build parameters you can quickly find the number of lines in your Make code outpacing the code for your project!
10
+
11
+ The fundamental reason for this is that your project structure and the actions that build your project are orthogonal concepts. Make forces you to intertwine these two concepts causing your Makefiles to balloon in size. Bake, on the other hand, disentangles the target and toolset concepts and allows you to grow the set of targets and the toolsets that build them independently. This is the primary goal of the Bake project.
12
+
13
+ Another goal is to provide users with a large set of pre-written toolsets that will perform the actions required to turn your project into built products. Thus, in most cases, the end user simply defines the structure of the their project, indicates which toolsets to use and Bake does the rest.
14
+
15
+ The end result is that Bake requires a bare minimum of code to provide all the instructions that are needed to build a project. Furthermore, the bulk of the code defines the structure of the project rather than the behaviour of the build tools involved, so the definition files also act as a guide to the project layout, instead of being a distracting maze of complex build actions.
16
+
17
+ == Targets
18
+
19
+ In Bake, projects are broken up into components called *targets*. Targets are the basic unit of a project and can represent C libraries, zip files, Java classes or anything else that belongs in a project.
20
+
21
+ Targets are tree-like. Each target has a single parent target and can have any number of child targets. Every target that belongs in a particular project is connected in a single tree.
22
+
23
+ === Products
24
+
25
+ Every target can have a number of *products*. The products of a target are the concrete set of files that are created during the build process.
26
+
27
+ === Dependencies
28
+
29
+ Targets are related to each other by two relations. The first is the parent-child relation that makes up the tree structure of a project. The other is the *dependency* relation.
30
+
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
+
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.
34
+
35
+ === Options
36
+
37
+ === Requirements
38
+
39
+ == Bakefiles
40
+
41
+ === Commands
42
+
43
+ === Projects
44
+
45
+ == Toolsets
46
+
47
+ == Features
48
+
49
+ === Macros
50
+
51
+ === Plugins
52
+
53
+ === Addins
54
+
data/TUTORIAL CHANGED
@@ -8,7 +8,7 @@ Once you've installed Bake, you should be able to execute it by running the <tt>
8
8
 
9
9
  # root.bake
10
10
  exe 'test' do
11
- src 'main.cpp'
11
+ dep 'main.cpp'
12
12
  end
13
13
 
14
14
  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>:
@@ -34,7 +34,7 @@ If you run <tt>bake</tt> from the <i>test/</i> directory you should see output r
34
34
  Generally, it is a good idea to keep your project definitions separate from which toolset you're using since your choice of toolset is conceptually distinct from your project layout. This is especially true from cross-platform projects. For this reason, in this project we'll keep this information in a separate bakefile called <i>config.bake</i> which will also live in the <i>test/</i> directory:
35
35
 
36
36
  # config.bake
37
- using 'cpp.gcc'
37
+ using Cpp::Gcc
38
38
 
39
39
  The <tt>using</tt> directive above tells Bake that when building C++ targets, it should use the <tt>gcc</tt> toolset. If you now run the <tt>bake</tt> command, and as long as your toolset is properly configured, you should get an executable file called <i>test</i> (or <i>test.exe</i> on Windows platforms) in the <i>test/</i> directory.
40
40
 
@@ -46,9 +46,7 @@ Unfortunately, the <i>test</i> executable will probably be accompanied by a numb
46
46
 
47
47
  exe 'test' do
48
48
  opt :outdir => 'bin'
49
-
50
- src 'main.cpp'
51
-
49
+ dep 'main.cpp'
52
50
  end
53
51
 
54
52
  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.
@@ -57,9 +55,7 @@ Delete all the product files generated by the last build so that your directory
57
55
 
58
56
  test/
59
57
  config.bake
60
-
61
58
  root.bake
62
-
63
59
  main.cpp
64
60
 
65
61
  Now run Bake again. Note that a new directory called <i>bin/</i> has been created and that all the products of the build have been placed inside. C++ executable targets have a number of other properties that affect different aspects of the build. For example, to turn off multithreading for a single threaded application, you would specify "<tt>opt :multithreaded? => false</tt>". Note that we've used a boolean value type instead of a string. The type is important as a value of <tt>'false'</tt> would result in the opposite behavior we would expect (this is because strings always evaluate to true when examined in a boolean context). For a full list of target properties and their types, see the Reference.
@@ -83,38 +79,31 @@ As a first try at managing this project, let's create a <i>root.bake</i> in the
83
79
 
84
80
  # root.bake
85
81
  lib 'test_lib' do
86
- src 'test_lib/source1.cpp', 'test_lib/source2.cpp'
82
+ dep 'test_lib/source1.cpp', 'test_lib/source2.cpp'
87
83
  end
88
84
 
89
85
  exe 'test_exe' do
90
- dep 'test_lib'
91
- src 'test_exe/source1.cpp'
86
+ dep '.:test_lib', 'test_exe/source1.cpp'
92
87
  end
93
88
 
94
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.
95
90
 
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 '.'.
92
+
96
93
  === More About Properties
97
94
 
98
- The easiest way to avoid conflicts like these is to set the <tt>:outdir</tt> property differently on each target so that the object files will live in different directories. To do this, however, we're going to take a shortcut and only set the output directory once:
95
+ The easiest way to avoid conflicts where output files overwrite eachother is to set the <tt>:outdir</tt> property differently on each target so that the object files will live in different directories. To do this, however, we're going to take a shortcut and only set the output directory once:
99
96
 
100
97
  # root.bake
101
98
  opt :outdir => 'bin/${name}'
102
-
103
-
104
99
  lib 'test_lib' do
105
-
106
- src 'test_lib/source1.cpp', 'test_lib/source2.cpp'
107
-
100
+ opt :name => 'test_lib'
101
+ dep 'test_lib/source1.cpp', 'test_lib/source2.cpp'
108
102
  end
109
103
 
110
-
111
-
112
104
  exe 'test_exe' do
113
-
114
- dep 'test_lib'
115
-
116
- src 'test_exe/source1.cpp'
117
-
105
+ opt :name => 'test_exe'
106
+ dep '.:test_lib', 'test_exe/source1.cpp'
118
107
  end
119
108
 
120
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?
@@ -1,6 +1,7 @@
1
- require 'bake/scheme_loader'
2
1
  require 'bake/context'
3
2
  require 'bake/project_loader'
3
+ require 'bake/string_utils'
4
+ require 'bake/plugin'
4
5
  require 'bake_version'
5
6
  require 'optparse'
6
7
 
@@ -21,8 +22,8 @@ module Bake
21
22
  def initialize
22
23
  raise "App instance already created" if @@instance
23
24
  @@instance = self
24
- @scheme_loader = SchemeLoader.new
25
- @context = Context.new(@scheme_loader)
25
+ @context = Context.new
26
+ @context.import(Plugins::System, Plugins::Macro)
26
27
  end
27
28
 
28
29
  def run(*args)
@@ -42,41 +43,13 @@ module Bake
42
43
 
43
44
  method = options[:clean] ? :clean : :build
44
45
  if targets.empty?
45
- send(method, @project_loader.invok_project)
46
+ @project_loader.invok_project.send(method)
46
47
  else
47
- targets.each { |target| send(method, target) }
48
+ targets.each { |target| target.send(method) }
48
49
  end
49
50
  end
50
51
 
51
52
  private
52
- def build(target)
53
- return if target[:built?]
54
- target.deps.each { |dep| build(dep) }
55
- target.children.each { |child| build(child) }
56
- toolset = toolset(target)
57
- Dir.chdir(target[:cwdir]) do
58
- toolset.build(target) if toolset
59
- end
60
- target.opt :built? => true
61
- end
62
-
63
- def clean(target)
64
- target.children.each do |child|
65
- clean(child)
66
- end
67
- toolset = toolset(target)
68
- toolset.clean(target) if toolset
69
- end
70
-
71
- def toolset(target)
72
- if !target.class.const_defined?(:SCHEME)
73
- raise "no toolset for target '#{target.class.name}'"
74
- end
75
- scheme = target.class.const_get(:SCHEME)
76
- return scheme.toolset if scheme
77
- return nil
78
- end
79
-
80
53
  def determine_platform
81
54
  if RUBY_PLATFORM =~ /darwin/i
82
55
  return 'osx'
@@ -134,7 +107,7 @@ def parse_backtrace(backtrace)
134
107
  if backtrace.find { |entry| entry =~ /^.*?(config|root|sub)\.bake:\d+/ }
135
108
  return $&
136
109
  end
137
- return 'internal error'
110
+ return backtrace.join("\n")
138
111
  end
139
112
 
140
113
  exit_code = 1
@@ -0,0 +1,20 @@
1
+ module Bake
2
+ module Addon
3
+ def command(name, method = nil, &block)
4
+ if !block
5
+ method ||= name
6
+ addon = self
7
+ block = Proc.new do |*args|
8
+ block = args.last.is_a?(Proc) ? args.pop : nil
9
+ addon.send(method, self, *args, &block)
10
+ end
11
+ end
12
+ commands[name] = block
13
+ end
14
+
15
+ def commands
16
+ return @commands ||= { }
17
+ end
18
+ end
19
+ end
20
+
@@ -1,10 +1,33 @@
1
1
  require 'bake/string_utils.rb'
2
+ require 'bake/addon.rb'
2
3
 
3
4
  module Bake
4
5
  class Context
5
- def initialize(scheme_loader)
6
- @scheme_loader = scheme_loader
6
+ attr_reader :current
7
+
8
+ def initialize
7
9
  @current = nil
10
+ @commands = { }
11
+ end
12
+
13
+ def import(*addon_classes)
14
+ addon_classes.each do |addon_class|
15
+ addon = addon_class.new
16
+ addon.commands.each_pair do |name, block|
17
+ register(name, &block)
18
+ end
19
+ end
20
+ end
21
+
22
+ def using(*addon_classes)
23
+ addon_classes.each do |addon_class|
24
+ addon = addon_class.new
25
+ @current.opt(:addons => addon_class.new)
26
+ end
27
+ end
28
+
29
+ def register(name, &block)
30
+ @commands[name] = block
8
31
  end
9
32
 
10
33
  def context_eval(target, str = nil, file = nil, &block)
@@ -21,78 +44,35 @@ module Bake
21
44
  end
22
45
  end
23
46
 
24
- def using(scheme_and_toolset)
25
- scheme_name, toolset_name = scheme_and_toolset.to_s.split('.')
26
- toolset_name = 'default' if !toolset_name
27
- scheme = @scheme_loader.scheme(scheme_name)
28
- raise "invalid scheme '#{scheme_name}'" if !scheme
29
- scheme.toolset = toolset_name
30
- end
31
-
32
- def glob(*args)
33
- has_options = args.last.respond_to?(:to_hash)
34
- options = has_options ? args.pop.to_hash : {}
35
- exclude = array_opt(options, :exclude)
36
- files = []
37
- args.each do |pat|
38
- matches = Dir[pat]
39
- matches.each do |file|
40
- if !exclude.find { |exc| File.fnmatch(exc, file) }
41
- files.push(file)
42
- end
47
+ def method_missing(name, *args, &block)
48
+ command = @commands[name]
49
+ return call(command, *args, &block) if command
50
+ if @current
51
+ if @current.respond_to?(name)
52
+ return @current.send(name, *args, &block)
43
53
  end
44
- end
45
- if block_given?
46
- for i in 0...files.size
47
- yield(files[i])
54
+ @current.get(:addons).each do |addon|
55
+ command = addon.commands[name]
56
+ return call(command, *args, &block) if command
48
57
  end
49
58
  end
50
- return files
51
- end
52
-
53
- def macro(name, &block)
54
- raise "no block given for macro '#{name}'" if !block
55
- @current.opt(:macros => { :name => name, :block => block })
56
- end
57
-
58
- def method_missing(method, *args, &block)
59
- @scheme_loader.schemes.each do |name, scheme|
60
- if scheme.has_constructor?(method)
61
- target = scheme.construct(method, @current, *args)
62
- context_eval(target, &block) if block
63
- return target
64
- end
65
- end
66
- return object_method_missing(method, *args, &block) if !@current
67
- macro = @current[:macros].find { |macro| macro[:name] == method }
68
- if macro
69
- return instance_exec(*args, &macro[:block])
70
- end
71
- if @current.respond_to?(method)
72
- return @current.send(method, *args, &block)
73
- end
74
- raise "undefined symbol '#{method}'"
59
+ raise "unknown command '#{name}'"
75
60
  end
76
61
 
77
62
  private
78
- def array_opt(options, name)
79
- opt = options[name]
80
- return [] if !opt
81
- if opt.respond_to?(:to_ary)
82
- opt = opt.to_ary
83
- else
84
- opt = [ opt ]
85
- end
63
+ def call(command, *args, &block)
64
+ args << block if block
65
+ return instance_exec(*args, &command)
86
66
  end
87
67
 
88
68
  # took this implementation from http://tinyurl.com/gzrtn
89
69
  def instance_exec(*args, &block)
90
70
  mname = "__instance_exec_#{Thread.current.object_id.abs}"
91
- class << self; self end.class_eval{ define_method(mname, &block) }
71
+ class << self; self end.class_eval { define_method(mname, &block) }
92
72
  begin
93
73
  ret = send(mname, *args)
94
74
  ensure
95
- class << self; self end.class_eval{ undef_method(mname) } rescue nil
75
+ class << self; self end.class_eval { undef_method(mname) } rescue nil
96
76
  end
97
77
  ret
98
78
  end
@@ -0,0 +1,14 @@
1
+ require 'bake/target'
2
+
3
+ module Bake
4
+ class FileTarget < Target
5
+ attr_reader :path
6
+
7
+ def initialize(parent, path)
8
+ super(parent, nil)
9
+ @path = path
10
+ @built = true
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,72 @@
1
+ require 'bake/string_utils'
2
+
3
+ class Object
4
+ def self.const_missing(name)
5
+ return Bake::Plugins.const_get(name)
6
+ end
7
+ end
8
+
9
+ module Bake
10
+ class PluginManager
11
+ @@plugins = { }
12
+ @@search_dirs = [ File.join(File.dirname(__FILE__), '..') ]
13
+
14
+ def self.search_dirs
15
+ return @@search_dirs
16
+ end
17
+
18
+ def self.plugins
19
+ return @@plugins
20
+ end
21
+
22
+ def self.load_plugin(full_name)
23
+ parent = Plugins
24
+ plugin = nil
25
+ full_name.to_s.split('::').each do |name|
26
+ plugin = load_child_plugin(parent, name)
27
+ end
28
+ return plugin
29
+ end
30
+
31
+ def self.load_child_plugin(parent, name)
32
+ name = name.to_s
33
+ full_name = parent.name + '::' + name
34
+ plugin_file = full_name.underscore.gsub('::', '/')
35
+
36
+ plugin = nil
37
+ if parent.const_defined?(name)
38
+ plugin = parent.const_get(name)
39
+ end
40
+
41
+ @@search_dirs.each do |rootdir|
42
+ if File.exists?(File.join(rootdir, plugin_file + '.rb'))
43
+ require plugin_file
44
+ if !plugin && parent.const_defined?(name)
45
+ plugin = parent.const_get(name)
46
+ end
47
+ end
48
+
49
+ if File.directory?(File.join(rootdir, plugin_file))
50
+ if !plugin
51
+ plugin = Module.new
52
+ parent.const_set(name, plugin)
53
+ end
54
+ def plugin.const_missing(name)
55
+ return PluginManager.load_child_plugin(self, name)
56
+ end
57
+ end
58
+ end
59
+
60
+ raise "could not find plugin '#{name}'" if !plugin
61
+ @@plugins[full_name] = plugin
62
+ return plugin
63
+ end
64
+ end
65
+
66
+ module Plugins
67
+ def self.const_missing(name)
68
+ return PluginManager.load_plugin(name)
69
+ end
70
+ end
71
+ end
72
+