rscons 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ build_tests_run
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rscons.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Josh Holtrop
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # Rscons
2
+
3
+ Software construction library inspired by SCons and implemented in Ruby
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'rscons'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install rscons
18
+
19
+ ## Usage
20
+
21
+ RScons::Environment.new do |env|
22
+ env.Program('program', Dir['*.c'])
23
+ end
24
+
25
+ main_env = RScons::Environment.new do |env|
26
+ env.build_dir('src', 'build/main')
27
+ env.Program('program', Dir['src/**/*.cc'])
28
+ end
29
+
30
+ debug_env = main_env.clone do |env|
31
+ env.build_dir('src', 'build/debug')
32
+ env['CFLAGS'] = ['-g', '-O0']
33
+ env.Program('program-debug', Dir['src/**/*.cc'])
34
+ end
35
+
36
+ ## Contributing
37
+
38
+ 1. Fork it
39
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
40
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
41
+ 4. Push to the branch (`git push origin my-new-feature`)
42
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require "bundler"
2
+ begin
3
+ Bundler.setup(:default, :development)
4
+ rescue Bundler::BundlerError => e
5
+ raise LoadError.new("Unable to setup Bundler; you might need to `bundle install`: #{e.message}")
6
+ end
7
+
8
+ require "bundler/gem_tasks"
9
+ require "rspec/core/rake_task"
10
+ require "rake/clean"
11
+ require "yard"
12
+
13
+ CLEAN.include 'build_tests_run'
14
+
15
+ RSpec::Core::RakeTask.new(:spec)
16
+
17
+ YARD::Rake::YardocTask.new do |yard|
18
+ yard.files = ['lib/**/*.rb']
19
+ end
20
+
21
+ task :default => :spec
@@ -0,0 +1,5 @@
1
+ Rscons::Environment.new do |env|
2
+ env.append('CPPPATH' => Dir['src/**/*/'])
3
+ env.build_dir('src', 'build')
4
+ env.Program('build_dir', Dir['src/**/*.c'])
5
+ end
@@ -0,0 +1,6 @@
1
+ #include "two.h"
2
+
3
+ int main(int argc, char *argv[])
4
+ {
5
+ two();
6
+ }
@@ -0,0 +1,7 @@
1
+ #include <stdio.h>
2
+ #include "two.h"
3
+
4
+ void two(void)
5
+ {
6
+ printf("Hello from two()\n");
7
+ }
@@ -0,0 +1,6 @@
1
+ #ifndef TWO_H
2
+ #define TWO_H
3
+
4
+ void two(void);
5
+
6
+ #endif
@@ -0,0 +1,11 @@
1
+ debug = Rscons::Environment.new do |env|
2
+ env.build_dir('src', 'debug')
3
+ env['CFLAGS'] = '-O2'
4
+ env['CPPFLAGS'] = '-DSTRING="Debug Version"'
5
+ env.Program('program-debug', Dir['src/*.c'])
6
+ end
7
+
8
+ release = debug.clone('CPPFLAGS' => '-DSTRING="Release Version"') do |env|
9
+ env.build_dir('src', 'release')
10
+ env.Program('program-release', Dir['src/*.c'])
11
+ end
@@ -0,0 +1,6 @@
1
+ #include <stdio.h>
2
+
3
+ int main(int argc, char *argv[])
4
+ {
5
+ printf("Hello, %s\n", STRING);
6
+ }
@@ -0,0 +1,16 @@
1
+ class MySource < Rscons::Builder
2
+ def run(target, sources, cache, env)
3
+ File.open(target, 'w') do |fh|
4
+ fh.puts <<EOF
5
+ #define THE_VALUE 5678
6
+ EOF
7
+ end
8
+ target
9
+ end
10
+ end
11
+
12
+ Rscons::Environment.new(echo: :short) do |env|
13
+ env.add_builder(MySource.new)
14
+ env.MySource('inc.h', [])
15
+ env.Program('program', Dir['*.c'])
16
+ end
@@ -0,0 +1,7 @@
1
+ #include <stdio.h>
2
+ #include "inc.h"
3
+
4
+ int main(int argc, char *argv[])
5
+ {
6
+ printf("The value is %d\n", THE_VALUE);
7
+ }
@@ -0,0 +1,3 @@
1
+ Rscons::Environment.new(echo: :short) do |env|
2
+ env.Program('header', Dir['*.c'])
3
+ end
@@ -0,0 +1,7 @@
1
+ #include <stdio.h>
2
+ #include "header.h"
3
+
4
+ int main(int argc, char *argv[])
5
+ {
6
+ printf("The value is %d\n", VALUE);
7
+ }
@@ -0,0 +1,6 @@
1
+ #ifndef HEADER_H
2
+ #define HEADER_H
3
+
4
+ #define VALUE 2
5
+
6
+ #endif
@@ -0,0 +1,4 @@
1
+ Rscons::Environment.new do |env|
2
+ # CHANGE FLAGS
3
+ env.Program('simple', Dir['*.c'])
4
+ end
@@ -0,0 +1,6 @@
1
+ #include <stdio.h>
2
+
3
+ int main(int argc, char *argv[])
4
+ {
5
+ printf("This is a simple C program\n");
6
+ }
@@ -0,0 +1,4 @@
1
+ Rscons::Environment.new do |env|
2
+ # CHANGE FLAGS
3
+ env.Program('simple', Dir['*.cc'])
4
+ end
@@ -0,0 +1,8 @@
1
+ #include <iostream>
2
+
3
+ using namespace std;
4
+
5
+ int main(int argc, char *argv[])
6
+ {
7
+ cout << "This is a simple C++ program" << endl;
8
+ }
data/lib/rscons.rb ADDED
@@ -0,0 +1,20 @@
1
+ require "rscons/builder"
2
+ require "rscons/cache"
3
+ require "rscons/environment"
4
+ require "rscons/varset"
5
+ require "rscons/version"
6
+
7
+ require "rscons/monkey/module"
8
+ require "rscons/monkey/string"
9
+
10
+ # default builders
11
+ require "rscons/builders/object"
12
+ require "rscons/builders/program"
13
+
14
+ # Namespace module for rscons classes
15
+ module Rscons
16
+ DEFAULT_BUILDERS = [
17
+ Object,
18
+ Program,
19
+ ]
20
+ end
@@ -0,0 +1,20 @@
1
+ module Rscons
2
+ # Class to hold an object that knows how to build a certain type of file.
3
+ class Builder
4
+ # Return a set of default variable values for the Environment to use
5
+ # unless the user overrides any.
6
+ # @param env [Environment] The Environment.
7
+ def default_variables(env)
8
+ {}
9
+ end
10
+
11
+ # Return whether this builder object is capable of producing a given target
12
+ # file name from a given source file name.
13
+ # @param target [String] The target file name.
14
+ # @param source [String, Array] The source file name(s).
15
+ # @param env [Environment] The Environment.
16
+ def produces?(target, source, env)
17
+ false
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,69 @@
1
+ module Rscons
2
+ # A default RScons builder which knows how to produce an object file from
3
+ # various types of source files.
4
+ class Object < Builder
5
+ def default_variables(env)
6
+ {
7
+ 'OBJSUFFIX' => '.o',
8
+
9
+ 'AS' => '$CC',
10
+ 'ASFLAGS' => [],
11
+ 'ASSUFFIX' => '.S',
12
+ 'ASPPPATH' => '$CPPPATH',
13
+ 'ASPPFLAGS' => '$CPPFLAGS',
14
+ 'ASDEPGEN' => ['-MMD', '-MF', '$DEPFILE'],
15
+ 'ASCOM' => ['$AS', '-c', '-o', '$TARGET', '$ASDEPGEN', '-I$[ASPPPATH]', '$ASPPFLAGS', '$ASFLAGS', '$SOURCES'],
16
+
17
+ 'CPPFLAGS' => [],
18
+ 'CPPPATH' => [],
19
+
20
+ 'CC' => 'gcc',
21
+ 'CFLAGS' => [],
22
+ 'CSUFFIX' => '.c',
23
+ 'CCDEPGEN' => ['-MMD', '-MF', '$DEPFILE'],
24
+ 'CCCOM' => ['$CC', '-c', '-o', '$TARGET', '$CCDEPGEN', '-I$[CPPPATH]', '$CPPFLAGS', '$CFLAGS', '$SOURCES'],
25
+
26
+ 'CXX' => 'g++',
27
+ 'CXXFLAGS' => [],
28
+ 'CXXSUFFIX' => '.cc',
29
+ 'CXXDEPGEN' => ['-MMD', '-MF', '$DEPFILE'],
30
+ 'CXXCOM' =>['$CXX', '-c', '-o', '$TARGET', '$CXXDEPGEN', '-I$[CPPPATH]', '$CPPFLAGS', '$CXXFLAGS', '$SOURCES'],
31
+ }
32
+ end
33
+
34
+ def produces?(target, source, env)
35
+ target.has_suffix?(env['OBJSUFFIX']) and (
36
+ source.has_suffix?(env['ASSUFFIX']) or
37
+ source.has_suffix?(env['CSUFFIX']) or
38
+ source.has_suffix?(env['CXXSUFFIX']))
39
+ end
40
+
41
+ def run(target, sources, cache, env)
42
+ vars = {
43
+ 'TARGET' => target,
44
+ 'SOURCES' => sources,
45
+ 'DEPFILE' => target.set_suffix('.mf'),
46
+ }
47
+ com_prefix = if sources.first.has_suffix?(env['ASSUFFIX'])
48
+ 'AS'
49
+ elsif sources.first.has_suffix?(env['CSUFFIX'])
50
+ 'CC'
51
+ elsif sources.first.has_suffix?(env['CXXSUFFIX'])
52
+ 'CXX'
53
+ else
54
+ raise "Error: unknown input file type: #{sources.first.inspect}"
55
+ end
56
+ command = env.build_command(env["#{com_prefix}COM"], vars)
57
+ unless cache.up_to_date?(target, command, sources)
58
+ return false unless env.execute("#{com_prefix} #{target}", command)
59
+ deps = sources
60
+ if File.exists?(vars['DEPFILE'])
61
+ deps += env.parse_makefile_deps(vars['DEPFILE'], target)
62
+ FileUtils.rm_f(vars['DEPFILE'])
63
+ end
64
+ cache.register_build(target, command, deps.uniq)
65
+ end
66
+ target
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,48 @@
1
+ module Rscons
2
+ # A default RScons builder that knows how to link object files into an
3
+ # executable program.
4
+ class Program < Builder
5
+ def default_variables(env)
6
+ {
7
+ 'LD' => nil,
8
+ 'OBJSUFFIX' => '.o',
9
+ 'LIBSUFFIX' => '.a',
10
+ 'LDFLAGS' => [],
11
+ 'LIBPATHS' => [],
12
+ 'LIBS' => [],
13
+ 'LDCOM' => ['$LD', '-o', '$TARGET', '$LDFLAGS', '$SOURCES', '-L$[LIBPATHS]', '-l$[LIBS]']
14
+ }
15
+ end
16
+
17
+ def run(target, sources, cache, env)
18
+ # convert sources to object file names
19
+ objects = sources.map do |source|
20
+ if source.has_suffix?([env['OBJSUFFIX'], env['LIBSUFFIX']])
21
+ source
22
+ else
23
+ o_file = env.get_build_fname(source, env['OBJSUFFIX', :string])
24
+ builder = env.builders.values.find { |b| b.produces?(o_file, source, env) }
25
+ builder or raise "No builder found to convert input source #{source.inspect} to an object file."
26
+ builder.run(o_file, [source], cache, env) or break
27
+ end
28
+ end
29
+ if objects
30
+ use_cxx = sources.map do |s|
31
+ s.has_suffix?(env['CXXSUFFIX'])
32
+ end.any?
33
+ ld_alt = use_cxx ? env['CXX'] : env['CC']
34
+ vars = {
35
+ 'TARGET' => target,
36
+ 'SOURCES' => objects,
37
+ 'LD' => env['LD'] || ld_alt,
38
+ }
39
+ command = env.build_command(env['LDCOM'], vars)
40
+ unless cache.up_to_date?(target, command, objects)
41
+ return false unless env.execute("LD #{target}", command)
42
+ cache.register_build(target, command, objects)
43
+ end
44
+ target
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,147 @@
1
+ require 'yaml'
2
+ require 'fileutils'
3
+ require 'digest/md5'
4
+ require 'set'
5
+ require 'rscons/version'
6
+
7
+ module Rscons
8
+ # The Cache class keeps track of file checksums, build target commands and
9
+ # dependencies in a YAML file which persists from one invocation to the next.
10
+ # Example cache:
11
+ # {
12
+ # version: '1.2.3',
13
+ # targets: {
14
+ # 'program' => {
15
+ # 'checksum' => 'A1B2C3D4',
16
+ # 'command' => ['gcc', '-o', 'program', 'program.o'],
17
+ # 'deps' => [
18
+ # {
19
+ # 'fname' => 'program.o',
20
+ # 'checksum' => '87654321',
21
+ # }
22
+ # ],
23
+ # }
24
+ # 'program.o' => {
25
+ # 'checksum' => '87654321',
26
+ # 'command' => ['gcc', '-c', '-o', 'program.o', 'program.c'],
27
+ # 'deps' => [
28
+ # {
29
+ # 'fname' => 'program.c',
30
+ # 'checksum' => '456789ABC',
31
+ # },
32
+ # {
33
+ # 'fname' => 'program.h',
34
+ # 'checksum' => '7979764643',
35
+ # }
36
+ # ]
37
+ # }
38
+ # }
39
+ # }
40
+ class Cache
41
+ #### Constants
42
+
43
+ # Name of the file to store cache information in
44
+ CACHE_FILE = '.rsconscache'
45
+
46
+ #### Class Methods
47
+
48
+ # Remove the cache file
49
+ def self.clear
50
+ FileUtils.rm_f(CACHE_FILE)
51
+ end
52
+
53
+ #### Instance Methods
54
+
55
+ # Create a Cache object and load in the previous contents from the cache
56
+ # file.
57
+ def initialize
58
+ @cache = YAML.load(File.read(CACHE_FILE)) rescue {
59
+ targets: {},
60
+ version: VERSION,
61
+ }
62
+ @lookup_checksums = {}
63
+ end
64
+
65
+ # Write the cache to disk to be loaded next time.
66
+ def write
67
+ File.open(CACHE_FILE, 'w') do |fh|
68
+ fh.puts(YAML.dump(@cache))
69
+ end
70
+ end
71
+
72
+ # Check if a target is up to date
73
+ # @param target [String] The name of the target file.
74
+ # @param command [Array] The command used to build the target.
75
+ # @param deps [Array] List of the target's dependency files.
76
+ # @param options [Hash] Optional options. Can contain the following keys:
77
+ # :strict_deps::
78
+ # Only consider a target up to date if its list of dependencies is
79
+ # exactly equal (including order) to the cached list of dependencies
80
+ # @return true value if the target is up to date, meaning that:
81
+ # - the target exists on disk
82
+ # - the cache has information for the target
83
+ # - the command used to build the target is the same as last time
84
+ # - all dependencies listed are also listed in the cache, or, if
85
+ # :strict_deps was given in options, the list of dependencies is
86
+ # exactly equal to those cached
87
+ # - each cached dependency file's current checksum matches the checksum
88
+ # stored in the cache file
89
+ def up_to_date?(target, command, deps, options = {})
90
+ # target file must exist on disk
91
+ return false unless File.exists?(target)
92
+
93
+ # target must be registered in the cache
94
+ return false unless @cache[:targets].has_key?(target)
95
+
96
+ # command used to build target must be identical
97
+ return false unless @cache[:targets][target][:command] == command
98
+
99
+ cached_deps = @cache[:targets][target][:deps].map { |dc| dc[:fname] }
100
+ if options[:strict_deps]
101
+ # depedencies passed in must exactly equal those in the cache
102
+ return false unless deps == cached_deps
103
+ else
104
+ # all dependencies passed in must exist in cache (but cache may have more)
105
+ return false unless (Set.new(deps) - Set.new(cached_deps)).empty?
106
+ end
107
+
108
+ # all cached dependencies must have their checksums match
109
+ @cache[:targets][target][:deps].map do |dep_cache|
110
+ dep_cache[:checksum] == lookup_checksum(dep_cache[:fname])
111
+ end.all?
112
+ end
113
+
114
+ # Store cache information about a target built by a builder
115
+ # @param target [String] The name of the target.
116
+ # @param command [Array] The command used to build the target.
117
+ # @param deps [Array] List of dependencies for the target.
118
+ def register_build(target, command, deps)
119
+ @cache[:targets][target] = {
120
+ command: command,
121
+ checksum: calculate_checksum(target),
122
+ deps: deps.map do |dep|
123
+ {
124
+ fname: dep,
125
+ checksum: lookup_checksum(dep),
126
+ }
127
+ end
128
+ }
129
+ end
130
+
131
+ # Private Instance Methods
132
+ private
133
+
134
+ # Return a file's checksum, or the previously calculated checksum for
135
+ # the same file
136
+ # @param file [String] The file name.
137
+ def lookup_checksum(file)
138
+ @lookup_checksums[file] || calculate_checksum(file)
139
+ end
140
+
141
+ # Calculate and return a file's checksum
142
+ # @param file [String] The file name.
143
+ def calculate_checksum(file)
144
+ @lookup_checksums[file] = Digest::MD5.hexdigest(File.read(file)).encode(__ENCODING__) rescue ''
145
+ end
146
+ end
147
+ end