rscons 0.0.1
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.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +42 -0
- data/Rakefile +21 -0
- data/build_tests/build_dir/build.rb +5 -0
- data/build_tests/build_dir/src/one/one.c +6 -0
- data/build_tests/build_dir/src/two/two.c +7 -0
- data/build_tests/build_dir/src/two/two.h +6 -0
- data/build_tests/clone_env/build.rb +11 -0
- data/build_tests/clone_env/src/program.c +6 -0
- data/build_tests/custom_builder/build.rb +16 -0
- data/build_tests/custom_builder/program.c +7 -0
- data/build_tests/header/build.rb +3 -0
- data/build_tests/header/header.c +7 -0
- data/build_tests/header/header.h +6 -0
- data/build_tests/simple/build.rb +4 -0
- data/build_tests/simple/simple.c +6 -0
- data/build_tests/simple_cc/build.rb +4 -0
- data/build_tests/simple_cc/simple.cc +8 -0
- data/lib/rscons.rb +20 -0
- data/lib/rscons/builder.rb +20 -0
- data/lib/rscons/builders/object.rb +69 -0
- data/lib/rscons/builders/program.rb +48 -0
- data/lib/rscons/cache.rb +147 -0
- data/lib/rscons/environment.rb +227 -0
- data/lib/rscons/monkey/module.rb +7 -0
- data/lib/rscons/monkey/string.rb +19 -0
- data/lib/rscons/varset.rb +83 -0
- data/lib/rscons/version.rb +4 -0
- data/rscons.gemspec +29 -0
- data/spec/build_tests_spec.rb +146 -0
- data/spec/spec_helper.rb +1 -0
- metadata +232 -0
@@ -0,0 +1,227 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Rscons
|
5
|
+
# The Environment class is the main programmatic interface to RScons. It
|
6
|
+
# contains a collection of construction variables, options, builders, and
|
7
|
+
# rules for building targets.
|
8
|
+
class Environment
|
9
|
+
# [Array] of {Builder} objects.
|
10
|
+
attr_reader :builders
|
11
|
+
|
12
|
+
# Create an Environment object.
|
13
|
+
# @param variables [Hash]
|
14
|
+
# The variables hash can contain both construction variables, which are
|
15
|
+
# uppercase strings (such as "CC" or "LDFLAGS"), and RScons options,
|
16
|
+
# which are lowercase symbols (such as :echo).
|
17
|
+
# If a block is given, the Environment object is yielded to the block and
|
18
|
+
# when the block returns, the {#process} method is automatically called.
|
19
|
+
def initialize(variables = {})
|
20
|
+
@varset = VarSet.new(variables)
|
21
|
+
@targets = {}
|
22
|
+
@builders = {}
|
23
|
+
@build_dirs = {}
|
24
|
+
@varset[:exclude_builders] ||= []
|
25
|
+
unless @varset[:exclude_builders] == :all
|
26
|
+
exclude_builders = Set.new(@varset[:exclude_builders] || [])
|
27
|
+
DEFAULT_BUILDERS.each do |builder_class|
|
28
|
+
unless exclude_builders.include?(builder_class.short_name)
|
29
|
+
add_builder(builder_class.new)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
(@varset[:builders] || []).each do |builder|
|
34
|
+
add_builder(builder)
|
35
|
+
end
|
36
|
+
@varset[:echo] ||= :command
|
37
|
+
|
38
|
+
if block_given?
|
39
|
+
yield self
|
40
|
+
self.process
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Make a copy of the Environment object.
|
45
|
+
# The cloned environment will contain a copy of all environment options,
|
46
|
+
# construction variables, builders, and build directories. It will not
|
47
|
+
# contain a copy of the targets.
|
48
|
+
# If a block is given, the Environment object is yielded to the block and
|
49
|
+
# when the block returns, the {#process} method is automatically called.
|
50
|
+
def clone(variables = {})
|
51
|
+
env = Environment.new()
|
52
|
+
@builders.each do |builder_name, builder|
|
53
|
+
env.add_builder(builder)
|
54
|
+
end
|
55
|
+
@build_dirs.each do |src_dir, obj_dir|
|
56
|
+
env.build_dir(src_dir, obj_dir)
|
57
|
+
end
|
58
|
+
env.append(@varset)
|
59
|
+
env.append(variables)
|
60
|
+
|
61
|
+
if block_given?
|
62
|
+
yield env
|
63
|
+
env.process
|
64
|
+
end
|
65
|
+
env
|
66
|
+
end
|
67
|
+
|
68
|
+
# Add a {Builder} object to the Environment.
|
69
|
+
def add_builder(builder)
|
70
|
+
@builders[builder.class.short_name] = builder
|
71
|
+
var_defs = builder.default_variables(self)
|
72
|
+
if var_defs
|
73
|
+
var_defs.each_pair do |var, val|
|
74
|
+
@varset[var] ||= val
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Specify a build directory for this Environment.
|
80
|
+
# Source files from src_dir will produce object files under obj_dir.
|
81
|
+
def build_dir(src_dir, obj_dir)
|
82
|
+
@build_dirs[src_dir.gsub('\\', '/')] = obj_dir.gsub('\\', '/')
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return the file name to be built from source_fname with suffix suffix.
|
86
|
+
# This method takes into account the Environment's build directories.
|
87
|
+
# It also creates any parent directories needed to be able to open and
|
88
|
+
# write to the output file.
|
89
|
+
def get_build_fname(source_fname, suffix)
|
90
|
+
build_fname = source_fname.set_suffix(suffix).gsub('\\', '/')
|
91
|
+
@build_dirs.each do |src_dir, obj_dir|
|
92
|
+
build_fname.sub!(/^#{src_dir}\//, "#{obj_dir}/")
|
93
|
+
end
|
94
|
+
FileUtils.mkdir_p(File.dirname(build_fname))
|
95
|
+
build_fname
|
96
|
+
end
|
97
|
+
|
98
|
+
# Access a construction variable or environment option.
|
99
|
+
# @see VarSet#[]
|
100
|
+
def [](*args)
|
101
|
+
@varset.send(:[], *args)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Set a construction variable or environment option.
|
105
|
+
# @see VarSet#[]=
|
106
|
+
def []=(*args)
|
107
|
+
@varset.send(:[]=, *args)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Add a set of construction variables or environment options.
|
111
|
+
# @see VarSet#append
|
112
|
+
def append(*args)
|
113
|
+
@varset.send(:append, *args)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Return a list of target file names
|
117
|
+
def targets
|
118
|
+
@targets.keys
|
119
|
+
end
|
120
|
+
|
121
|
+
# Return a list of sources needed to build target target.
|
122
|
+
def target_sources(target)
|
123
|
+
@targets[target][:source] rescue nil
|
124
|
+
end
|
125
|
+
|
126
|
+
# Build all target specified in the Environment.
|
127
|
+
# When a block is passed to Environment.new, this method is automatically
|
128
|
+
# called after the block returns.
|
129
|
+
def process
|
130
|
+
cache = Cache.new
|
131
|
+
targets_processed = Set.new
|
132
|
+
process_target = proc do |target|
|
133
|
+
if @targets[target][:source].map do |src|
|
134
|
+
targets_processed.include?(src) or not @targets.include?(src) or process_target.call(src)
|
135
|
+
end.all?
|
136
|
+
@targets[target][:builder].run(target,
|
137
|
+
@targets[target][:source],
|
138
|
+
cache,
|
139
|
+
self,
|
140
|
+
*@targets[target][:args])
|
141
|
+
else
|
142
|
+
false
|
143
|
+
end
|
144
|
+
end
|
145
|
+
@targets.each do |target, info|
|
146
|
+
next if targets_processed.include?(target)
|
147
|
+
unless process_target.call(target)
|
148
|
+
$stderr.puts "Error: failed to build #{target}"
|
149
|
+
break
|
150
|
+
end
|
151
|
+
end
|
152
|
+
cache.write
|
153
|
+
end
|
154
|
+
|
155
|
+
# Build a command line from the given template, resolving references to
|
156
|
+
# variables using the Environment's construction variables and any extra
|
157
|
+
# variables specified.
|
158
|
+
# @param command_template [Array] template for the command with variable
|
159
|
+
# references
|
160
|
+
# @param extra_vars [Hash, VarSet] extra variables to use in addition to
|
161
|
+
# (or replace) the Environment's construction variables when building
|
162
|
+
# the command
|
163
|
+
def build_command(command_template, extra_vars)
|
164
|
+
@varset.merge(extra_vars).expand_varref(command_template)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Execute a builder command
|
168
|
+
# @param short_desc [String] Message to print if the Environment's :echo
|
169
|
+
# mode is set to :short
|
170
|
+
# @param command [Array] The command to execute.
|
171
|
+
# @param options [Hash] Optional options to pass to {Kernel#system}.
|
172
|
+
def execute(short_desc, command, options = {})
|
173
|
+
print_command = proc do
|
174
|
+
puts command.map { |c| c =~ /\s/ ? "'#{c}'" : c }.join(' ')
|
175
|
+
end
|
176
|
+
if @varset[:echo] == :command
|
177
|
+
print_command.call
|
178
|
+
elsif @varset[:echo] == :short
|
179
|
+
puts short_desc
|
180
|
+
end
|
181
|
+
system(*command, options).tap do |result|
|
182
|
+
unless result or @varset[:echo] == :command
|
183
|
+
$stdout.write "Failed command was: "
|
184
|
+
print_command.call
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
alias_method :orig_method_missing, :method_missing
|
190
|
+
def method_missing(method, *args)
|
191
|
+
if @builders.has_key?(method.to_s)
|
192
|
+
target, source, *rest = args
|
193
|
+
source = [source] unless source.is_a?(Array)
|
194
|
+
@targets[target] = {
|
195
|
+
builder: @builders[method.to_s],
|
196
|
+
source: source,
|
197
|
+
args: rest,
|
198
|
+
}
|
199
|
+
else
|
200
|
+
orig_method_missing(method, *args)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Parse dependencies for a given target from a Makefile.
|
205
|
+
# This method is used internally by RScons builders.
|
206
|
+
# @param mf_fname [String] File name of the Makefile to read.
|
207
|
+
# @param target [String] Name of the target to gather dependencies for.
|
208
|
+
def parse_makefile_deps(mf_fname, target)
|
209
|
+
deps = []
|
210
|
+
buildup = ''
|
211
|
+
File.read(mf_fname).each_line do |line|
|
212
|
+
if line =~ /^(.*)\\\s*$/
|
213
|
+
buildup += ' ' + $1
|
214
|
+
else
|
215
|
+
if line =~ /^(.*): (.*)$/
|
216
|
+
target, tdeps = $1.strip, $2
|
217
|
+
if target == target
|
218
|
+
deps += tdeps.split(' ').map(&:strip)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
buildup = ''
|
222
|
+
end
|
223
|
+
end
|
224
|
+
deps
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Standard Ruby String class.
|
2
|
+
class String
|
3
|
+
# Check if the given string ends with any of the supplied suffixes
|
4
|
+
# @param suffix [String, Array] The suffix to look for.
|
5
|
+
# @return a true value if the string ends with one of the suffixes given.
|
6
|
+
def has_suffix?(suffix)
|
7
|
+
if suffix
|
8
|
+
suffix = [suffix] if suffix.is_a?(String)
|
9
|
+
suffix.find {|s| self.end_with?(s)}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Return a new string with the suffix (dot character and extension) changed
|
14
|
+
# to the given suffix.
|
15
|
+
# @param suffix [String] The new suffix.
|
16
|
+
def set_suffix(suffix = '')
|
17
|
+
sub(/\.[^.]*$/, suffix)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Rscons
|
2
|
+
# This class represents a collection of variables which can be accessed
|
3
|
+
# as certain types
|
4
|
+
class VarSet
|
5
|
+
# The underlying hash
|
6
|
+
attr_reader :vars
|
7
|
+
|
8
|
+
# Create a VarSet
|
9
|
+
# @param vars [Hash] Optional initial variables.
|
10
|
+
def initialize(vars = {})
|
11
|
+
@vars = vars
|
12
|
+
end
|
13
|
+
|
14
|
+
# Access the value of variable as a particular type
|
15
|
+
# @param key [String, Symbol] The variable name.
|
16
|
+
# @param type [Symbol, nil] Optional specification of the type desired.
|
17
|
+
# If the variable is a String and type is :array, a 1-element array with
|
18
|
+
# the variable value will be returned. If the variable is an Array and
|
19
|
+
# type is :string, the first element from the variable value will be
|
20
|
+
# returned.
|
21
|
+
def [](key, type = nil)
|
22
|
+
val = @vars[key]
|
23
|
+
if type == :array and val.is_a?(String)
|
24
|
+
[val]
|
25
|
+
elsif type == :string and val.is_a?(Array)
|
26
|
+
val.first
|
27
|
+
else
|
28
|
+
val
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Assign a value to a variable.
|
33
|
+
# @param key [String, Symbol] The variable name.
|
34
|
+
# @param val [Object] The value.
|
35
|
+
def []=(key, val)
|
36
|
+
@vars[key] = val
|
37
|
+
end
|
38
|
+
|
39
|
+
# Add or overwrite a set of variables
|
40
|
+
# @param values [VarSet, Hash] New set of variables.
|
41
|
+
def append(values)
|
42
|
+
values = values.vars if values.is_a?(VarSet)
|
43
|
+
@vars.merge!(values)
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
# Create a new VarSet object based on the first merged with other.
|
48
|
+
# @param other [VarSet, Hash] Other variables to add or overwrite.
|
49
|
+
def merge(other = {})
|
50
|
+
VarSet.new(Marshal.load(Marshal.dump(@vars))).append(other)
|
51
|
+
end
|
52
|
+
alias_method :clone, :merge
|
53
|
+
|
54
|
+
# Replace "$" variable references in varref with the variables values,
|
55
|
+
# recursively.
|
56
|
+
# @param varref [String, Array] Value containing references to variables.
|
57
|
+
def expand_varref(varref)
|
58
|
+
if varref.is_a?(Array)
|
59
|
+
varref.map do |ent|
|
60
|
+
expand_varref(ent)
|
61
|
+
end.flatten
|
62
|
+
else
|
63
|
+
if varref =~ /^(.*)\$\[(\w+)\](.*)$/
|
64
|
+
# expand array with given prefix, suffix
|
65
|
+
prefix, varname, suffix = $1, $2, $3
|
66
|
+
varval = expand_varref(@vars[varname])
|
67
|
+
unless varval.is_a?(Array)
|
68
|
+
raise "Array expected for $#{varname}"
|
69
|
+
end
|
70
|
+
varval.map {|e| "#{prefix}#{e}#{suffix}"}
|
71
|
+
elsif varref =~ /^\$(.*)$/
|
72
|
+
# expand a single variable reference
|
73
|
+
varname = $1
|
74
|
+
varval = expand_varref(@vars[varname])
|
75
|
+
varval or raise "Could not find variable #{varname.inspect}"
|
76
|
+
expand_varref(varval)
|
77
|
+
else
|
78
|
+
varref
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/rscons.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rscons/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "rscons"
|
8
|
+
gem.version = Rscons::VERSION
|
9
|
+
gem.authors = ["Josh Holtrop"]
|
10
|
+
gem.email = ["jholtrop@gmail.com"]
|
11
|
+
gem.description = %q{Software construction library inspired by SCons and implemented in Ruby}
|
12
|
+
gem.summary = %q{Software construction library inspired by SCons and implemented in Ruby}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_development_dependency "rspec-core"
|
21
|
+
gem.add_development_dependency "rspec-mocks"
|
22
|
+
gem.add_development_dependency "rspec-expectations"
|
23
|
+
gem.add_development_dependency "rspec"
|
24
|
+
gem.add_development_dependency "rake"
|
25
|
+
gem.add_development_dependency "simplecov"
|
26
|
+
gem.add_development_dependency "json"
|
27
|
+
gem.add_development_dependency 'rdoc'
|
28
|
+
gem.add_development_dependency "yard"
|
29
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
describe Rscons do
|
4
|
+
before(:all) do
|
5
|
+
FileUtils.rm_rf('build_tests_run')
|
6
|
+
@owd = Dir.pwd
|
7
|
+
end
|
8
|
+
|
9
|
+
after(:each) do
|
10
|
+
Dir.chdir(@owd)
|
11
|
+
FileUtils.rm_rf('build_tests_run')
|
12
|
+
end
|
13
|
+
|
14
|
+
def build_testdir
|
15
|
+
if File.exists?("build.rb")
|
16
|
+
system("ruby -I #{@owd}/lib -r rscons build.rb > build.out")
|
17
|
+
end
|
18
|
+
get_build_output
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_dir(build_test_directory)
|
22
|
+
FileUtils.cp_r("build_tests/#{build_test_directory}", 'build_tests_run')
|
23
|
+
Dir.chdir("build_tests_run")
|
24
|
+
build_testdir
|
25
|
+
end
|
26
|
+
|
27
|
+
def file_sub(fname)
|
28
|
+
contents = File.read(fname)
|
29
|
+
replaced = ''
|
30
|
+
contents.each_line do |line|
|
31
|
+
replaced += yield(line)
|
32
|
+
end
|
33
|
+
File.open(fname, 'w') do |fh|
|
34
|
+
fh.write(replaced)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_build_output
|
39
|
+
File.read('build.out').lines.map(&:strip)
|
40
|
+
end
|
41
|
+
|
42
|
+
###########################################################################
|
43
|
+
# Tests
|
44
|
+
###########################################################################
|
45
|
+
|
46
|
+
it 'builds a C program with one source file' do
|
47
|
+
test_dir('simple')
|
48
|
+
File.exists?('simple.o').should be_true
|
49
|
+
`./simple`.should == "This is a simple C program\n"
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'prints commands as they are executed' do
|
53
|
+
lines = test_dir('simple')
|
54
|
+
lines.should == [
|
55
|
+
'gcc -c -o simple.o -MMD -MF simple.mf simple.c',
|
56
|
+
'gcc -o simple simple.o',
|
57
|
+
]
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'prints short representations of the commands being executed' do
|
61
|
+
lines = test_dir('header')
|
62
|
+
lines.should == [
|
63
|
+
'CC header.o',
|
64
|
+
'LD header',
|
65
|
+
]
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'builds a C program with one source file and one header file' do
|
69
|
+
test_dir('header')
|
70
|
+
File.exists?('header.o').should be_true
|
71
|
+
`./header`.should == "The value is 2\n"
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'rebuilds a C module when a header it depends on changes' do
|
75
|
+
test_dir('header')
|
76
|
+
`./header`.should == "The value is 2\n"
|
77
|
+
file_sub('header.h') {|line| line.sub(/2/, '5')}
|
78
|
+
build_testdir
|
79
|
+
`./header`.should == "The value is 5\n"
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'does not rebuild a C module when its dependencies have not changed' do
|
83
|
+
lines = test_dir('header')
|
84
|
+
`./header`.should == "The value is 2\n"
|
85
|
+
lines.should == [
|
86
|
+
'CC header.o',
|
87
|
+
'LD header',
|
88
|
+
]
|
89
|
+
lines = build_testdir
|
90
|
+
lines.should == []
|
91
|
+
end
|
92
|
+
|
93
|
+
it "does not rebuild a C module when only the file's timestampe has changed" do
|
94
|
+
lines = test_dir('header')
|
95
|
+
`./header`.should == "The value is 2\n"
|
96
|
+
lines.should == [
|
97
|
+
'CC header.o',
|
98
|
+
'LD header',
|
99
|
+
]
|
100
|
+
file_sub('header.c') {|line| line}
|
101
|
+
lines = build_testdir
|
102
|
+
lines.should == []
|
103
|
+
end
|
104
|
+
|
105
|
+
it 're-links a program when the link flags have changed' do
|
106
|
+
lines = test_dir('simple')
|
107
|
+
lines.should == [
|
108
|
+
'gcc -c -o simple.o -MMD -MF simple.mf simple.c',
|
109
|
+
'gcc -o simple simple.o',
|
110
|
+
]
|
111
|
+
file_sub('build.rb') {|line| line.sub(/.*CHANGE.FLAGS.*/, ' env["LIBS"] += ["c"]')}
|
112
|
+
lines = build_testdir
|
113
|
+
lines.should == ['gcc -o simple simple.o -lc']
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'builds object files in a different build directory' do
|
117
|
+
lines = test_dir('build_dir')
|
118
|
+
`./build_dir`.should == "Hello from two()\n"
|
119
|
+
File.exists?('build/one/one.o').should be_true
|
120
|
+
File.exists?('build/two/two.o').should be_true
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'allows Ruby classes as custom builders to be used to construct files' do
|
124
|
+
lines = test_dir('custom_builder')
|
125
|
+
lines.should == ['CC program.o', 'LD program']
|
126
|
+
File.exists?('inc.h').should be_true
|
127
|
+
`./program`.should == "The value is 5678\n"
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'allows cloning Environment objects' do
|
131
|
+
lines = test_dir('clone_env')
|
132
|
+
lines.should == [
|
133
|
+
%q{gcc -c -o debug/program.o -MMD -MF debug/program.mf '-DSTRING="Debug Version"' -O2 src/program.c},
|
134
|
+
%q{gcc -o program-debug debug/program.o},
|
135
|
+
%q{gcc -c -o release/program.o -MMD -MF release/program.mf '-DSTRING="Release Version"' -O2 src/program.c},
|
136
|
+
%q{gcc -o program-release release/program.o},
|
137
|
+
]
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'builds a C++ program with one source file' do
|
141
|
+
test_dir('simple_cc')
|
142
|
+
File.exists?('simple.o').should be_true
|
143
|
+
`./simple`.should == "This is a simple C++ program\n"
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|