makefile 0.0.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +11 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +37 -0
- data/Rakefile +7 -0
- data/lib/makefile.rb +7 -0
- data/lib/makefile/command.rb +21 -0
- data/lib/makefile/errors.rb +7 -0
- data/lib/makefile/expression.rb +61 -0
- data/lib/makefile/macro.rb +45 -0
- data/lib/makefile/reader.rb +59 -0
- data/lib/makefile/suffix_rule.rb +15 -0
- data/lib/makefile/target.rb +29 -0
- data/lib/makefile/version.rb +3 -0
- data/makefile.gemspec +25 -0
- data/spec/command_spec.rb +48 -0
- data/spec/expression_spec.rb +171 -0
- data/spec/reader_spec.rb +126 -0
- data/spec/spec_helper.rb +1 -0
- metadata +123 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 91375163dc2731c3a0e2d1ca9e2f8470e9881bb9d111b9923a8b462708d7c029
|
4
|
+
data.tar.gz: 7ed3732d26e82c8f19b1614417105456089d0ca413461d77e252fadd5efad9b1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 34c99ce75a8561f0223febaa24aaecba36578ca65ddf0103ae6a1471e5df38a8b38e3742970ad779e0e517fd55dfd4a32b4a0d573d928344d378c4bcd8c9521f
|
7
|
+
data.tar.gz: f48064e328200049c8491e468f01d55d607d1df6e6b3d3e3f0820dfee174d67885b912e14425b70c86e76a9c199ed1cf68e0683b654452608cf238f32de0922f
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Yuki Sonoda (Yugui)
|
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,37 @@
|
|
1
|
+
# Makefile
|
2
|
+
|
3
|
+
An incomplete implementation of Makefile parser.
|
4
|
+
|
5
|
+
It supports a subset of GNU Make's syntax.
|
6
|
+
|
7
|
+
## Project Status
|
8
|
+
|
9
|
+
Pre-alpha. APIs can change without notice or backward compatibility.
|
10
|
+
|
11
|
+
The current priority is to parse CRuby's makefiles.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
gem 'makefile'
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install makefile
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
TODO: Write usage instructions here
|
30
|
+
|
31
|
+
## Contributing
|
32
|
+
|
33
|
+
1. Fork it
|
34
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
35
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
36
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
37
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/makefile.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
|
3
|
+
module Makefile
|
4
|
+
class Command
|
5
|
+
def initialize(raw_cmd)
|
6
|
+
@raw_cmd = raw_cmd
|
7
|
+
@cmd = Expression.new(raw_cmd)
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :raw_cmd, :cmd
|
11
|
+
|
12
|
+
def ==(rhs)
|
13
|
+
self.raw_cmd == rhs.raw_cmd
|
14
|
+
end
|
15
|
+
|
16
|
+
def argv(target, macroset)
|
17
|
+
args = cmd.evaluate(target, macroset).sub(/\A@/, '')
|
18
|
+
Shellwords.split(args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Makefile
|
4
|
+
MACRO_REF_PATTERN = %r!
|
5
|
+
\$ (?:
|
6
|
+
\(
|
7
|
+
(?<paren> [^:)]+ ) (?: :(?<paren_subst>[^=]+) = (?<paren_substval>[^)]*) )?
|
8
|
+
\) |
|
9
|
+
{
|
10
|
+
(?<brace> [^:}]+ ) (?: :(?<brace_subst>[^=]+) = (?<brace_substval>[^}]*) )?
|
11
|
+
} |
|
12
|
+
(?<single> [^({] )
|
13
|
+
)
|
14
|
+
!x
|
15
|
+
|
16
|
+
# An expression which can contain macro reference
|
17
|
+
class Expression
|
18
|
+
def initialize(raw_text)
|
19
|
+
@raw_text = raw_text
|
20
|
+
end
|
21
|
+
attr_reader :raw_text
|
22
|
+
|
23
|
+
def evaluate(target=nil, macroset)
|
24
|
+
evaluate_internal(target, macroset, Set.new)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Shows some implementation details of #evaluate
|
28
|
+
#
|
29
|
+
# Only Makefile::Macro is allowed to call this method.
|
30
|
+
# Others should use #evaluate
|
31
|
+
def evaluate_internal(target, macroset, parent_refs)
|
32
|
+
raw_text.gsub(MACRO_REF_PATTERN) do
|
33
|
+
match = $~
|
34
|
+
case
|
35
|
+
when match[:single]
|
36
|
+
type, name = :single, $~[:single]
|
37
|
+
when match[:paren]
|
38
|
+
type = :quoted
|
39
|
+
name = match[:paren]
|
40
|
+
substpat, substexpr = match[:paren_subst], match[:paren_substval]
|
41
|
+
when match[:brace]
|
42
|
+
type = :quoted
|
43
|
+
name = match[:brace]
|
44
|
+
substpat, substexpr = match[:brace_subst], match[:brace_substval]
|
45
|
+
else
|
46
|
+
raise 'never reach'
|
47
|
+
end
|
48
|
+
|
49
|
+
macro = macroset[name]
|
50
|
+
if macro&.match?(type)
|
51
|
+
expanded = macro.expand_internal(target, macroset, parent_refs)
|
52
|
+
next expanded unless substpat
|
53
|
+
|
54
|
+
replacement = Expression.new(substexpr).
|
55
|
+
evaluate_internal(target, macroset, parent_refs)
|
56
|
+
expanded.gsub(/#{Regexp.escape substpat}(?=\s|$)/, replacement)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'makefile/expression'
|
2
|
+
|
3
|
+
module Makefile
|
4
|
+
class Macro
|
5
|
+
def initialize(name, raw_value = nil, allow_single: true, allow_quoted: true, &block)
|
6
|
+
raise ArgumentError, 'either raw_value or block must be given' unless \
|
7
|
+
raw_value or block
|
8
|
+
|
9
|
+
@name = name
|
10
|
+
@raw_value = raw_value
|
11
|
+
@value = Expression.new(raw_value) if raw_value
|
12
|
+
@allow_single = allow_single
|
13
|
+
@allow_quoted = allow_quoted
|
14
|
+
@block = block
|
15
|
+
end
|
16
|
+
attr_reader :name, :raw_value, :value
|
17
|
+
|
18
|
+
def match?(type)
|
19
|
+
case type
|
20
|
+
when :single
|
21
|
+
return @allow_single
|
22
|
+
when :quoted
|
23
|
+
return @allow_quoted
|
24
|
+
else
|
25
|
+
raise ArgumentError, 'must be :single or :quoted'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Shows some implementation details of #expand
|
30
|
+
#
|
31
|
+
# Only Makefile::Expression is allowed to call this method.
|
32
|
+
def expand_internal(target, macroset, parent_refs)
|
33
|
+
raise Makefile::Error, "Macro #{name} references itself" \
|
34
|
+
if parent_refs.include?(name)
|
35
|
+
|
36
|
+
parent_refs << name
|
37
|
+
begin
|
38
|
+
expr = value || Expression.new(@block.call(target, macroset))
|
39
|
+
expr.evaluate_internal(target, macroset, parent_refs)
|
40
|
+
ensure
|
41
|
+
parent_refs.delete name
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'makefile/command'
|
2
|
+
require 'makefile/target'
|
3
|
+
require 'makefile/errors'
|
4
|
+
|
5
|
+
class Makefile::Reader
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize(input)
|
9
|
+
@input = input
|
10
|
+
end
|
11
|
+
|
12
|
+
def each
|
13
|
+
rule = nil
|
14
|
+
while line = read_line
|
15
|
+
next if line.chomp.empty?
|
16
|
+
if line.start_with?("\t")
|
17
|
+
raise Makefile::ParseError, "commands outside of rule at line #{lineno}" unless rule
|
18
|
+
command = Makefile::Command.new(line[1..-1])
|
19
|
+
rule.add_command(command)
|
20
|
+
next
|
21
|
+
else
|
22
|
+
yield rule if rule
|
23
|
+
rule = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
case line
|
27
|
+
when /\A([[:alpha:]_.][[:alnum:]_.-]*)\s*=\s*(.*)$/
|
28
|
+
yield Makefile::Macro.new($1, $2)
|
29
|
+
when /^(\.[^.]+)(\.[^.]+)?:$/
|
30
|
+
rule = Makefile::SuffixRule.new($1, $2)
|
31
|
+
when /^(.+):(.*)$/
|
32
|
+
rule = Makefile::Target.new($1, raw_deps: $2.strip)
|
33
|
+
else
|
34
|
+
raise NotImplementedError, "Unrecognized line #{line.dump} at #{lineno}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
yield rule if rule
|
38
|
+
end
|
39
|
+
|
40
|
+
def read
|
41
|
+
to_a
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def read_line
|
46
|
+
return nil if @input.eof?
|
47
|
+
line = ""
|
48
|
+
begin
|
49
|
+
fragment = @input.readline
|
50
|
+
fragment = fragment.sub(/#.*$/, '')
|
51
|
+
line << fragment
|
52
|
+
end while !@input.eof? and line.sub!(/\\\r?\n?/, ' ')
|
53
|
+
return line
|
54
|
+
end
|
55
|
+
|
56
|
+
def lineno
|
57
|
+
@input.lineno
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Makefile; end
|
2
|
+
|
3
|
+
class Makefile::SuffixRule
|
4
|
+
def initialize(source, target)
|
5
|
+
@source = source
|
6
|
+
@target = target
|
7
|
+
@commands = []
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :source, :target, :commands
|
11
|
+
|
12
|
+
def add_command(command)
|
13
|
+
@commands << command
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Makefile
|
2
|
+
class Target
|
3
|
+
def initialize(name, raw_deps: nil, commands: [])
|
4
|
+
@name = name
|
5
|
+
if raw_deps
|
6
|
+
@raw_deps = [raw_deps]
|
7
|
+
@deps = [Expression.new(raw_deps)]
|
8
|
+
else
|
9
|
+
@raw_deps, @deps = [], []
|
10
|
+
end
|
11
|
+
@commands = commands
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :name, :raw_deps, :commands
|
15
|
+
|
16
|
+
def deps(macroset)
|
17
|
+
@deps.map {|expr| expr.evaluate(macroset).split(/\s+/) }.flatten
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_dependency(raw_deps)
|
21
|
+
@raw_deps << raw_deps
|
22
|
+
@deps << Expression.new(raw_deps)
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_command(command)
|
26
|
+
@commands << command
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/makefile.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'makefile/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "makefile"
|
8
|
+
spec.version = Makefile::VERSION
|
9
|
+
spec.authors = ["Yuki Yugui Sonoda"]
|
10
|
+
spec.email = ["yugui@yugui.jp"]
|
11
|
+
spec.description = %q{Makefile parser}
|
12
|
+
spec.summary = %q{Makefile parser}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 2.1"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "rr"
|
25
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
require 'makefile'
|
3
|
+
|
4
|
+
describe Makefile::Command do
|
5
|
+
describe '#argv' do
|
6
|
+
it "spilts shell tokens" do
|
7
|
+
cmd = Makefile::Command.new("ls -l spec/command_spec.rb")
|
8
|
+
target = double('target')
|
9
|
+
argv = cmd.argv(target, {})
|
10
|
+
|
11
|
+
expect(argv).to eq(%w[ ls -l spec/command_spec.rb ])
|
12
|
+
end
|
13
|
+
|
14
|
+
it "evaluates macro before split " do
|
15
|
+
cmd = Makefile::Command.new("${A}$(B) C$(D)")
|
16
|
+
target = double('target')
|
17
|
+
argv = cmd.argv(
|
18
|
+
target,
|
19
|
+
'A' => Makefile::Macro.new('A', 'a '),
|
20
|
+
'B' => Makefile::Macro.new('B', 'b '),
|
21
|
+
'D' => Makefile::Macro.new('D', 'd '),
|
22
|
+
)
|
23
|
+
|
24
|
+
expect(argv).to eq(%w[ a b Cd ])
|
25
|
+
end
|
26
|
+
|
27
|
+
it "propagates target to macro expansion" do
|
28
|
+
cmd = Makefile::Command.new("${A}")
|
29
|
+
target = double('target')
|
30
|
+
expect(cmd.cmd).to receive(:evaluate).with(target, anything).and_call_original
|
31
|
+
|
32
|
+
cmd.argv(
|
33
|
+
target,
|
34
|
+
'A' => Makefile::Macro.new('A') { 'a' },
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "ignores silence marker" do
|
39
|
+
cmd = Makefile::Command.new("$(Q)echo 1")
|
40
|
+
argv = cmd.argv(
|
41
|
+
double('target'),
|
42
|
+
'Q' => Makefile::Macro.new('Q', '@'),
|
43
|
+
)
|
44
|
+
|
45
|
+
expect(argv).to eq(%w[ echo 1 ])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
require 'makefile'
|
3
|
+
|
4
|
+
describe Makefile::Expression do
|
5
|
+
describe '#evaluate' do
|
6
|
+
it 'returns literal expression as is' do
|
7
|
+
expr = Makefile::Expression.new('abcde fghij')
|
8
|
+
target = double('target')
|
9
|
+
macroset = double('macroset')
|
10
|
+
|
11
|
+
result = expr.evaluate(target, macroset)
|
12
|
+
|
13
|
+
expect(result).to eq('abcde fghij')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'expands single letter macros' do
|
17
|
+
expr = Makefile::Expression.new('A$BCDE $F $G')
|
18
|
+
result = expr.evaluate(
|
19
|
+
double('target'),
|
20
|
+
'B' => Makefile::Macro.new('B', 'b'),
|
21
|
+
'F' => Makefile::Macro.new('F', 'f'),
|
22
|
+
'G' => Makefile::Macro.new('G', 'g'),
|
23
|
+
)
|
24
|
+
|
25
|
+
expect(result).to eq('AbCDE f g')
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'expands parenthesized macros' do
|
29
|
+
expr = Makefile::Expression.new('A$(BCD)E $(FG)')
|
30
|
+
result = expr.evaluate(
|
31
|
+
double('target'),
|
32
|
+
'B' => Makefile::Macro.new('B', 'b'),
|
33
|
+
'BCD' => Makefile::Macro.new('BCD', '123'),
|
34
|
+
'FG' => Makefile::Macro.new('FG', '45'),
|
35
|
+
)
|
36
|
+
|
37
|
+
expect(result).to eq('A123E 45')
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'expands braced macros' do
|
41
|
+
expr = Makefile::Expression.new('A${BCD}E ${FG}')
|
42
|
+
result = expr.evaluate(
|
43
|
+
double('target'),
|
44
|
+
'B' => Makefile::Macro.new('B', 'b'),
|
45
|
+
'BCD' => Makefile::Macro.new('BCD', '123'),
|
46
|
+
'FG' => Makefile::Macro.new('FG', '45'),
|
47
|
+
)
|
48
|
+
|
49
|
+
expect(result).to eq('A123E 45')
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'expands undefined macros into blank' do
|
53
|
+
expr = Makefile::Expression.new('A$BCD$(EF)G${HI}')
|
54
|
+
result = expr.evaluate(double('target'), {})
|
55
|
+
|
56
|
+
expect(result).to eq('ACDG')
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'raises an exception on self-recursion' do
|
60
|
+
expr = Makefile::Expression.new('$A')
|
61
|
+
expect {
|
62
|
+
expr.evaluate(
|
63
|
+
double('target'),
|
64
|
+
'A' => Makefile::Macro.new('A', '_${A}'),
|
65
|
+
)
|
66
|
+
}.to raise_error(Makefile::Error)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'raises an exception on mutual recursion' do
|
70
|
+
expr = Makefile::Expression.new('$A')
|
71
|
+
expect {
|
72
|
+
expr.evaluate(
|
73
|
+
double('target'),
|
74
|
+
'A' => Makefile::Macro.new('A', '_${B}'),
|
75
|
+
'B' => Makefile::Macro.new('B', '-${A}'),
|
76
|
+
)
|
77
|
+
}.to raise_error(Makefile::Error)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'expands $$ to $' do
|
81
|
+
expr = Makefile::Expression.new('$${A}')
|
82
|
+
result = expr.evaluate(
|
83
|
+
double('target'),
|
84
|
+
'A' => 'a',
|
85
|
+
'$' => Makefile::Macro.new('$', '$', allow_quoted: false),
|
86
|
+
)
|
87
|
+
|
88
|
+
expect(result).to eq('${A}')
|
89
|
+
end
|
90
|
+
|
91
|
+
%w[ $($) ${$} ].each do |ref|
|
92
|
+
it "does not expand #{ref} with the default rule" do
|
93
|
+
expr = Makefile::Expression.new(ref)
|
94
|
+
result = expr.evaluate(
|
95
|
+
double('target'),
|
96
|
+
'$' => Makefile::Macro.new('$', '$', allow_quoted: false),
|
97
|
+
)
|
98
|
+
|
99
|
+
expect(result).to eq("")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'expands only once' do
|
104
|
+
expr = Makefile::Expression.new('$T')
|
105
|
+
result = expr.evaluate(
|
106
|
+
double('target'),
|
107
|
+
'M' => Makefile::Macro.new('M', '$$'),
|
108
|
+
'N' => Makefile::Macro.new('N', '(S)'),
|
109
|
+
'S' => Makefile::Macro.new('S', '1'),
|
110
|
+
'T' => Makefile::Macro.new('T', '$(M)$(N)'),
|
111
|
+
'$' => Makefile::Macro.new('$', '$', allow_quoted: false),
|
112
|
+
)
|
113
|
+
|
114
|
+
expect(result).to eq('$(S)')
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'allows hook variables' do
|
118
|
+
expr = Makefile::Expression.new('$A')
|
119
|
+
target = double('target')
|
120
|
+
called = false
|
121
|
+
macroset = {
|
122
|
+
'A' => Makefile::Macro.new('A') do |t, m|
|
123
|
+
called = true
|
124
|
+
expect(t).to equal(target)
|
125
|
+
expect(m).to eq(macroset)
|
126
|
+
|
127
|
+
'abcde'
|
128
|
+
end
|
129
|
+
}
|
130
|
+
result = expr.evaluate(target, macroset)
|
131
|
+
|
132
|
+
expect(called).to be_truthy
|
133
|
+
expect(result).to eq('abcde')
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'substitutes on macro expansion' do
|
137
|
+
expr = Makefile::Expression.new('$A')
|
138
|
+
result = expr.evaluate(
|
139
|
+
double('target'),
|
140
|
+
'A' => Makefile::Macro.new('A', '$(B:.S=.o)'),
|
141
|
+
'B' => Makefile::Macro.new('B', '$(C:.c=.S)'),
|
142
|
+
'C' => Makefile::Macro.new('C', "foo.c bar.c\tbaz.c\vqux.c"),
|
143
|
+
)
|
144
|
+
|
145
|
+
expect(result).to eq("foo.o bar.o\tbaz.o\vqux.o")
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'evaluates replacement text on expansion' do
|
149
|
+
expr = Makefile::Expression.new('$A')
|
150
|
+
result = expr.evaluate(
|
151
|
+
double('target'),
|
152
|
+
'A' => Makefile::Macro.new('A', '$(B:.c=$S)'),
|
153
|
+
'B' => Makefile::Macro.new('B', "foo.c bar.c\tbaz.c\vqux.c"),
|
154
|
+
'S' => Makefile::Macro.new('S', '${T}'),
|
155
|
+
'T' => Makefile::Macro.new('T', '.o'),
|
156
|
+
)
|
157
|
+
|
158
|
+
expect(result).to eq("foo.o bar.o\tbaz.o\vqux.o")
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'replace patterns only before whitespace' do
|
162
|
+
expr = Makefile::Expression.new('$(A:abc=123)')
|
163
|
+
result = expr.evaluate(
|
164
|
+
double('target'),
|
165
|
+
'$' => Makefile::Macro.new('$', '$', allow_quoted: false),
|
166
|
+
'A' => Makefile::Macro.new('A', "abc.abc abc-abc\tabc$$abc\vabc")
|
167
|
+
)
|
168
|
+
expect(result).to eq("abc.123 abc-123\tabc$123\v123")
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
data/spec/reader_spec.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
require 'makefile'
|
3
|
+
|
4
|
+
describe Makefile::Reader do
|
5
|
+
def create_input_stub(lines)
|
6
|
+
lines = lines.dup
|
7
|
+
input = double('input')
|
8
|
+
allow(input).to receive(:eof?) { lines.empty? }
|
9
|
+
allow(input).to receive(:readline) { lines.shift }
|
10
|
+
input
|
11
|
+
end
|
12
|
+
|
13
|
+
it "reads input with #read_line" do
|
14
|
+
eof = false
|
15
|
+
input = double('input')
|
16
|
+
allow(input).to receive(:eof?) { eof }
|
17
|
+
expect(input).to receive(:readline) { "a=b\n" }
|
18
|
+
expect(input).to receive(:readline) { eof = true; "b=c" }
|
19
|
+
|
20
|
+
Makefile::Reader.new(input).read
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#each" do
|
24
|
+
it "returns a Macro object on read a macro" do
|
25
|
+
input = create_input_stub(<<-EOF.lines)
|
26
|
+
MACRO1=1
|
27
|
+
EOF
|
28
|
+
|
29
|
+
macro, = *Makefile::Reader.new(input).read
|
30
|
+
expect(macro).to be_an_instance_of(Makefile::Macro)
|
31
|
+
expect(macro.name).to eq("MACRO1")
|
32
|
+
expect(macro.raw_value).to eq("1")
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'ignores whitespaces around macro assignment' do
|
36
|
+
input = create_input_stub(<<-EOF.lines)
|
37
|
+
MACRO1 =1
|
38
|
+
MACRO2= 2
|
39
|
+
MACRO3 = 3
|
40
|
+
MACRO4 \t\v=\t\v 4
|
41
|
+
EOF
|
42
|
+
|
43
|
+
macros = Makefile::Reader.new(input).read
|
44
|
+
expect(macros).to all(be_an_instance_of(Makefile::Macro))
|
45
|
+
expect(macros.size).to be(4)
|
46
|
+
|
47
|
+
macros.each.with_index do |macro, i|
|
48
|
+
expect(macro.name).to eq("MACRO#{i+1}")
|
49
|
+
expect(macro.raw_value).to eq("#{i+1}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "reads multiline macro" do
|
54
|
+
input = create_input_stub(<<-EOF.lines)
|
55
|
+
MACRO1=1 \\
|
56
|
+
\t2 \\
|
57
|
+
\v3 \\
|
58
|
+
4
|
59
|
+
EOF
|
60
|
+
|
61
|
+
macro, = *Makefile::Reader.new(input).read
|
62
|
+
expect(macro).to be_an_instance_of(Makefile::Macro)
|
63
|
+
expect(macro.name).to eq("MACRO1")
|
64
|
+
expect(macro.raw_value).to eq("1 \t2 \v3 4")
|
65
|
+
end
|
66
|
+
|
67
|
+
it "returns a SuffixRule object on read a rule definition" do
|
68
|
+
input = create_input_stub(<<-EOF.lines)
|
69
|
+
.c.o:
|
70
|
+
$(CC) -c -o $@ $<
|
71
|
+
EOF
|
72
|
+
|
73
|
+
rule, = *Makefile::Reader.new(input).read
|
74
|
+
expect(rule).to be_an_instance_of(Makefile::SuffixRule)
|
75
|
+
expect(rule.source).to eq(".c")
|
76
|
+
expect(rule.target).to eq(".o")
|
77
|
+
expect(rule.commands.size).to eq(1)
|
78
|
+
expect(rule.commands[0]).to eq(
|
79
|
+
Makefile::Command.new("$(CC) -c -o $@ $<\n"))
|
80
|
+
end
|
81
|
+
|
82
|
+
it "reads multiline command" do
|
83
|
+
input = create_input_stub(<<-EOF.lines)
|
84
|
+
.c.o:
|
85
|
+
\t$(ECHO) \\
|
86
|
+
1 \\
|
87
|
+
\t2 \\
|
88
|
+
\v3
|
89
|
+
EOF
|
90
|
+
|
91
|
+
rule, = *Makefile::Reader.new(input).read
|
92
|
+
expect(rule.commands.size).to eq(1)
|
93
|
+
expect(rule.commands[0]).to eq(
|
94
|
+
Makefile::Command.new("$(ECHO) 1 \t2 \v3\n"))
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'returns a Target object on reading a target definition' do
|
98
|
+
input = create_input_stub(<<-EOF.lines)
|
99
|
+
foo: bar$(EXT) baz
|
100
|
+
$(CC) -c -o foo bar baz
|
101
|
+
EOF
|
102
|
+
|
103
|
+
target, = *Makefile::Reader.new(input).read
|
104
|
+
expect(target).to be_an_instance_of(Makefile::Target)
|
105
|
+
expect(target.name).to eq('foo')
|
106
|
+
expect(target.raw_deps).to eq(['bar$(EXT) baz'])
|
107
|
+
expect(target.commands.size).to eq(1)
|
108
|
+
expect(target.commands[0]).to eq(
|
109
|
+
Makefile::Command.new("$(CC) -c -o foo bar baz\n"))
|
110
|
+
end
|
111
|
+
|
112
|
+
it "skips comments" do
|
113
|
+
input = create_input_stub(<<-EOF.lines)
|
114
|
+
MACRO1=1# test
|
115
|
+
# MACRO2=2
|
116
|
+
EOF
|
117
|
+
|
118
|
+
iter = Makefile::Reader.new(input).enum_for(:each)
|
119
|
+
macro = iter.next
|
120
|
+
expect(macro).to be_an_instance_of(Makefile::Macro)
|
121
|
+
expect(macro.name).to eq("MACRO1")
|
122
|
+
expect(macro.raw_value).to eq("1")
|
123
|
+
expect { iter.next }.to raise_error(StopIteration)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'rspec'
|
metadata
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: makefile
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.pre
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yuki Yugui Sonoda
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-11-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.1'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rr
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Makefile parser
|
70
|
+
email:
|
71
|
+
- yugui@yugui.jp
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".travis.yml"
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- lib/makefile.rb
|
83
|
+
- lib/makefile/command.rb
|
84
|
+
- lib/makefile/errors.rb
|
85
|
+
- lib/makefile/expression.rb
|
86
|
+
- lib/makefile/macro.rb
|
87
|
+
- lib/makefile/reader.rb
|
88
|
+
- lib/makefile/suffix_rule.rb
|
89
|
+
- lib/makefile/target.rb
|
90
|
+
- lib/makefile/version.rb
|
91
|
+
- makefile.gemspec
|
92
|
+
- spec/command_spec.rb
|
93
|
+
- spec/expression_spec.rb
|
94
|
+
- spec/reader_spec.rb
|
95
|
+
- spec/spec_helper.rb
|
96
|
+
homepage: ''
|
97
|
+
licenses:
|
98
|
+
- MIT
|
99
|
+
metadata: {}
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">"
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: 1.3.1
|
114
|
+
requirements: []
|
115
|
+
rubygems_version: 3.1.2
|
116
|
+
signing_key:
|
117
|
+
specification_version: 4
|
118
|
+
summary: Makefile parser
|
119
|
+
test_files:
|
120
|
+
- spec/command_spec.rb
|
121
|
+
- spec/expression_spec.rb
|
122
|
+
- spec/reader_spec.rb
|
123
|
+
- spec/spec_helper.rb
|