Compare commits

...
15 Commits
18 changed files with 251 additions and 49 deletions

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
class MySource < Rscons::Builder class MySource < Rscons::Builder
def run(target, sources, cache, env) def run(target, sources, cache, env, vars = {})
File.open(target, 'w') do |fh| File.open(target, 'w') do |fh|
fh.puts <<EOF fh.puts <<EOF
#define THE_VALUE 5678 #define THE_VALUE 5678
+4
View File
@@ -0,0 +1,4 @@
Rscons::Environment.new(echo: :command) do |env|
env.Program('library', ['lib.a', 'three.c'])
env.Library("lib.a", ['one.c', 'two.c'], 'CPPFLAGS' => ['-Dmake_lib'])
end
+8
View File
@@ -0,0 +1,8 @@
#include <stdio.h>
#ifdef make_lib
int main(int argc, char *argv[])
{
printf("Library\n");
}
#endif
View File
Whitespace-only changes.
View File
Whitespace-only changes.
+4
View File
@@ -0,0 +1,4 @@
Rscons::Environment.new(echo: :command) do |env|
env.Object("one.o", "one.c", 'CPPFLAGS' => ['-DONE'])
env.Program('two_sources', ['one.o', 'two.c'])
end
+8
View File
@@ -0,0 +1,8 @@
#include <stdio.h>
#ifdef ONE
int main(int argc, char *argv[])
{
printf("This is a C program with two sources.\n");
}
#endif
+3
View File
@@ -0,0 +1,3 @@
#ifdef ONE
#error ONE should not be defined
#endif
+21
View File
@@ -8,13 +8,34 @@ require "rscons/monkey/module"
require "rscons/monkey/string" require "rscons/monkey/string"
# default builders # default builders
require "rscons/builders/library"
require "rscons/builders/object" require "rscons/builders/object"
require "rscons/builders/program" require "rscons/builders/program"
# Namespace module for rscons classes # Namespace module for rscons classes
module Rscons module Rscons
DEFAULT_BUILDERS = [ DEFAULT_BUILDERS = [
Library,
Object, Object,
Program, Program,
] ]
class BuildError < Exception
end
# Remove all generated files
def self.clean
cache = Cache.new
# remove all built files
cache.targets.each do |target|
FileUtils.rm_f(target)
end
# remove all created directories if they are empty
cache.directories.sort {|a, b| b.size <=> a.size}.each do |directory|
if (Dir.entries(directory) - ['.', '..']).empty?
Dir.rmdir(directory) rescue nil
end
end
Cache.clear
end
end end
+15
View File
@@ -1,3 +1,5 @@
require "fileutils"
module Rscons module Rscons
# Class to hold an object that knows how to build a certain type of file. # Class to hold an object that knows how to build a certain type of file.
class Builder class Builder
@@ -16,5 +18,18 @@ module Rscons
def produces?(target, source, env) def produces?(target, source, env)
false false
end end
# Check if the cache is up to date for the target and if not execute the
# build command.
# Return the name of the target or false on failure.
def standard_build(short_cmd_string, target, command, sources, env, cache)
unless cache.up_to_date?(target, command, sources)
cache.mkdir_p(File.dirname(target))
FileUtils.rm_f(target)
return false unless env.execute(short_cmd_string, command)
cache.register_build(target, command, sources)
end
target
end
end end
end end
+28
View File
@@ -0,0 +1,28 @@
require 'fileutils'
module Rscons
# A default RScons builder that produces a static library archive.
class Library < Builder
def default_variables(env)
{
'AR' => 'ar',
'LIBSUFFIX' => '.a',
'ARFLAGS' => [],
'ARCOM' => ['$AR', 'rcs', '$ARFLAGS', '$_TARGET', '$_SOURCES']
}
end
def run(target, sources, cache, env, vars = {})
# build sources to linkable objects
objects = env.build_sources(sources, [env['OBJSUFFIX'], env['LIBSUFFIX']].flatten, cache, vars)
if objects
vars = vars.merge({
'_TARGET' => target,
'_SOURCES' => objects,
})
command = env.build_command(env['ARCOM'], vars)
standard_build("AR #{target}", target, command, objects, env, cache)
end
end
end
end
+17 -15
View File
@@ -11,8 +11,8 @@ module Rscons
'ASSUFFIX' => '.S', 'ASSUFFIX' => '.S',
'ASPPPATH' => '$CPPPATH', 'ASPPPATH' => '$CPPPATH',
'ASPPFLAGS' => '$CPPFLAGS', 'ASPPFLAGS' => '$CPPFLAGS',
'ASDEPGEN' => ['-MMD', '-MF', '$DEPFILE'], 'ASDEPGEN' => ['-MMD', '-MF', '$_DEPFILE'],
'ASCOM' => ['$AS', '-c', '-o', '$TARGET', '$ASDEPGEN', '-I$[ASPPPATH]', '$ASPPFLAGS', '$ASFLAGS', '$SOURCES'], 'ASCOM' => ['$AS', '-c', '-o', '$_TARGET', '$ASDEPGEN', '-I$[ASPPPATH]', '$ASPPFLAGS', '$ASFLAGS', '$_SOURCES'],
'CPPFLAGS' => [], 'CPPFLAGS' => [],
'CPPPATH' => [], 'CPPPATH' => [],
@@ -20,14 +20,14 @@ module Rscons
'CC' => 'gcc', 'CC' => 'gcc',
'CFLAGS' => [], 'CFLAGS' => [],
'CSUFFIX' => '.c', 'CSUFFIX' => '.c',
'CCDEPGEN' => ['-MMD', '-MF', '$DEPFILE'], 'CCDEPGEN' => ['-MMD', '-MF', '$_DEPFILE'],
'CCCOM' => ['$CC', '-c', '-o', '$TARGET', '$CCDEPGEN', '-I$[CPPPATH]', '$CPPFLAGS', '$CFLAGS', '$SOURCES'], 'CCCOM' => ['$CC', '-c', '-o', '$_TARGET', '$CCDEPGEN', '-I$[CPPPATH]', '$CPPFLAGS', '$CFLAGS', '$_SOURCES'],
'CXX' => 'g++', 'CXX' => 'g++',
'CXXFLAGS' => [], 'CXXFLAGS' => [],
'CXXSUFFIX' => '.cc', 'CXXSUFFIX' => '.cc',
'CXXDEPGEN' => ['-MMD', '-MF', '$DEPFILE'], 'CXXDEPGEN' => ['-MMD', '-MF', '$_DEPFILE'],
'CXXCOM' =>['$CXX', '-c', '-o', '$TARGET', '$CXXDEPGEN', '-I$[CPPPATH]', '$CPPFLAGS', '$CXXFLAGS', '$SOURCES'], 'CXXCOM' =>['$CXX', '-c', '-o', '$_TARGET', '$CXXDEPGEN', '-I$[CPPPATH]', '$CPPFLAGS', '$CXXFLAGS', '$_SOURCES'],
} }
end end
@@ -38,12 +38,12 @@ module Rscons
source.has_suffix?(env['CXXSUFFIX'])) source.has_suffix?(env['CXXSUFFIX']))
end end
def run(target, sources, cache, env) def run(target, sources, cache, env, vars = {})
vars = { vars = vars.merge({
'TARGET' => target, '_TARGET' => target,
'SOURCES' => sources, '_SOURCES' => sources,
'DEPFILE' => target.set_suffix('.mf'), '_DEPFILE' => target.set_suffix('.mf'),
} })
com_prefix = if sources.first.has_suffix?(env['ASSUFFIX']) com_prefix = if sources.first.has_suffix?(env['ASSUFFIX'])
'AS' 'AS'
elsif sources.first.has_suffix?(env['CSUFFIX']) elsif sources.first.has_suffix?(env['CSUFFIX'])
@@ -55,11 +55,13 @@ module Rscons
end end
command = env.build_command(env["#{com_prefix}COM"], vars) command = env.build_command(env["#{com_prefix}COM"], vars)
unless cache.up_to_date?(target, command, sources) unless cache.up_to_date?(target, command, sources)
cache.mkdir_p(File.dirname(target))
FileUtils.rm_f(target)
return false unless env.execute("#{com_prefix} #{target}", command) return false unless env.execute("#{com_prefix} #{target}", command)
deps = sources deps = sources
if File.exists?(vars['DEPFILE']) if File.exists?(vars['_DEPFILE'])
deps += env.parse_makefile_deps(vars['DEPFILE'], target) deps += env.parse_makefile_deps(vars['_DEPFILE'], target)
FileUtils.rm_f(vars['DEPFILE']) FileUtils.rm_f(vars['_DEPFILE'])
end end
cache.register_build(target, command, deps.uniq) cache.register_build(target, command, deps.uniq)
end end
+9 -22
View File
@@ -10,38 +10,25 @@ module Rscons
'LDFLAGS' => [], 'LDFLAGS' => [],
'LIBPATH' => [], 'LIBPATH' => [],
'LIBS' => [], 'LIBS' => [],
'LDCOM' => ['$LD', '-o', '$TARGET', '$LDFLAGS', '$SOURCES', '-L$[LIBPATH]', '-l$[LIBS]'] 'LDCOM' => ['$LD', '-o', '$_TARGET', '$LDFLAGS', '$_SOURCES', '-L$[LIBPATH]', '-l$[LIBS]']
} }
end end
def run(target, sources, cache, env) def run(target, sources, cache, env, vars = {})
# convert sources to object file names # build sources to linkable objects
objects = sources.map do |source| objects = env.build_sources(sources, [env['OBJSUFFIX'], env['LIBSUFFIX']].flatten, cache, vars)
if source.has_suffix?([env['OBJSUFFIX'], env['LIBSUFFIX']])
source
else
o_file = env.get_build_fname(source, env['OBJSUFFIX', :string])
builder = env.builders.values.find { |b| b.produces?(o_file, source, env) }
builder or raise "No builder found to convert input source #{source.inspect} to an object file."
builder.run(o_file, [source], cache, env) or break
end
end
if objects if objects
use_cxx = sources.map do |s| use_cxx = sources.map do |s|
s.has_suffix?(env['CXXSUFFIX']) s.has_suffix?(env['CXXSUFFIX'])
end.any? end.any?
ld_alt = use_cxx ? env['CXX'] : env['CC'] ld_alt = use_cxx ? env['CXX'] : env['CC']
vars = { vars = vars.merge({
'TARGET' => target, '_TARGET' => target,
'SOURCES' => objects, '_SOURCES' => objects,
'LD' => env['LD'] || ld_alt, 'LD' => env['LD'] || ld_alt,
} })
command = env.build_command(env['LDCOM'], vars) command = env.build_command(env['LDCOM'], vars)
unless cache.up_to_date?(target, command, objects) standard_build("LD #{target}", target, command, objects, env, cache)
return false unless env.execute("LD #{target}", command)
cache.register_build(target, command, objects)
end
target
end end
end end
end end
+36 -1
View File
@@ -36,6 +36,11 @@ module Rscons
# ] # ]
# } # }
# } # }
# directories: {
# 'build' => true,
# 'build/one' => true,
# 'build/two' => true,
# }
# } # }
class Cache class Cache
#### Constants #### Constants
@@ -57,6 +62,7 @@ module Rscons
def initialize def initialize
@cache = YAML.load(File.read(CACHE_FILE)) rescue { @cache = YAML.load(File.read(CACHE_FILE)) rescue {
targets: {}, targets: {},
directories: {},
version: VERSION, version: VERSION,
} }
@lookup_checksums = {} @lookup_checksums = {}
@@ -80,6 +86,7 @@ module Rscons
# @return true value if the target is up to date, meaning that: # @return true value if the target is up to date, meaning that:
# - the target exists on disk # - the target exists on disk
# - the cache has information for the target # - the cache has information for the target
# - the target's checksum matches its checksum when it was last built
# - the command used to build the target is the same as last time # - the command used to build the target is the same as last time
# - all dependencies listed are also listed in the cache, or, if # - all dependencies listed are also listed in the cache, or, if
# :strict_deps was given in options, the list of dependencies is # :strict_deps was given in options, the list of dependencies is
@@ -93,6 +100,9 @@ module Rscons
# target must be registered in the cache # target must be registered in the cache
return false unless @cache[:targets].has_key?(target) return false unless @cache[:targets].has_key?(target)
# target must have the same checksum as when it was built last
return false unless @cache[:targets][target][:checksum] == lookup_checksum(target)
# command used to build target must be identical # command used to build target must be identical
return false unless @cache[:targets][target][:command] == command return false unless @cache[:targets][target][:command] == command
@@ -128,6 +138,31 @@ module Rscons
} }
end end
# Return a list of targets that have been built
def targets
@cache[:targets].keys
end
# Make any needed directories and record the ones that are created for
# removal upon a "clean" operation.
def mkdir_p(path)
parts = path.split(/[\\\/]/)
(0..parts.size).each do |i|
subpath = File.join(*parts[0, i + 1])
unless File.exists?(subpath)
FileUtils.mkdir(subpath)
unless @cache[:directories].include?(subpath)
@cache[:directories][subpath] = true
end
end
end
end
# Return a list of directories which were created as a part of the build
def directories
@cache[:directories].keys
end
# Private Instance Methods # Private Instance Methods
private private
@@ -141,7 +176,7 @@ module Rscons
# Calculate and return a file's checksum # Calculate and return a file's checksum
# @param file [String] The file name. # @param file [String] The file name.
def calculate_checksum(file) def calculate_checksum(file)
@lookup_checksums[file] = Digest::MD5.hexdigest(File.read(file)).encode(__ENCODING__) rescue '' @lookup_checksums[file] = Digest::MD5.hexdigest(File.read(file, {mode: 'rb'})).encode(__ENCODING__) rescue ''
end end
end end
end end
+39 -8
View File
@@ -11,9 +11,10 @@ module Rscons
# Create an Environment object. # Create an Environment object.
# @param variables [Hash] # @param variables [Hash]
# The variables hash can contain both construction variables, which are # The variables hash can contain construction variables, which are
# uppercase strings (such as "CC" or "LDFLAGS"), and RScons options, # uppercase strings (such as "CC" or "LDFLAGS"), user variables, which
# which are lowercase symbols (such as :echo). # are lowercase strings (such as "sources"), and RScons options, which
# are lowercase symbols (such as :echo).
# If a block is given, the Environment object is yielded to the block and # If a block is given, the Environment object is yielded to the block and
# when the block returns, the {#process} method is automatically called. # when the block returns, the {#process} method is automatically called.
def initialize(variables = {}) def initialize(variables = {})
@@ -95,7 +96,6 @@ module Rscons
end end
build_fname.gsub!('\\', '/') build_fname.gsub!('\\', '/')
end end
FileUtils.mkdir_p(File.dirname(build_fname))
build_fname build_fname
end end
@@ -141,7 +141,7 @@ module Rscons
@targets[target][:source], @targets[target][:source],
cache, cache,
self, self,
*@targets[target][:args]) @targets[target][:vars] || {})
else else
false false
end end
@@ -149,8 +149,8 @@ module Rscons
@targets.each do |target, info| @targets.each do |target, info|
next if targets_processed.include?(target) next if targets_processed.include?(target)
unless process_target.call(target) unless process_target.call(target)
$stderr.puts "Error: failed to build #{target}" cache.write
break raise BuildError.new("Failed to build #{target}")
end end
end end
cache.write cache.write
@@ -193,11 +193,15 @@ module Rscons
alias_method :orig_method_missing, :method_missing alias_method :orig_method_missing, :method_missing
def method_missing(method, *args) def method_missing(method, *args)
if @builders.has_key?(method.to_s) if @builders.has_key?(method.to_s)
target, source, *rest = args target, source, vars, *rest = args
unless vars.nil? or vars.is_a?(Hash) or vars.is_a?(VarSet)
raise "Unexpected construction variable set: #{vars.inspect}"
end
source = [source] unless source.is_a?(Array) source = [source] unless source.is_a?(Array)
@targets[target] = { @targets[target] = {
builder: @builders[method.to_s], builder: @builders[method.to_s],
source: source, source: source,
vars: vars,
args: rest, args: rest,
} }
else else
@@ -228,5 +232,32 @@ module Rscons
end end
deps deps
end end
# Build a list of source files into files containing one of the suffixes
# given by suffixes.
# This method is used internally by RScons builders.
# @param sources [Array] List of source files to build.
# @param suffixes [Array] List of suffixes to try to convert source files into.
# @param cache [Cache] The Cache.
# Return a list of the converted file names.
def build_sources(sources, suffixes, cache, vars = {})
sources.map do |source|
if source.has_suffix?(suffixes)
source
else
converted = nil
suffixes.each do |suffix|
converted_fname = get_build_fname(source, suffix)
builder = @builders.values.find { |b| b.produces?(converted_fname, source, self) }
if builder
converted = builder.run(converted_fname, [source], cache, self, vars)
return nil unless converted
break
end
end
converted or raise "Could not find a builder to handle #{source.inspect}."
end
end
end
end end
end end
+5 -1
View File
@@ -8,7 +8,11 @@ module Rscons
# Create a VarSet # Create a VarSet
# @param vars [Hash] Optional initial variables. # @param vars [Hash] Optional initial variables.
def initialize(vars = {}) def initialize(vars = {})
@vars = vars if vars.is_a?(VarSet)
@vars = vars.clone.vars
else
@vars = vars
end
end end
# Access the value of variable as a particular type # Access the value of variable as a particular type
+1 -1
View File
@@ -1,4 +1,4 @@
module Rscons module Rscons
# gem version # gem version
VERSION = "0.0.2" VERSION = "0.0.8"
end end
+52
View File
@@ -120,6 +120,35 @@ describe Rscons do
File.exists?('build/two/two.o').should be_true File.exists?('build/two/two.o').should be_true
end end
it 'cleans built files' do
lines = test_dir('build_dir')
`./build_dir`.should == "Hello from two()\n"
File.exists?('build/one/one.o').should be_true
File.exists?('build/two/two.o').should be_true
Rscons.clean
File.exists?('build/one/one.o').should be_false
File.exists?('build/two/two.o').should be_false
File.exists?('build/one').should be_false
File.exists?('build/two').should be_false
File.exists?('build').should be_false
File.exists?('src/one/one.c').should be_true
end
it 'does not clean created directories if other non-rscons-generated files reside there' do
lines = test_dir('build_dir')
`./build_dir`.should == "Hello from two()\n"
File.exists?('build/one/one.o').should be_true
File.exists?('build/two/two.o').should be_true
File.open('build/two/tmp', 'w') { |fh| fh.puts "dum" }
Rscons.clean
File.exists?('build/one/one.o').should be_false
File.exists?('build/two/two.o').should be_false
File.exists?('build/one').should be_false
File.exists?('build/two').should be_true
File.exists?('build').should be_true
File.exists?('src/one/one.c').should be_true
end
it 'allows Ruby classes as custom builders to be used to construct files' do it 'allows Ruby classes as custom builders to be used to construct files' do
lines = test_dir('custom_builder') lines = test_dir('custom_builder')
lines.should == ['CC program.o', 'LD program'] lines.should == ['CC program.o', 'LD program']
@@ -143,4 +172,27 @@ describe Rscons do
`./simple`.should == "This is a simple C++ program\n" `./simple`.should == "This is a simple C++ program\n"
end end
it 'allows overriding construction variables for individual builder calls' do
lines = test_dir('two_sources')
lines.should == [
'gcc -c -o one.o -MMD -MF one.mf -DONE one.c',
'gcc -c -o two.o -MMD -MF two.mf two.c',
'gcc -o two_sources one.o two.o',
]
File.exists?('two_sources').should be_true
`./two_sources`.should == "This is a C program with two sources.\n"
end
it 'builds a static library archive' do
lines = test_dir('library')
lines.should == [
'gcc -c -o one.o -MMD -MF one.mf -Dmake_lib one.c',
'gcc -c -o two.o -MMD -MF two.mf -Dmake_lib two.c',
'ar rcs lib.a one.o two.o',
'gcc -c -o three.o -MMD -MF three.mf three.c',
'gcc -o library lib.a three.o',
]
File.exists?('library').should be_true
`ar t lib.a`.should == "one.o\ntwo.o\n"
end
end end