Compare commits

..
19 Commits
Author SHA1 Message Date
josh e041ea6612 v0.0.11 2013-09-19 17:06:37 -04:00
josh ab51ddcc1b do not try to clean directories that have already been removed 2013-09-19 17:06:20 -04:00
josh 3e4e9cce1c v0.0.10 2013-09-19 16:48:15 -04:00
josh 32d19489cb Cache: encode directory names in __ENCODING__ - close #3 2013-09-19 16:45:35 -04:00
josh 45a0d56c54 recover from, and warn when, .rsconscache loads something other than a Hash 2013-09-19 16:18:07 -04:00
josh 9b954d9d98 v0.0.9 2013-09-19 09:43:17 -04:00
josh e63b4c932f Cache: create missing hash entries from previous versions of the cache file 2013-09-19 09:43:00 -04:00
josh 97e11f45e5 v0.0.8 2013-09-18 21:18:00 -04:00
josh 5c6239ea6d Rscons.clean() removes any directories it created if they are empty 2013-09-18 21:17:42 -04:00
josh 770a22250a add Rscons.clear() class method 2013-09-17 21:48:50 -04:00
josh 983cb7cf21 v0.0.7 2013-09-16 23:03:14 -04:00
josh 986b8f08cc prepend builder-temporary construction variables with "_"; reserve lowercase string variables for user 2013-09-16 22:30:16 -04:00
josh f13664331f create requisite output directories for build targets; refactor common buile functionality into Builder#standard_build() 2013-09-15 15:27:16 -04:00
josh 17c7bf1d31 v0.0.6 2013-09-12 23:37:37 -04:00
josh 64d15602cc add Library default builder 2013-09-12 23:35:34 -04:00
josh e04e67698a add rspec test for overridding construction variables for an individual builer 2013-09-12 23:05:04 -04:00
josh 837dff9374 pass extra construction variables to Builder#run() 2013-09-12 22:57:22 -04:00
josh 75f2418570 refactor some Program functionality into Environment#build_sources() 2013-09-12 22:38:52 -04:00
josh 711e96cec8 write out cache file when raising a build error exception 2013-08-12 14:40:46 -04:00
18 changed files with 249 additions and 49 deletions

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
class MySource < Rscons::Builder
def run(target, sources, cache, env)
def run(target, sources, cache, env, vars = {})
File.open(target, 'w') do |fh|
fh.puts <<EOF
#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
+19
View File
@@ -8,16 +8,35 @@ require "rscons/monkey/module"
require "rscons/monkey/string"
# default builders
require "rscons/builders/library"
require "rscons/builders/object"
require "rscons/builders/program"
# Namespace module for rscons classes
module Rscons
DEFAULT_BUILDERS = [
Library,
Object,
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|
next unless File.directory?(directory)
if (Dir.entries(directory) - ['.', '..']).empty?
Dir.rmdir(directory) rescue nil
end
end
Cache.clear
end
end
+15
View File
@@ -1,3 +1,5 @@
require "fileutils"
module Rscons
# Class to hold an object that knows how to build a certain type of file.
class Builder
@@ -16,5 +18,18 @@ module Rscons
def produces?(target, source, env)
false
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
+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',
'ASPPPATH' => '$CPPPATH',
'ASPPFLAGS' => '$CPPFLAGS',
'ASDEPGEN' => ['-MMD', '-MF', '$DEPFILE'],
'ASCOM' => ['$AS', '-c', '-o', '$TARGET', '$ASDEPGEN', '-I$[ASPPPATH]', '$ASPPFLAGS', '$ASFLAGS', '$SOURCES'],
'ASDEPGEN' => ['-MMD', '-MF', '$_DEPFILE'],
'ASCOM' => ['$AS', '-c', '-o', '$_TARGET', '$ASDEPGEN', '-I$[ASPPPATH]', '$ASPPFLAGS', '$ASFLAGS', '$_SOURCES'],
'CPPFLAGS' => [],
'CPPPATH' => [],
@@ -20,14 +20,14 @@ module Rscons
'CC' => 'gcc',
'CFLAGS' => [],
'CSUFFIX' => '.c',
'CCDEPGEN' => ['-MMD', '-MF', '$DEPFILE'],
'CCCOM' => ['$CC', '-c', '-o', '$TARGET', '$CCDEPGEN', '-I$[CPPPATH]', '$CPPFLAGS', '$CFLAGS', '$SOURCES'],
'CCDEPGEN' => ['-MMD', '-MF', '$_DEPFILE'],
'CCCOM' => ['$CC', '-c', '-o', '$_TARGET', '$CCDEPGEN', '-I$[CPPPATH]', '$CPPFLAGS', '$CFLAGS', '$_SOURCES'],
'CXX' => 'g++',
'CXXFLAGS' => [],
'CXXSUFFIX' => '.cc',
'CXXDEPGEN' => ['-MMD', '-MF', '$DEPFILE'],
'CXXCOM' =>['$CXX', '-c', '-o', '$TARGET', '$CXXDEPGEN', '-I$[CPPPATH]', '$CPPFLAGS', '$CXXFLAGS', '$SOURCES'],
'CXXDEPGEN' => ['-MMD', '-MF', '$_DEPFILE'],
'CXXCOM' =>['$CXX', '-c', '-o', '$_TARGET', '$CXXDEPGEN', '-I$[CPPPATH]', '$CPPFLAGS', '$CXXFLAGS', '$_SOURCES'],
}
end
@@ -38,12 +38,12 @@ module Rscons
source.has_suffix?(env['CXXSUFFIX']))
end
def run(target, sources, cache, env)
vars = {
'TARGET' => target,
'SOURCES' => sources,
'DEPFILE' => target.set_suffix('.mf'),
}
def run(target, sources, cache, env, vars = {})
vars = vars.merge({
'_TARGET' => target,
'_SOURCES' => sources,
'_DEPFILE' => target.set_suffix('.mf'),
})
com_prefix = if sources.first.has_suffix?(env['ASSUFFIX'])
'AS'
elsif sources.first.has_suffix?(env['CSUFFIX'])
@@ -55,11 +55,13 @@ module Rscons
end
command = env.build_command(env["#{com_prefix}COM"], vars)
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)
deps = sources
if File.exists?(vars['DEPFILE'])
deps += env.parse_makefile_deps(vars['DEPFILE'], target)
FileUtils.rm_f(vars['DEPFILE'])
if File.exists?(vars['_DEPFILE'])
deps += env.parse_makefile_deps(vars['_DEPFILE'], target)
FileUtils.rm_f(vars['_DEPFILE'])
end
cache.register_build(target, command, deps.uniq)
end
+9 -22
View File
@@ -10,38 +10,25 @@ module Rscons
'LDFLAGS' => [],
'LIBPATH' => [],
'LIBS' => [],
'LDCOM' => ['$LD', '-o', '$TARGET', '$LDFLAGS', '$SOURCES', '-L$[LIBPATH]', '-l$[LIBS]']
'LDCOM' => ['$LD', '-o', '$_TARGET', '$LDFLAGS', '$_SOURCES', '-L$[LIBPATH]', '-l$[LIBS]']
}
end
def run(target, sources, cache, env)
# convert sources to object file names
objects = sources.map do |source|
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
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
use_cxx = sources.map do |s|
s.has_suffix?(env['CXXSUFFIX'])
end.any?
ld_alt = use_cxx ? env['CXX'] : env['CC']
vars = {
'TARGET' => target,
'SOURCES' => objects,
vars = vars.merge({
'_TARGET' => target,
'_SOURCES' => objects,
'LD' => env['LD'] || ld_alt,
}
})
command = env.build_command(env['LDCOM'], vars)
unless cache.up_to_date?(target, command, objects)
return false unless env.execute("LD #{target}", command)
cache.register_build(target, command, objects)
end
target
standard_build("LD #{target}", target, command, objects, env, cache)
end
end
end
+38 -4
View File
@@ -36,6 +36,11 @@ module Rscons
# ]
# }
# }
# directories: {
# 'build' => true,
# 'build/one' => true,
# 'build/two' => true,
# }
# }
class Cache
#### Constants
@@ -55,10 +60,14 @@ module Rscons
# Create a Cache object and load in the previous contents from the cache
# file.
def initialize
@cache = YAML.load(File.read(CACHE_FILE)) rescue {
targets: {},
version: VERSION,
}
@cache = YAML.load(File.read(CACHE_FILE)) rescue {}
unless @cache.is_a?(Hash)
$stderr.puts "Warning: #{CACHE_FILE} was corrupt. Contents:\n#{@cache.inspect}"
@cache = {}
end
@cache[:targets] ||= {}
@cache[:directories] ||= {}
@cache[:version] ||= VERSION
@lookup_checksums = {}
end
@@ -132,6 +141,31 @@ module Rscons
}
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]).encode(__ENCODING__)
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
+38 -6
View File
@@ -11,9 +11,10 @@ module Rscons
# Create an Environment object.
# @param variables [Hash]
# The variables hash can contain both construction variables, which are
# uppercase strings (such as "CC" or "LDFLAGS"), and RScons options,
# which are lowercase symbols (such as :echo).
# The variables hash can contain construction variables, which are
# uppercase strings (such as "CC" or "LDFLAGS"), user variables, which
# 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
# when the block returns, the {#process} method is automatically called.
def initialize(variables = {})
@@ -95,7 +96,6 @@ module Rscons
end
build_fname.gsub!('\\', '/')
end
FileUtils.mkdir_p(File.dirname(build_fname))
build_fname
end
@@ -141,7 +141,7 @@ module Rscons
@targets[target][:source],
cache,
self,
*@targets[target][:args])
@targets[target][:vars] || {})
else
false
end
@@ -149,6 +149,7 @@ module Rscons
@targets.each do |target, info|
next if targets_processed.include?(target)
unless process_target.call(target)
cache.write
raise BuildError.new("Failed to build #{target}")
end
end
@@ -192,11 +193,15 @@ module Rscons
alias_method :orig_method_missing, :method_missing
def method_missing(method, *args)
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)
@targets[target] = {
builder: @builders[method.to_s],
source: source,
vars: vars,
args: rest,
}
else
@@ -227,5 +232,32 @@ module Rscons
end
deps
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
+4
View File
@@ -8,8 +8,12 @@ module Rscons
# Create a VarSet
# @param vars [Hash] Optional initial variables.
def initialize(vars = {})
if vars.is_a?(VarSet)
@vars = vars.clone.vars
else
@vars = vars
end
end
# Access the value of variable as a particular type
# @param key [String, Symbol] The variable name.
+1 -1
View File
@@ -1,4 +1,4 @@
module Rscons
# gem version
VERSION = "0.0.4"
VERSION = "0.0.11"
end
+52
View File
@@ -120,6 +120,35 @@ describe Rscons do
File.exists?('build/two/two.o').should be_true
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
lines = test_dir('custom_builder')
lines.should == ['CC program.o', 'LD program']
@@ -143,4 +172,27 @@ describe Rscons do
`./simple`.should == "This is a simple C++ program\n"
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