reno 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +24 -0
- data/reno.rb +20 -0
- data/reno/builder.rb +136 -0
- data/reno/cache.rb +57 -0
- data/reno/configuration.rb +50 -0
- data/reno/languages.rb +61 -0
- data/reno/languages/c.rb +156 -0
- data/reno/lock.rb +33 -0
- data/reno/options.rb +89 -0
- data/reno/package.rb +82 -0
- data/reno/platforms.rb +17 -0
- data/reno/sourcefile.rb +139 -0
- data/reno/toolchains.rb +39 -0
- data/reno/toolchains/gnu.rb +53 -0
- metadata +68 -0
data/LICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2010, Zoxc
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
* Redistributions of source code must retain the above copyright
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
8
|
+
* Redistributions in binary form must reproduce the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
10
|
+
documentation and/or other materials provided with the distribution.
|
11
|
+
* Neither the name of Zoxc nor the
|
12
|
+
names of its contributors may be used to endorse or promote products
|
13
|
+
derived from this software without specific prior written permission.
|
14
|
+
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
19
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
20
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
21
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
22
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
23
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
24
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/reno.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Reno
|
2
|
+
require 'rexml/document'
|
3
|
+
|
4
|
+
ROOT = File.dirname(File.expand_path(__FILE__)) unless defined?(Reno::ROOT)
|
5
|
+
|
6
|
+
unless $LOAD_PATH.any? { |lp| File.expand_path(lp) == ROOT }
|
7
|
+
$LOAD_PATH.unshift(ROOT)
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'reno/lock'
|
11
|
+
require 'reno/configuration'
|
12
|
+
require 'reno/builder'
|
13
|
+
require 'reno/cache'
|
14
|
+
require 'reno/platforms'
|
15
|
+
require 'reno/sourcefile'
|
16
|
+
require 'reno/options'
|
17
|
+
require 'reno/languages'
|
18
|
+
require 'reno/toolchains'
|
19
|
+
require 'reno/package'
|
20
|
+
end
|
data/reno/builder.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Reno
|
5
|
+
class BuilderError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class Builder
|
9
|
+
attr_reader :sources, :cache, :sqlcache, :base, :output, :objects, :package, :conf
|
10
|
+
attr :conf, true
|
11
|
+
|
12
|
+
def self.readydirs(path)
|
13
|
+
FileUtils.makedirs(File.dirname(path))
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.cleanpath(base, path)
|
17
|
+
Pathname.new(File.expand_path(path)).relative_path_from(Pathname.new(base)).to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.execute(command, *args)
|
21
|
+
#puts [command, *args].join(' ')
|
22
|
+
IO.popen([command, *args]) do |f|
|
23
|
+
f.readlines
|
24
|
+
end
|
25
|
+
raise "#{command} failed with error code #{$?.exitstatus}" if $?.exitstatus != 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.capture(command, *args)
|
29
|
+
#puts [command, *args].join(' ')
|
30
|
+
result = nil
|
31
|
+
IO.popen([command, *args]) do |f|
|
32
|
+
result = f.readlines.join('')
|
33
|
+
end
|
34
|
+
raise "#{command} failed with error code #{$?.exitstatus}" if $?.exitstatus != 0
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.clean(filename)
|
39
|
+
if File.exists?(filename)
|
40
|
+
File.delete(filename)
|
41
|
+
begin
|
42
|
+
dir = File.dirname(filename)
|
43
|
+
while File.exists?(dir)
|
44
|
+
Dir.rmdir(dir)
|
45
|
+
dir = File.dirname(dir)
|
46
|
+
end
|
47
|
+
rescue SystemCallError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def puts(*args)
|
53
|
+
@puts_lock.synchronize do
|
54
|
+
Kernel.puts *args
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def output(file)
|
59
|
+
result = File.expand_path(file, @bulid_base)
|
60
|
+
Builder.readydirs(result)
|
61
|
+
result
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize(package)
|
65
|
+
@package = package
|
66
|
+
@changed = Lock.new
|
67
|
+
@base = @package.base
|
68
|
+
@bulid_base = File.expand_path(@package.output, @base)
|
69
|
+
@objects = Lock.new([])
|
70
|
+
@files = Lock.new([])
|
71
|
+
@puts_lock = Mutex.new
|
72
|
+
@cache = Lock.new({})
|
73
|
+
end
|
74
|
+
|
75
|
+
def run(threads = 8)
|
76
|
+
# Creates an unique list of the files
|
77
|
+
@files.value = Dir[*@conf.get(:patterns)].map { |file| Builder.cleanpath(@base, file) }.uniq
|
78
|
+
@sqlcache = Cache.new(self)
|
79
|
+
|
80
|
+
# Start worker threads
|
81
|
+
workers = []
|
82
|
+
threads.times do
|
83
|
+
workers << Thread.new do
|
84
|
+
work
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Wait for all the workers
|
89
|
+
workers.each { |worker| worker.join }
|
90
|
+
|
91
|
+
# Normalize locks
|
92
|
+
@objects = @objects.value
|
93
|
+
|
94
|
+
# Link the package if it changed or the output doesn't exist
|
95
|
+
|
96
|
+
output_name = output(package.output_name)
|
97
|
+
|
98
|
+
if @changed.value || !File.exists?(output_name)
|
99
|
+
puts "Linking #{@package.name}..."
|
100
|
+
|
101
|
+
# Find a toochain to link this package
|
102
|
+
linker = @package.default[:linker]
|
103
|
+
linker = Toolchains::Hash.values.first unless linker
|
104
|
+
raise BuilderError, "Unable to find a linker to use." unless linker
|
105
|
+
|
106
|
+
linker.link(self, output(package.output_name))
|
107
|
+
else
|
108
|
+
puts "Nothing to do with #{@package.name}."
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_file
|
113
|
+
@files.lock do |files|
|
114
|
+
files.pop
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def output_file(result)
|
119
|
+
@objects.lock do |output|
|
120
|
+
output << result
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def work
|
125
|
+
while filename = get_file
|
126
|
+
file = SourceFile.locate(self, filename)
|
127
|
+
rebuild = file.rebuild?
|
128
|
+
if rebuild
|
129
|
+
@changed.value = true
|
130
|
+
file.build
|
131
|
+
end
|
132
|
+
output_file file
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/reno/cache.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'sequel'
|
3
|
+
|
4
|
+
module Reno
|
5
|
+
class Cache
|
6
|
+
attr_reader :base, :db
|
7
|
+
|
8
|
+
class FileModel
|
9
|
+
def initialize(db, name, &block)
|
10
|
+
@db = db
|
11
|
+
@row = @db[:name => name]
|
12
|
+
unless @row
|
13
|
+
@row = block.call
|
14
|
+
@row[:id] = @db.insert(@row)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](name)
|
19
|
+
@row[name]
|
20
|
+
end
|
21
|
+
|
22
|
+
def []=(name, value)
|
23
|
+
@row[name] = value
|
24
|
+
@db.filter(:id => @row[:id]).update(name => value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def setup_table(name, &block)
|
29
|
+
@db.create_table(name, &block) unless @db.table_exists?(name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(builder)
|
33
|
+
@db = Sequel.sqlite(:database => builder.output('cache.db'))
|
34
|
+
|
35
|
+
setup_table :files do
|
36
|
+
primary_key :id, :type => Integer
|
37
|
+
String :name
|
38
|
+
String :md5
|
39
|
+
String :output
|
40
|
+
FalseClass :dependencies
|
41
|
+
end
|
42
|
+
|
43
|
+
setup_table :dependencies do
|
44
|
+
Integer :file
|
45
|
+
Integer :dependency
|
46
|
+
end
|
47
|
+
|
48
|
+
Languages.constants.each do |language|
|
49
|
+
language = Languages.const_get(language)
|
50
|
+
|
51
|
+
next if language.superclass != Languages::Language
|
52
|
+
|
53
|
+
language.setup_schema(self)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Reno
|
2
|
+
class ConfigurationError < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
class ConfigurationNode
|
6
|
+
attr_reader :langs, :builder, :parent, :hash, :patterns
|
7
|
+
|
8
|
+
def initialize(package, builder, parent, patterns)
|
9
|
+
@package = package
|
10
|
+
@builder = builder
|
11
|
+
@parent = parent
|
12
|
+
@patterns = patterns.to_a
|
13
|
+
@children = []
|
14
|
+
@hash = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def add(name, values)
|
18
|
+
@hash[name] = [] unless @hash.has_key?(name)
|
19
|
+
@hash[name].concat(values)
|
20
|
+
end
|
21
|
+
|
22
|
+
def get(name, file = nil, base = nil)
|
23
|
+
if file && !@patterns.empty?
|
24
|
+
Dir.chdir(base) do
|
25
|
+
return nil unless @patterns.any? { |pattern| File.fnmatch?(pattern, file) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
child_values = []
|
30
|
+
|
31
|
+
@children.each do |child|
|
32
|
+
child_values.concat(child.get(name, file).reject { |value| !value })
|
33
|
+
end
|
34
|
+
|
35
|
+
if Array === @hash[name]
|
36
|
+
@hash[name] + child_values
|
37
|
+
else
|
38
|
+
child_values
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def derive(patterns)
|
43
|
+
patterns = patterns.to_a
|
44
|
+
child = ConfigurationNode.new(@package, @builder, self, patterns)
|
45
|
+
child.add(:patterns, patterns)
|
46
|
+
@children << child
|
47
|
+
child
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/reno/languages.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
module Reno
|
2
|
+
module Languages
|
3
|
+
class LanguageError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.locate(name)
|
7
|
+
Languages.constants.map { |lang| Languages.const_get(lang) }.each do |language|
|
8
|
+
|
9
|
+
next if language.superclass != Language
|
10
|
+
|
11
|
+
if language.name == name
|
12
|
+
return language
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
raise "Unable to find the language '#{name}'."
|
17
|
+
end
|
18
|
+
|
19
|
+
class Language
|
20
|
+
attr_reader :option
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"#<Language:#{self.class.name}>"
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(option, block)
|
27
|
+
@option = option
|
28
|
+
instance_eval(&block) if block
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_dependencies(file)
|
32
|
+
[]
|
33
|
+
end
|
34
|
+
|
35
|
+
def read(name)
|
36
|
+
instance_variable_get "@#{name}"
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
attr :name, true
|
41
|
+
|
42
|
+
def table_name(name)
|
43
|
+
"lang_#{self.name}_#{name}".downcase.to_sym
|
44
|
+
end
|
45
|
+
|
46
|
+
def setup_table(cache, name, &block)
|
47
|
+
cache.setup_table(table_name(name), &block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def setup_schema(cache)
|
51
|
+
end
|
52
|
+
|
53
|
+
def merge(nodes)
|
54
|
+
result = new(nil, nil)
|
55
|
+
nodes.each { |node| result.merge(node) }
|
56
|
+
result
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/reno/languages/c.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
module Reno
|
2
|
+
module Languages
|
3
|
+
class C < Language
|
4
|
+
self.name = 'C'
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
@defines = {}
|
8
|
+
@headers = []
|
9
|
+
@std = nil
|
10
|
+
@strict = nil
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def compare(file)
|
15
|
+
defines = file.db[self.class.table_name(:defines)]
|
16
|
+
attribs = file.db[self.class.table_name(:attribs)]
|
17
|
+
|
18
|
+
hash = {}
|
19
|
+
|
20
|
+
defines.filter(:file => file.row[:id]).all do |row|
|
21
|
+
hash[row[:define]] = row[:value]
|
22
|
+
end
|
23
|
+
|
24
|
+
attribs = attribs.filter(:file => file.row[:id]).first
|
25
|
+
attribs = {:std => nil, :strict => false} unless attribs
|
26
|
+
|
27
|
+
hash == @defines and attribs[:std] == @std.to_s and attribs[:strict] == @strict
|
28
|
+
end
|
29
|
+
|
30
|
+
def print(*values)
|
31
|
+
puts values.inspect
|
32
|
+
end
|
33
|
+
|
34
|
+
def store(file)
|
35
|
+
defines = file.db[self.class.table_name(:defines)]
|
36
|
+
attribs = file.db[self.class.table_name(:attribs)]
|
37
|
+
|
38
|
+
# Delete existing rows
|
39
|
+
defines.filter(:file => file.row[:id]).delete
|
40
|
+
attribs.filter(:file => file.row[:id]).delete
|
41
|
+
|
42
|
+
# Add the current ones
|
43
|
+
@defines.each_pair do |key, value|
|
44
|
+
defines.insert(:file => file.row[:id], :define => key, :value => value)
|
45
|
+
end
|
46
|
+
|
47
|
+
attribs.insert(:file => file.row[:id], :strict => @strict, :std => @std.to_s)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.setup_schema(cache)
|
51
|
+
setup_table(cache, :defines) do
|
52
|
+
Integer :file
|
53
|
+
String :define
|
54
|
+
String :value
|
55
|
+
end
|
56
|
+
|
57
|
+
setup_table(cache, :attribs) do
|
58
|
+
Integer :file
|
59
|
+
FalseClass :strict
|
60
|
+
String :std
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def merge(other)
|
65
|
+
@defines.merge!(other.read(:defines))
|
66
|
+
|
67
|
+
other_value = other.read(:std)
|
68
|
+
@std = other_value if other_value
|
69
|
+
|
70
|
+
other_value = other.read(:strict)
|
71
|
+
@strict = other_value if other_value != nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def define(name, value = nil)
|
75
|
+
@defines[name] = value
|
76
|
+
end
|
77
|
+
|
78
|
+
def strict(value = true)
|
79
|
+
@strict = value
|
80
|
+
end
|
81
|
+
|
82
|
+
def std(value)
|
83
|
+
@std = value
|
84
|
+
end
|
85
|
+
|
86
|
+
def headers(*dirs)
|
87
|
+
@headers.concat(dirs)
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.extensions
|
91
|
+
['.c', '.h']
|
92
|
+
end
|
93
|
+
|
94
|
+
# The get_dependencies method is under the GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 license.
|
95
|
+
# It's from rant-0.5.8\lib\rant\c\include.rb
|
96
|
+
|
97
|
+
# include.rb - Support for C - parsing #include statements.
|
98
|
+
#
|
99
|
+
# Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
|
100
|
+
|
101
|
+
|
102
|
+
# Searches for all `#include' statements in the C/C++ source
|
103
|
+
# from the string +src+.
|
104
|
+
#
|
105
|
+
# Returns two arguments:
|
106
|
+
# 1. A list of all standard library includes (e.g. #include <stdio.h>).
|
107
|
+
# 2. A list of all local includes (e.g. #include "stdio.h").
|
108
|
+
|
109
|
+
def self.get_dependencies(file)
|
110
|
+
src = file.content
|
111
|
+
|
112
|
+
includes = []
|
113
|
+
in_block_comment = false
|
114
|
+
prev_line = nil
|
115
|
+
|
116
|
+
src.each_line do |line|
|
117
|
+
line.chomp!
|
118
|
+
|
119
|
+
if block_start_i = line.index("/*")
|
120
|
+
c_start_i = line.index("//")
|
121
|
+
if !c_start_i || block_start_i < c_start_i
|
122
|
+
if block_end_i = line.index("*/")
|
123
|
+
if block_end_i > block_start_i
|
124
|
+
line[block_start_i..block_end_i+1] = ""
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
if prev_line
|
131
|
+
line = prev_line << line
|
132
|
+
prev_line = nil
|
133
|
+
end
|
134
|
+
|
135
|
+
if line =~ /\\$/
|
136
|
+
prev_line = line.chomp[0...line.length-1]
|
137
|
+
end
|
138
|
+
|
139
|
+
if in_block_comment
|
140
|
+
in_block_comment = false if line =~ %r|\*/|
|
141
|
+
next
|
142
|
+
end
|
143
|
+
|
144
|
+
case line
|
145
|
+
when /\s*#\s*include\s+"([^"]+)"/
|
146
|
+
includes << $1
|
147
|
+
when %r|(?!//)[^/]*/\*|
|
148
|
+
in_block_comment = true
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
includes
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
data/reno/lock.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Reno
|
2
|
+
class Lock
|
3
|
+
def initialize(value = nil)
|
4
|
+
@value = value
|
5
|
+
@mutex = Mutex.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def lock
|
9
|
+
@mutex.synchronize do
|
10
|
+
yield @value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def value
|
15
|
+
if block_given?
|
16
|
+
@mutex.synchronize do
|
17
|
+
return @value if @value
|
18
|
+
@value = yield
|
19
|
+
end
|
20
|
+
else
|
21
|
+
@mutex.synchronize do
|
22
|
+
@value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def value=(value)
|
28
|
+
@mutex.synchronize do
|
29
|
+
@value = value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/reno/options.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
module Reno
|
2
|
+
class Option
|
3
|
+
attr_reader :package, :parent, :name, :desc, :children
|
4
|
+
|
5
|
+
def initialize(package, parent, name = nil, desc = nil, default = nil, block)
|
6
|
+
@package = package
|
7
|
+
@parent = parent
|
8
|
+
@name = name
|
9
|
+
@desc = desc
|
10
|
+
@default = default
|
11
|
+
@children = []
|
12
|
+
@langs = {}
|
13
|
+
instance_eval(&block) if block
|
14
|
+
end
|
15
|
+
|
16
|
+
def apply_config(conf, data)
|
17
|
+
conf.add(:langs, [@langs])
|
18
|
+
@children.each do |child|
|
19
|
+
child_data = data.elements.find { |element| element.attribute('name').value == child.name.to_s && element.name == child.xml_node } if data
|
20
|
+
child.apply_config(conf, child_data)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def xml_node
|
25
|
+
"group"
|
26
|
+
end
|
27
|
+
|
28
|
+
def o(name, desc = nil, default = false, &block)
|
29
|
+
@children << BooleanOption.new(@package, self, name, desc, default, block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def group(name, desc = nil, &block)
|
33
|
+
@children << Option.new(@package, self, name, desc, block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def source(*args, &block)
|
37
|
+
@children << SourceOption.new(@package, self, block, args)
|
38
|
+
end
|
39
|
+
|
40
|
+
alias :sources :source
|
41
|
+
|
42
|
+
def default(hash)
|
43
|
+
@package.default.merge!(hash)
|
44
|
+
end
|
45
|
+
|
46
|
+
def lang(name = nil, &block)
|
47
|
+
lang = name || @package.default[:lang]
|
48
|
+
|
49
|
+
raise "Unable to find the default language." unless lang
|
50
|
+
|
51
|
+
lang = lang.to_s
|
52
|
+
|
53
|
+
return @langs[lang] if @langs[lang]
|
54
|
+
|
55
|
+
obj = Languages.locate(lang).new(self, block)
|
56
|
+
@langs[lang] = obj
|
57
|
+
obj
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class BooleanOption < Option
|
62
|
+
def apply_config(conf, data)
|
63
|
+
if (data && data.attribute('value')) ? data.attribute('value').value == "true" : @default
|
64
|
+
super
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def xml_node
|
69
|
+
"option"
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
class SourceOption < Option
|
75
|
+
def initialize(*args, sources)
|
76
|
+
@sources = sources
|
77
|
+
super *args
|
78
|
+
end
|
79
|
+
|
80
|
+
def apply_config(conf, data)
|
81
|
+
conf = conf.derive(@sources)
|
82
|
+
super
|
83
|
+
end
|
84
|
+
|
85
|
+
def xml_node
|
86
|
+
"file"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/reno/package.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
module Reno
|
2
|
+
class PackageError < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
Packages = {}
|
6
|
+
|
7
|
+
class PackageOption < Option
|
8
|
+
def name(name)
|
9
|
+
@package.name = name
|
10
|
+
end
|
11
|
+
|
12
|
+
def desc(desc)
|
13
|
+
@package.desc = desc
|
14
|
+
end
|
15
|
+
|
16
|
+
def version(version)
|
17
|
+
@package.version = version
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Package
|
22
|
+
attr_reader :default, :name, :base, :output
|
23
|
+
attr :desc, true
|
24
|
+
attr :version, true
|
25
|
+
|
26
|
+
def initialize(&block)
|
27
|
+
@default = {}
|
28
|
+
@output = 'build'
|
29
|
+
@base = Dir.getwd
|
30
|
+
# This should be the last thing to be set up. The object might depend on the other variables.
|
31
|
+
@option = PackageOption.new(self, nil, block)
|
32
|
+
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def output_name
|
37
|
+
raise "You can't link packages!"
|
38
|
+
end
|
39
|
+
|
40
|
+
def name=(name)
|
41
|
+
raise "You have already assigned this package a name" if @name
|
42
|
+
|
43
|
+
@name = name.to_s
|
44
|
+
|
45
|
+
raise PackageError, "The package #{@name} already exist." if Packages[@name]
|
46
|
+
Packages[@name] = self
|
47
|
+
end
|
48
|
+
|
49
|
+
def builder(data = nil)
|
50
|
+
builder = Builder.new(self)
|
51
|
+
conf = ConfigurationNode.new(@option.package, builder, nil, [])
|
52
|
+
@option.apply_config(conf, data)
|
53
|
+
builder.conf = conf
|
54
|
+
builder
|
55
|
+
end
|
56
|
+
|
57
|
+
def load_config(data)
|
58
|
+
conf = PackageConf.new(self)
|
59
|
+
Options.new(conf, data, @options)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Library < Package
|
64
|
+
def output_name
|
65
|
+
if Rake::Win32.windows?
|
66
|
+
@name + '.dll'
|
67
|
+
else
|
68
|
+
@name + '.so'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class Application < Package
|
74
|
+
def output_name
|
75
|
+
if Platforms.current == Platforms::Windows
|
76
|
+
@name + '.exe'
|
77
|
+
else
|
78
|
+
@name
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/reno/platforms.rb
ADDED
data/reno/sourcefile.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module Reno
|
4
|
+
class SourceFileError < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
class SourceFile
|
8
|
+
attr_reader :path, :name, :row, :db, :builder
|
9
|
+
|
10
|
+
def self.locate(builder, filename)
|
11
|
+
builder.cache.lock do |cache|
|
12
|
+
return cache[filename] || new(builder, filename, cache)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(builder, filename, cache)
|
17
|
+
@builder = builder
|
18
|
+
@name = filename
|
19
|
+
@path = File.expand_path(@name, @builder.base)
|
20
|
+
@changed = Lock.new
|
21
|
+
@language = Lock.new
|
22
|
+
@lang_conf = Lock.new
|
23
|
+
@compiler = Lock.new
|
24
|
+
@output = Lock.new
|
25
|
+
@content = Lock.new
|
26
|
+
@digest = Lock.new
|
27
|
+
cache[filename] = self
|
28
|
+
@db = @builder.sqlcache.db
|
29
|
+
@row = Cache::FileModel.new(@db[:files], @name) do
|
30
|
+
{:name => @name, :md5 => digest, :dependencies => false, :output => nil}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_language
|
35
|
+
file_ext = File.extname(@name).downcase
|
36
|
+
|
37
|
+
Languages.constants.map { |name| Languages.const_get(name) }.each do |language|
|
38
|
+
next if language.superclass != Languages::Language
|
39
|
+
|
40
|
+
if language.extensions.any? { |ext| ext == file_ext }
|
41
|
+
return language
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
raise SourceFileError, "Unable to find a language for the file '#{@name}'."
|
46
|
+
end
|
47
|
+
|
48
|
+
def digest
|
49
|
+
@digest.value { Digest::MD5.hexdigest(content) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def content
|
53
|
+
@content.value { File.open(@path, 'rb') { |f| f.read } }
|
54
|
+
end
|
55
|
+
|
56
|
+
def output
|
57
|
+
@output.value { @builder.output(@name + '.o') }
|
58
|
+
end
|
59
|
+
|
60
|
+
def language
|
61
|
+
@language.value { find_language }
|
62
|
+
end
|
63
|
+
|
64
|
+
def compiler
|
65
|
+
@compiler.value { Toolchains.locate(language) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def lang_conf
|
69
|
+
@lang_conf.value do
|
70
|
+
langs = @builder.conf.get(:langs, nil).map { |langs| langs[language.name] }.reject { |lang| !lang }
|
71
|
+
language.merge(langs)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def get_dependencies
|
76
|
+
@builder.puts "Getting dependencies for #{@name}..."
|
77
|
+
@db[:dependencies].filter(:file => @row[:id]).delete
|
78
|
+
|
79
|
+
dependencies = compiler.get_dependencies(self)
|
80
|
+
dependencies.each do |path|
|
81
|
+
file = SourceFile.locate(@builder, Builder.cleanpath(@builder.base, path))
|
82
|
+
@db[:dependencies].insert(:file => @row[:id], :dependency => file.row[:id])
|
83
|
+
end
|
84
|
+
|
85
|
+
@row[:dependencies] = true
|
86
|
+
end
|
87
|
+
|
88
|
+
def dependencies_changed?
|
89
|
+
get_dependencies unless @row[:dependencies]
|
90
|
+
|
91
|
+
dependencies = @db[:dependencies].filter(:file => @row[:id]).all
|
92
|
+
|
93
|
+
dependencies.any? do |dependency|
|
94
|
+
SourceFile.locate(@builder, @db[:files][:id => dependency[:dependency]][:name]).changed?
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def changed?
|
99
|
+
@changed.value do |changed|
|
100
|
+
# Check if the file has changed and reset dependencies and output if needed
|
101
|
+
|
102
|
+
if !File.exists?(@path)
|
103
|
+
@row[:dependencies] = false
|
104
|
+
changed = true
|
105
|
+
elsif @row[:md5] != digest then
|
106
|
+
@row[:dependencies] = false
|
107
|
+
@row[:md5] = digest
|
108
|
+
changed = true
|
109
|
+
else
|
110
|
+
changed = dependencies_changed? ? true : false
|
111
|
+
end
|
112
|
+
|
113
|
+
@row[:output] = nil if changed
|
114
|
+
|
115
|
+
changed
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def rebuild?
|
120
|
+
return true unless @row[:output] and File.exists?(File.expand_path(@row[:output], @builder.base)) and lang_conf.compare(self)
|
121
|
+
|
122
|
+
changed?
|
123
|
+
end
|
124
|
+
|
125
|
+
def build
|
126
|
+
@builder.puts "Compiling #{@name}..."
|
127
|
+
compiler.compile(self)
|
128
|
+
|
129
|
+
output = @output.value
|
130
|
+
|
131
|
+
if File.exists? output
|
132
|
+
@row[:output] = output
|
133
|
+
lang_conf.store(self)
|
134
|
+
else
|
135
|
+
raise SourceFileError, "Can't find output '#{output}' from #{name}."
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
data/reno/toolchains.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Reno
|
2
|
+
module Toolchains
|
3
|
+
class ToolchainError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
Hash = {}
|
7
|
+
Compilers = {}
|
8
|
+
|
9
|
+
def self.register(toolchain, language)
|
10
|
+
array = Compilers[language]
|
11
|
+
array = Compilers[language] = [] unless array
|
12
|
+
array << toolchain
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.locate(language)
|
16
|
+
array = Compilers[language.name]
|
17
|
+
|
18
|
+
raise "Unable to find a compiler for the language '#{language.name}'." unless array
|
19
|
+
|
20
|
+
array.first
|
21
|
+
end
|
22
|
+
|
23
|
+
class Toolchain
|
24
|
+
attr_reader :option
|
25
|
+
|
26
|
+
def self.get_dependencies(file)
|
27
|
+
file.language.get_dependencies(file)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.register(name, *languages)
|
31
|
+
Hash[name] = self
|
32
|
+
|
33
|
+
languages.each do |language|
|
34
|
+
Toolchains.register(self, language)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Reno
|
2
|
+
module Toolchains
|
3
|
+
class Gnu < Toolchain
|
4
|
+
register :gnu, 'C'
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def command(file = nil)
|
8
|
+
ENV['CC'] || 'gcc'
|
9
|
+
end
|
10
|
+
|
11
|
+
def language(file)
|
12
|
+
case file.lang_conf
|
13
|
+
when Languages::C
|
14
|
+
'c'
|
15
|
+
else
|
16
|
+
raise ToolchainError, "GCC doesn't support the language #{file.language.name}."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def compile(file)
|
21
|
+
Builder.execute(command(file), '-pipe', *defines(file), *std(file), '-x', language(file), '-c', file.path, '-o', file.output)
|
22
|
+
end
|
23
|
+
|
24
|
+
def std(file)
|
25
|
+
case file.lang_conf.read(:std)
|
26
|
+
when nil
|
27
|
+
return []
|
28
|
+
when :c89
|
29
|
+
result = '89'
|
30
|
+
when :c99
|
31
|
+
result = '99'
|
32
|
+
else
|
33
|
+
raise ToolchainError, "Unable to find language mode #{file.lang_conf.read(:std)}."
|
34
|
+
end
|
35
|
+
|
36
|
+
["-std=#{file.lang_conf.read(:strict) ? "c" : "gnu"}#{result}"]
|
37
|
+
end
|
38
|
+
|
39
|
+
def defines(file)
|
40
|
+
defines = []
|
41
|
+
file.lang_conf.read(:defines).each_pair do |key, value|
|
42
|
+
defines << '-D' << (value ? "#{key}=#{value}" : key.to_s)
|
43
|
+
end
|
44
|
+
defines
|
45
|
+
end
|
46
|
+
|
47
|
+
def link(builder, output)
|
48
|
+
Builder.execute(command, '-pipe', *builder.objects.map { |object| object.output }, '-o', output)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: reno
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Zoxc
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-04 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email:
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- LICENSE
|
26
|
+
- reno.rb
|
27
|
+
- reno/builder.rb
|
28
|
+
- reno/cache.rb
|
29
|
+
- reno/configuration.rb
|
30
|
+
- reno/languages/c.rb
|
31
|
+
- reno/languages.rb
|
32
|
+
- reno/lock.rb
|
33
|
+
- reno/options.rb
|
34
|
+
- reno/package.rb
|
35
|
+
- reno/platforms.rb
|
36
|
+
- reno/sourcefile.rb
|
37
|
+
- reno/toolchains/gnu.rb
|
38
|
+
- reno/toolchains.rb
|
39
|
+
has_rdoc: true
|
40
|
+
homepage:
|
41
|
+
licenses: []
|
42
|
+
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
require_paths:
|
47
|
+
- .
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.9.0
|
53
|
+
version:
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.3.5
|
64
|
+
signing_key:
|
65
|
+
specification_version: 3
|
66
|
+
summary: Reno is a compiling framework in Ruby.
|
67
|
+
test_files: []
|
68
|
+
|