rmk-ruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE +674 -0
- data/README.md +37 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/rmk +74 -0
- data/lib/rmk/build.rb +238 -0
- data/lib/rmk/rmk.rb +197 -0
- data/lib/rmk/rule.rb +30 -0
- data/lib/rmk/schedule.rb +15 -0
- data/lib/rmk/storage.rb +30 -0
- data/lib/rmk/vars.rb +70 -0
- data/lib/rmk/vdir.rb +502 -0
- data/lib/rmk/version.rb +3 -0
- data/lib/rmk/vfile.rb +84 -0
- data/lib/rmk.rb +4 -0
- data/rmk.gemspec +35 -0
- metadata +93 -0
data/README.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# Rmk
|
2
|
+
|
3
|
+
A build tool like make, tup, ninja.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'rmk'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install rmk
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
TODO: Write usage instructions here
|
24
|
+
|
25
|
+
## Development
|
26
|
+
|
27
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
28
|
+
|
29
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
30
|
+
|
31
|
+
## Contributing
|
32
|
+
|
33
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/pbl-pw/rmk. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
34
|
+
|
35
|
+
## Code of Conduct
|
36
|
+
|
37
|
+
Everyone interacting in the Rmk project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/rmk/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "rmk"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/rmk
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'iniparse'
|
5
|
+
require "rmk"
|
6
|
+
|
7
|
+
options = {}
|
8
|
+
parser = OptionParser.new{ |opts|
|
9
|
+
opts.summary_width = 48
|
10
|
+
opts.banner = 'Usage: rmk [Options] [targets]'
|
11
|
+
opts.separator ''
|
12
|
+
opts.on '-O', '--outdir=dir', 'output root dir,can be absolute or relative to pwd,default _Build' do |dir|
|
13
|
+
options[:outroot] = dir
|
14
|
+
end
|
15
|
+
opts.on '-S', '--srcdir=dir', 'source root dir,can be absolute or relative to output root(start with ..),default ..' do |dir|
|
16
|
+
options[:srcroot] = dir
|
17
|
+
end
|
18
|
+
opts.on '-V', '--variant [x,y,z]', Array, 'variant list to build, empty for build all variants' do |list|
|
19
|
+
options[:variants] = list || []
|
20
|
+
end
|
21
|
+
opts.on '-h', '--help', 'show this help' do
|
22
|
+
puts opts
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
opts.on '-v', '--version', 'show version' do
|
26
|
+
puts 'rmk 0.2.0', ''
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
}
|
30
|
+
targets = parser.parse(ARGV)
|
31
|
+
if options[:variants]
|
32
|
+
raise "variants config file 'variants.rmk' not exist" unless File.exist? 'variants.rmk'
|
33
|
+
inifile = IniParse.open 'variants.rmk'
|
34
|
+
targets = targets.map{|tgt| %|"#{tgt}"|}.join ' '
|
35
|
+
exe = File.join RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']
|
36
|
+
thrs = []
|
37
|
+
(options[:variants].empty? ? inifile : options[:variants].map!{|vn|
|
38
|
+
variant = inifile[vn]
|
39
|
+
warn "variant '#{vn}' doesn't exist" unless variant
|
40
|
+
variant
|
41
|
+
}).each do |variant|
|
42
|
+
next unless variant
|
43
|
+
thrs << Thread.new{ system %|"#{exe}" "#{__FILE__}" -O"#{variant['outdir']}" -S"#{variant['srcdir']}" #{targets}|}
|
44
|
+
end
|
45
|
+
thrs.each{|thr| thr.join}
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
if File.exist? 'ini.rmk'
|
49
|
+
inifile = IniParse.open 'ini.rmk'
|
50
|
+
dir, link = inifile['Outdir']['Path'], inifile['Outdir']['LinkTo']
|
51
|
+
if link
|
52
|
+
require 'fileutils'
|
53
|
+
link = link.gsub(/\$(?:(\$)|{(.*?)})/){$1 || ENV[$2]}.gsub! ?\/, ?\\
|
54
|
+
FileUtils.mkpath link unless Dir.exist? link
|
55
|
+
if File.symlink? dir
|
56
|
+
if File.readlink(dir) != link
|
57
|
+
Dir.rmdir dir
|
58
|
+
File.symlink link, dir
|
59
|
+
end
|
60
|
+
elsif Dir.exist?(dir) || File.exist?(dir)
|
61
|
+
raise "need symlink '#{dir}' as outroot, but file(or dir) '#{dir}' already exist"
|
62
|
+
else
|
63
|
+
FileUtils.mkpath File.dirname dir
|
64
|
+
File.symlink link, dir
|
65
|
+
end
|
66
|
+
else
|
67
|
+
FileUtils.mkpath dir
|
68
|
+
end
|
69
|
+
options[:srcroot] ||= inifile['Srcdir']['Path']
|
70
|
+
options[:outroot] ||= dir
|
71
|
+
end
|
72
|
+
rmk = Rmk.new srcroot:options[:srcroot] || '..', outroot:options[:outroot] || '_Build'
|
73
|
+
rmk.parse
|
74
|
+
rmk.build *targets
|
data/lib/rmk/build.rb
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
require_relative 'rmk'
|
2
|
+
require_relative 'vdir'
|
3
|
+
require_relative 'vfile'
|
4
|
+
require 'open3'
|
5
|
+
require 'digest'
|
6
|
+
|
7
|
+
class Rmk::Build
|
8
|
+
attr_reader :dir, :vars
|
9
|
+
attr_reader :infiles, :depfiles, :orderfiles, :outfiles
|
10
|
+
|
11
|
+
# create Build
|
12
|
+
# @param dir [Rmk::VDir] build's dir
|
13
|
+
# @param rule [Rmk::Rule] build's rule
|
14
|
+
# @param vars [Rmk::Vars] upstream vars
|
15
|
+
# @param input [Array<Rmk::VFile>] input files
|
16
|
+
# @param implicit_input [String, nil] implicit input raw string
|
17
|
+
# @param order_only_input [String, nil] order-only input raw string
|
18
|
+
# @param output [String, nil] output raw string
|
19
|
+
# @param implicit_output [String, nil] implicit output raw string
|
20
|
+
# @param collection [String, nil] collection name
|
21
|
+
def initialize(dir, rule, vars, input, implicit_input, order_only_input, output, implicit_output, collection)
|
22
|
+
@mutex = Thread::Mutex.new
|
23
|
+
@updatedcnt = 0 # input file updated count
|
24
|
+
@runed = false # build has been runed
|
25
|
+
@input_modified = false # input file has modified
|
26
|
+
|
27
|
+
@dir = dir
|
28
|
+
@command = rule.command
|
29
|
+
@vars = vars.downstream_new clone_rmk:true
|
30
|
+
rmk_vars = @vars.rmk
|
31
|
+
@infiles = input
|
32
|
+
|
33
|
+
if @infiles.size == 1
|
34
|
+
if FFile === @infiles[0]
|
35
|
+
vname = @infiles[0].vname
|
36
|
+
if vname
|
37
|
+
match = /^((?:[^\/]+\/)*)([^\/]*)$/.match vname
|
38
|
+
rmk_vars['in_dir'], rmk_vars['in_nodir'] = match[1], match[2]
|
39
|
+
match = /^(.*)\.(.*)$/.match match[2]
|
40
|
+
rmk_vars['in_base'], rmk_vars['in_ext'] = match[1], match[2]
|
41
|
+
rmk_vars['in_noext'] = rmk_vars['in_dir'] + rmk_vars['in_base']
|
42
|
+
end
|
43
|
+
rmk_vars['stem'] = @infiles[0].stem if @infiles[0].stem
|
44
|
+
@infiles[0] = @infiles[0].vfile
|
45
|
+
end
|
46
|
+
end
|
47
|
+
rmk_vars['in'] = @infiles.map do |file|
|
48
|
+
file.input_ref_builds << self
|
49
|
+
next file.vpath || file.path unless file.src?
|
50
|
+
file.vpath ? @dir.rmk.join_rto_src_path(file.vpath) : file.path
|
51
|
+
end.join ' '
|
52
|
+
|
53
|
+
@vars.split_str(implicit_input).each do |fn|
|
54
|
+
files = @dir.find_inputfiles fn
|
55
|
+
raise "pattern '#{fn}' not match any file" if files.empty?
|
56
|
+
files.each{|f| f.input_ref_builds << self}
|
57
|
+
end if implicit_input
|
58
|
+
|
59
|
+
@orderfiles = []
|
60
|
+
@vars.split_str(order_only_input).each do |fn|
|
61
|
+
files = @dir.find_inputfiles fn
|
62
|
+
raise "pattern '#{fn}' not match any file" if files.empty?
|
63
|
+
files.each{|f| f.order_ref_builds << self}
|
64
|
+
end if order_only_input
|
65
|
+
raise 'no found any input file' if @infiles.empty? && @orderfiles.empty?
|
66
|
+
|
67
|
+
@outfiles = []
|
68
|
+
regout = proc do |fn|
|
69
|
+
file = @dir.add_out_file fn
|
70
|
+
file.output_ref_build = self
|
71
|
+
@outfiles << file
|
72
|
+
end
|
73
|
+
output = rule['out'] || raise('must have output') unless output
|
74
|
+
@vars.split_str(output).each ®out
|
75
|
+
collection.each{|col| col.concat @outfiles} if collection
|
76
|
+
rmk_vars['out'] = @outfiles.map {|file| file.vpath || file.path}.join ' '
|
77
|
+
rmk_vars['out_noext'] = rmk_vars['out'][/^(.*)\..*$/, 1] if @outfiles.size == 1
|
78
|
+
rule.apply_to @vars # interpolate rule's vars to self
|
79
|
+
@vars.split_str(implicit_output).each ®out if implicit_output
|
80
|
+
rmk_vars.freeze
|
81
|
+
@depfiles = []
|
82
|
+
@outfiles.each do |file|
|
83
|
+
next unless (fns = @dir.rmk.dep_storage.data![file.path])
|
84
|
+
fns.each {|fn| @dir.rmk.find_inputfiles(fn).each {|f| f.input_ref_builds << self; @depfiles << f}}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def input_updated!(modified, order:false)
|
89
|
+
Rmk::Schedule.new_thread! &method(:run) if @mutex.synchronize do
|
90
|
+
next @runed = :checkskip if @runed == :force
|
91
|
+
next if @runed
|
92
|
+
@updatedcnt += 1
|
93
|
+
@input_modified ||= order ? modified == :create : modified
|
94
|
+
needrun = @updatedcnt >= @infiles.size + @depfiles.size + @orderfiles.size
|
95
|
+
@runed = true if needrun
|
96
|
+
needrun
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def order_updated!(modified) input_updated! modified, order:true end
|
101
|
+
|
102
|
+
private def get_command
|
103
|
+
cmd = @vars.interpolate_str @vars['command'] || @command
|
104
|
+
digest = Digest::SHA1.new
|
105
|
+
digest << cmd
|
106
|
+
fdproc = proc{|f| digest << f.vpath || f.path}
|
107
|
+
@infiles.each &fdproc
|
108
|
+
@orderfiles.each &fdproc
|
109
|
+
@depfiles.each {|f| digest << f.path}
|
110
|
+
digest = digest.hexdigest
|
111
|
+
storage = @dir.rmk.cml_storage
|
112
|
+
[cmd, storage.data![@outfiles[0].path] != digest]
|
113
|
+
end
|
114
|
+
|
115
|
+
private def save_digest(fns)
|
116
|
+
digest = Digest::SHA1.new
|
117
|
+
digest << @command
|
118
|
+
fdproc = proc{|f| digest << f.vpath || f.path}
|
119
|
+
@infiles.each &fdproc
|
120
|
+
@orderfiles.each &fdproc
|
121
|
+
fns.each {|f| digest << f} if fns
|
122
|
+
digest = digest.hexdigest
|
123
|
+
storage = @dir.rmk.cml_storage
|
124
|
+
storage.sync{|data| data[@outfiles[0].path] = digest}
|
125
|
+
end
|
126
|
+
|
127
|
+
@output_mutex = Thread::Mutex.new
|
128
|
+
def self.puts(*parms) @output_mutex.synchronize{$stdout.puts *parms} end
|
129
|
+
def self.err_puts(*parms) @output_mutex.synchronize{$stderr.puts *parms} end
|
130
|
+
def self.log_cmd_out(out, err)
|
131
|
+
@output_mutex.synchronize do
|
132
|
+
$stdout.puts out unless out.empty?
|
133
|
+
$stderr.puts err unless err.empty?
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def parser_force_run!
|
138
|
+
return if @runed
|
139
|
+
@runed = :force
|
140
|
+
exec = nil
|
141
|
+
@outfiles.each{|file| file.state = File.exist?(file.path) ? :exist : (exec = :create)}
|
142
|
+
@command, changed = get_command
|
143
|
+
unless changed || exec
|
144
|
+
inproc = proc do |file|
|
145
|
+
next file.check_for_parse if file.src?
|
146
|
+
state = file.state
|
147
|
+
raise 'output file not updated when ref as config file build input' unless state
|
148
|
+
state != :exist
|
149
|
+
end
|
150
|
+
exec = @infiles.any? &inproc
|
151
|
+
exec ||= @depfiles.any? &inproc
|
152
|
+
return unless @orderfiles.any? do |file|
|
153
|
+
next if file.src?
|
154
|
+
state = file.state
|
155
|
+
raise 'output file not updated when ref as config file build input' unless state
|
156
|
+
state == :create
|
157
|
+
end unless exec
|
158
|
+
end
|
159
|
+
raise 'config file build fail' unless raw_exec @command
|
160
|
+
@outfiles.each{|file| file.state = :update if file.state == :exist}
|
161
|
+
end
|
162
|
+
|
163
|
+
private def run
|
164
|
+
if @runed == :checkskip
|
165
|
+
@outfiles.each {|file| file.updated! file.state != :exist && file.state}
|
166
|
+
save_digest process_depfile unless @outfiles[0].state == :exist
|
167
|
+
else
|
168
|
+
exec = nil
|
169
|
+
@outfiles.each{|file| file.state = File.exist?(file.path) ? :update : (exec = :create)}
|
170
|
+
@command, changed = get_command
|
171
|
+
return @outfiles.each{|file| file.updated! false} unless changed || @input_modified || exec
|
172
|
+
return unless raw_exec @command
|
173
|
+
@outfiles.each {|file| file.updated! file.state}
|
174
|
+
save_digest process_depfile
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
private def raw_exec(cmd)
|
179
|
+
@vars['depfile'] ||= (@outfiles[0].vpath || @outfiles[0].path) + '.dep' if @vars['deptype']
|
180
|
+
unless /^\s*$/.match? cmd
|
181
|
+
env = {}
|
182
|
+
env['PATH'] = @vars['PATH_prepend'] + ENV['PATH'] if @vars['PATH_prepend']
|
183
|
+
@vars['ENV_export'].split(/\s+/).each{|name| env[name] = @vars[name] if @vars.include? name} if @vars['ENV_export']
|
184
|
+
std, err, result = env.empty? ? Open3.capture3(cmd) : Open3.capture3(env, cmd)
|
185
|
+
std = std.empty? ? @vars['echo'] || "Rmk: run: #{cmd}" : (@vars['echo'] || "Rmk: run: #{cmd}") + ?\n + std
|
186
|
+
if result.exitstatus != 0
|
187
|
+
err = "execute faild: '#{cmd}'" if err.empty?
|
188
|
+
Rmk::Build.log_cmd_out std, err
|
189
|
+
@outfiles.each{|file| File.delete file.path if File.exist? file.path}
|
190
|
+
return false
|
191
|
+
end
|
192
|
+
Rmk::Build.log_cmd_out std, err
|
193
|
+
end
|
194
|
+
true
|
195
|
+
end
|
196
|
+
|
197
|
+
private def process_depfile
|
198
|
+
return unless @vars['deptype']
|
199
|
+
unless File.exist? @vars['depfile']
|
200
|
+
Rmk::Build.err_puts "Rmk: depend file '#{@vars['depfile']}' which must be created by build '#{@vars['out']}' not found"
|
201
|
+
end
|
202
|
+
if @vars['deptype'] == 'make'
|
203
|
+
files = parse_make_depfile @vars['depfile']
|
204
|
+
File.delete @vars['depfile']
|
205
|
+
return Rmk::Build.err_puts "Rmk: syntax of depend file '#{@vars['depfile']}' not support yet" unless files
|
206
|
+
@dir.rmk.dep_storage[@outfiles[0].path] = files
|
207
|
+
files.each do |file|
|
208
|
+
file = File.absolute_path Rmk.normalize_path(file), @dir.rmk.outroot
|
209
|
+
next if @dir.rmk.srcfiles.include?(file) || @dir.rmk.outfiles.include?(file)
|
210
|
+
@dir.rmk.mid_storage.sync{|data| data[file] ||= Rmk::VFile.generate_modified_id(file)}
|
211
|
+
end
|
212
|
+
files
|
213
|
+
else
|
214
|
+
Rmk::Build.err_puts "Rmk: depend type '#{@vars['deptype']}' not support"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def parse_make_depfile(path)
|
219
|
+
lines = IO.readlines path
|
220
|
+
line, lid = lines[0], 0
|
221
|
+
_, _, line = line.partition /(?<!\\)(?:\\\\)*\K:\s+/
|
222
|
+
return unless line
|
223
|
+
files = []
|
224
|
+
while lid < lines.size
|
225
|
+
joinline = line.sub! /(?<!\\)(?:\\\\)*\K\\\n\z/,''
|
226
|
+
parms = line.split /(?<!\\)(?:\\\\)*\K\s+/
|
227
|
+
unless parms.empty?
|
228
|
+
parms.delete_at 0 if parms[0].empty?
|
229
|
+
parms.map!{|parm| File.absolute_path parm.gsub(/\\(.)/, '\1')}
|
230
|
+
files.concat parms
|
231
|
+
end
|
232
|
+
break unless joinline
|
233
|
+
lid += 1
|
234
|
+
line = lines[lid]
|
235
|
+
end
|
236
|
+
files
|
237
|
+
end
|
238
|
+
end
|
data/lib/rmk/rmk.rb
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'rmk/version'
|
2
|
+
require_relative 'vars'
|
3
|
+
require_relative 'storage'
|
4
|
+
|
5
|
+
class Rmk
|
6
|
+
# normalize path, drive letter upcase and path seperator set to '/'
|
7
|
+
# @param path [String]
|
8
|
+
# @return [String]
|
9
|
+
def self.normalize_path(path) path.gsub(?\\, ?/).sub(/^[a-z](?=:)/){|ch|ch.upcase} end
|
10
|
+
|
11
|
+
# create Rmk object
|
12
|
+
# @param srcroot [String] source root dir,can be absolute or relative to output root(start with ..)
|
13
|
+
# @param outroot [String] output root dir,can be absolute or relative to pwd,default pwd
|
14
|
+
def initialize(srcroot:'', outroot:'')
|
15
|
+
@files_mutex = Thread::Mutex.new # files operate mutex
|
16
|
+
|
17
|
+
srcroot = Rmk.normalize_path srcroot
|
18
|
+
@outroot = File.join Rmk.normalize_path(File.absolute_path outroot), ''
|
19
|
+
@srcroot = File.join File.absolute_path(srcroot, @outroot), ''
|
20
|
+
raise "source path '#{@srcroot}' not exist or not directory" unless ::Dir.exist?(@srcroot)
|
21
|
+
warn 'in-source build' if @outroot == @srcroot
|
22
|
+
@src_relative = srcroot.match?(/^\.\./) && File.join(srcroot, '')
|
23
|
+
Dir.mkdir @outroot unless Dir.exist? @outroot
|
24
|
+
Dir.chdir @outroot
|
25
|
+
Dir.mkdir '.rmk' unless Dir.exist? '.rmk'
|
26
|
+
@mid_storage = Rmk::Storage.new '.rmk/mid', {}
|
27
|
+
@dep_storage = Rmk::Storage.new '.rmk/dep', {}
|
28
|
+
@cml_storage = Rmk::Storage.new '.rmk/cml', {} # command line text storage
|
29
|
+
@srcfiles = {}
|
30
|
+
@outfiles = {}
|
31
|
+
@defaultfiles = []
|
32
|
+
@vars = {'srcroot'=>@srcroot[0..-2], 'outroot'=>@outroot[0..-2], 'src_rto_root'=>(@src_relative || @srcroot)[0..-2]}.freeze
|
33
|
+
@virtual_root = Rmk::VDir.new self, nil
|
34
|
+
end
|
35
|
+
attr_reader :srcroot, :outroot, :vars, :virtual_root, :srcfiles, :outfiles
|
36
|
+
attr_reader :mid_storage, :dep_storage, :cml_storage
|
37
|
+
|
38
|
+
def join_abs_src_path(path) File.join @srcroot, path end
|
39
|
+
|
40
|
+
# join src file path relative to out root, or absolute src path when not relative src
|
41
|
+
def join_rto_src_path(path) ::File.join @src_relative ? @src_relative : @srcroot, path end
|
42
|
+
|
43
|
+
# split path pattern to dir part and file match regex part
|
44
|
+
# @param pattern [String] absolute path, can include '*' to match any char at last no dir part
|
45
|
+
# when pattern include '*', return [dir part, file(or dir) match regex, post dir part, post file part]
|
46
|
+
# ;otherwise return [origin pattern, nil, nil, nil]
|
47
|
+
def split_path_pattern(pattern)
|
48
|
+
match = /^([a-zA-Z]:\/(?:[^\/*]+\/)*+)([^\/*]*+)(?:\*([^\/*]*+))?(?(3)(\/(?:[^\/*]+\/)*+[^\/*]++)?)$/.match pattern
|
49
|
+
raise "file syntax '#{pattern}' error" unless match
|
50
|
+
dir, prefix, postfix, postpath = *match[1..5]
|
51
|
+
regex = postfix && /#{Regexp.escape prefix}(.*)#{Regexp.escape postfix}$/
|
52
|
+
[regex ? dir : pattern, regex, postpath]
|
53
|
+
end
|
54
|
+
|
55
|
+
# find files which can be build's imput file
|
56
|
+
# @param pattern [String] absolute path to find src and out files which can include '*' to match any char at last no dir part
|
57
|
+
# @param ffile [Boolean] return FFile struct or not
|
58
|
+
# @return [Array<VFile, FFile>] return array of FFile when ffile, otherwise array of VFile
|
59
|
+
def find_inputfiles(pattern, ffile:false)
|
60
|
+
pattern = Rmk.normalize_path pattern
|
61
|
+
path, regex, postpath = split_path_pattern pattern
|
62
|
+
files = []
|
63
|
+
if regex
|
64
|
+
@files_mutex.synchronize do
|
65
|
+
find_outfiles_imp files, path, regex, postpath, ffile:ffile
|
66
|
+
range = postpath ? path.size..-1-postpath.size : path.size..-1
|
67
|
+
Dir[pattern].each do |fn|
|
68
|
+
next if @outfiles.include? fn
|
69
|
+
next files << (ffile ? FFile.new(@srcfiles[fn], nil, fn[range][regex, 1]) : @srcfiles[fn]) if @srcfiles.include? fn
|
70
|
+
file = (@srcfiles[fn] = VFile.new rmk:self, path:fn, is_src:true,
|
71
|
+
vpath:fn.start_with?(@srcroot) && fn[@srcroot.size .. -1])
|
72
|
+
files << (ffile ? FFile.new(file, nil, fn[range][regex, 1]) : file)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
else
|
76
|
+
@files_mutex.synchronize do
|
77
|
+
next files << (ffile ? FFile.new(@outfiles[path]) : @outfiles[path]) if @outfiles.include? path
|
78
|
+
next files << (ffile ? FFile.new(@srcfiles[path]) : @srcfiles[path]) if @srcfiles.include? path
|
79
|
+
next unless File.exist? path
|
80
|
+
file = @srcfiles[path] = VFile.new rmk:self, path:path, is_src:true,
|
81
|
+
vpath:path.start_with?(@srcroot) && path[@srcroot.size .. -1]
|
82
|
+
files << (ffile ? FFile.new(file) : file)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
files
|
86
|
+
end
|
87
|
+
|
88
|
+
# find files which must be build's output
|
89
|
+
# @param pattern [String] absolute path to find out files which can include '*' to match any char at last no dir part
|
90
|
+
# @return [Array<Hash>] return Array of file, and Regex when has '*' pattern
|
91
|
+
def find_outfiles(pattern)
|
92
|
+
path, regex, postpath = split_path_pattern Rmk.normalize_path pattern
|
93
|
+
files = []
|
94
|
+
@files_mutex.synchronize do
|
95
|
+
next (files << @outfiles[path] if @outfiles.include? path) unless regex
|
96
|
+
find_outfiles_imp files, path, regex, postpath
|
97
|
+
end
|
98
|
+
files
|
99
|
+
end
|
100
|
+
|
101
|
+
# find outfiles raw implement(assume all parms valid)
|
102
|
+
# @param files [Array<VFile>] array to store finded files
|
103
|
+
# @param path [String] absolute path, path must be dir(end with '/') or empty
|
104
|
+
# @param regex [Regexp] file match regexp, or dir match regexp when postpath not nil
|
105
|
+
# @param postpath [String, nil] path after dir match regexp
|
106
|
+
# @param ffile [Boolean] return FFile struct or not
|
107
|
+
# @return [Array<VFile, FFile>] return array of FFile when ffile, otherwise array of VFile
|
108
|
+
private def find_outfiles_imp(files, path, regex, postpath, ffile:false)
|
109
|
+
range = postpath ? path.size..-1-postpath.size : path.size..-1
|
110
|
+
return @outfiles.each do |k, v|
|
111
|
+
next unless k.start_with?(path) && k[range].match?(regex)
|
112
|
+
files << (ffile ? FFile.new(v, nil, k[range][regex, 1]) : v)
|
113
|
+
end unless postpath
|
114
|
+
@outfiles.each do |k, v|
|
115
|
+
next unless k.start_with?(path) && k.end_with?(postpath) && k[range].match?(regex)
|
116
|
+
files << (ffile ? FFile.new(v, nil, k[range][regex, 1]) : v)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# register a out file
|
121
|
+
# @param path [String]
|
122
|
+
# @param vpath [String]
|
123
|
+
# @return [Rmk::VFile] return file obj added
|
124
|
+
def add_out_file(path:, vpath:nil)
|
125
|
+
@files_mutex.synchronize do
|
126
|
+
raise "file '#{path}' has been defined" if @outfiles.include? path
|
127
|
+
file = @srcfiles.delete(path).change_to_out! file if @srcfiles.include? path
|
128
|
+
@outfiles[path] = VFile.new rmk:self, path:path, vpath:vpath
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# register a src file
|
133
|
+
# @param path [String]
|
134
|
+
# @param vpath [String]
|
135
|
+
# @return [Rmk::VFile] return file obj added
|
136
|
+
def add_src_file(path:, vpath:nil)
|
137
|
+
@files_mutex.synchronize do
|
138
|
+
@outfiles[path] || (@srcfiles[path] ||= VFile.new(rmk:self, path:path, vpath:vpath, is_src:true))
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# add default target
|
143
|
+
def add_default(*files) @files_mutex.synchronize{@defaultfiles.concat files} end
|
144
|
+
|
145
|
+
# parse project
|
146
|
+
# @return [self]
|
147
|
+
def parse
|
148
|
+
puts 'Rmk: parse start'
|
149
|
+
@mid_storage.wait_ready
|
150
|
+
@dep_storage.wait_ready
|
151
|
+
@cml_storage.wait_ready
|
152
|
+
@virtual_root.parse
|
153
|
+
puts 'Rmk: parse done'
|
154
|
+
self
|
155
|
+
end
|
156
|
+
|
157
|
+
def build(*tgts)
|
158
|
+
puts 'Rmk: build start'
|
159
|
+
if tgts.empty?
|
160
|
+
files = @defaultfiles
|
161
|
+
else
|
162
|
+
files = tgts.map do |name|
|
163
|
+
file = Rmk.normalize_path name
|
164
|
+
file = @outfiles[File.absolute_path file, @outroot] || @srcfiles[File.absolute_path file, @srcroot]
|
165
|
+
raise "build target '#{name}' not found" unless file
|
166
|
+
file = file.input_ref_builds[0].outfiles[0] if file.src? && file.input_ref_builds.size == 1
|
167
|
+
file
|
168
|
+
end
|
169
|
+
end
|
170
|
+
if files.empty?
|
171
|
+
@srcfiles.each_value{|file| file.check_for_build}
|
172
|
+
else
|
173
|
+
checklist = []
|
174
|
+
checkproc = proc do |fi|
|
175
|
+
next checklist << fi if fi.src?
|
176
|
+
fi.output_ref_build.infiles.each &checkproc
|
177
|
+
fi.output_ref_build.depfiles.each &checkproc
|
178
|
+
fi.output_ref_build.orderfiles.each &checkproc
|
179
|
+
end
|
180
|
+
files.each &checkproc
|
181
|
+
exit puts('found nothing to build') || 0 if checklist.empty?
|
182
|
+
checklist.each {|file| file.check_for_build}
|
183
|
+
end
|
184
|
+
while Thread.list.size > 1
|
185
|
+
thr = Thread.list[-1]
|
186
|
+
thr.join unless thr == Thread.current
|
187
|
+
end
|
188
|
+
puts 'Rmk: build end'
|
189
|
+
@mid_storage.save
|
190
|
+
@dep_storage.data!.each_key {|key| @dep_storage.data!.delete key unless @outfiles.include? key}
|
191
|
+
@dep_storage.save
|
192
|
+
@cml_storage.data!.each_key {|key| @cml_storage.data!.delete key unless @outfiles.include? key}
|
193
|
+
@cml_storage.save
|
194
|
+
end
|
195
|
+
|
196
|
+
class VFile; end
|
197
|
+
end
|
data/lib/rmk/rule.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'rmk'
|
2
|
+
|
3
|
+
class Rmk::Rule
|
4
|
+
Var = Struct.new :append?, :value
|
5
|
+
|
6
|
+
# create Rule
|
7
|
+
# @param command [String] exec command template
|
8
|
+
def initialize(command)
|
9
|
+
@command = command
|
10
|
+
@vars = {}
|
11
|
+
@rmk_vars = {'out'=>nil, 'collection'=>nil}
|
12
|
+
end
|
13
|
+
attr_reader :command
|
14
|
+
|
15
|
+
def vars; self end
|
16
|
+
|
17
|
+
def [](name) @rmk_vars[name] end
|
18
|
+
|
19
|
+
# add var define template
|
20
|
+
# @return Array<Var>
|
21
|
+
def []=(name, append = false, value)
|
22
|
+
return @vars[name] = Var.new(append, value) unless @rmk_vars.include? name
|
23
|
+
raise "special var '#{name}' can't be append" if append
|
24
|
+
@rmk_vars[name] = value
|
25
|
+
end
|
26
|
+
|
27
|
+
def apply_to(tgt)
|
28
|
+
@vars.each{|name, var| var.append? ? tgt[name] += var.value : tgt[name] = var.value }
|
29
|
+
end
|
30
|
+
end
|
data/lib/rmk/schedule.rb
ADDED
data/lib/rmk/storage.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'schedule'
|
2
|
+
|
3
|
+
class Rmk::Storage
|
4
|
+
# create
|
5
|
+
# @param file [String] file path
|
6
|
+
# @param iniobj [Object] init inside obj
|
7
|
+
def initialize(file, iniobj)
|
8
|
+
@mutex = Thread::Mutex.new
|
9
|
+
@file = file
|
10
|
+
@data = File.exist?(@file) ? Rmk::Schedule.new_thread!{Marshal.load IO.binread @file} : iniobj
|
11
|
+
end
|
12
|
+
|
13
|
+
# wait for storage ready to read and write
|
14
|
+
# @note before call this method storage is not ready, can't call any follow methods
|
15
|
+
def wait_ready; @mutex.synchronize{@data = @data.value if Thread === @data} end
|
16
|
+
|
17
|
+
# save data to disk file
|
18
|
+
def save; @mutex.synchronize{IO.binwrite @file, Marshal.dump(@data)} end
|
19
|
+
|
20
|
+
# run block in mutex sync protected
|
21
|
+
# @yieldparam data [Hash] inside Hash obj
|
22
|
+
# @return [Object] block's result
|
23
|
+
def sync(&cmd) @mutex.synchronize{cmd.call @data} end
|
24
|
+
|
25
|
+
# get inside Hash obj without sync protected
|
26
|
+
def data!; @data end
|
27
|
+
|
28
|
+
# redirect undef method to inside data obj with sync protected, often used for single operation
|
29
|
+
def method_missing(name, *parms, &cmd) @mutex.synchronize{@data.send name, *parms, &cmd} end
|
30
|
+
end
|