canoe 0.3.0 → 0.3.2

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.
data/lib/dependence.rb CHANGED
@@ -1,11 +1,10 @@
1
1
  require_relative 'source_files'
2
- require_relative 'err'
3
-
2
+ require_relative 'util'
4
3
  ##
5
4
  # class DepAnalyzer
6
- # This class is the key component of canoe, which offers file dependency analysis functionality.
5
+ # This class is the key component of canoe, which offers file dependency analysis functionality.
7
6
  # A DepAnalyzer takes a directory as input, sources files and corresponding header files in this
8
- # directory should have same name, i.e. test.cpp and test.hpp.
7
+ # directory should have same name, e.g. test.cpp and test.hpp.
9
8
  # DepAnalyzer would read every source file and recursively process user header files included in this source file to
10
9
  # find out all user header files this source file depends on.
11
10
  # Based on dependencies built in previous stage, DepAnalyzer determines which files should be recompiled and return
@@ -14,120 +13,140 @@ require_relative 'err'
14
13
  # Dependencies could be written to a file to avoid wasting time parsing all files, Depanalyzer would read from
15
14
  # this file to construct dependencies. But if sources files included new headers or included headers are revmoed,
16
15
  # Depanalyzer should rebuild the whole dependencies.
17
- class DepAnalyzer
18
- include Err
19
- def self.read_from(filename)
20
- File.open(filename, "r") do |f|
21
- ret = Hash.new []
22
- f.each_with_index do |line, i|
23
- entry = line.split(': ')
24
- Err.abort_on_err("Bad .canoe.deps format, line #{i+1}") unless entry.length == 2
25
- ret[entry[0]] = entry[1].split
26
- end
27
- ret
28
- end
29
- end
16
+ module Canoe
17
+ class DepAnalyzer
18
+ include Err
19
+ include SystemCommand
30
20
 
31
- def self.compiling_filter(deps, build_time, src_sfx='cpp', hdr_sfx='hpp')
32
- files = []
33
- @processed = {}
34
- deps.keys.each do |k|
35
- @processed[k] = false
36
- end
37
- deps.each do |k, v|
38
- next if k.end_with? ".#{hdr_sfx}"
39
- if should_recompile?(k, build_time)
40
- files << k
41
- @processed[k] = true
42
- next
21
+ class << self
22
+ include WorkSpaceUtil
23
+ def read_from(filename)
24
+ File.open(filename, 'r') do |f|
25
+ ret = Hash.new []
26
+ f.each_with_index do |line, i|
27
+ entry = line.split(': ')
28
+ abort_on_err("Bad .canoe.deps format, line #{i + 1}") unless entry.length == 2
29
+ ret[entry[0]] = entry[1].split
30
+ end
31
+ ret
32
+ end
43
33
  end
44
- v.each do |f|
45
- if mark(f, build_time, deps) || mark(f.sub(".#{hdr_sfx}", ".#{src_sfx}"), build_time, deps)
46
- files << k
47
- break
34
+
35
+ def compiling_filter(deps, build_time, src_sfx = 'cpp', hdr_sfx = 'hpp')
36
+ files = []
37
+ @processed = {}
38
+ @recompiles = {}
39
+ deps.each_key do |k|
40
+ @processed[k] = false
41
+ @recompiles[k] = false
48
42
  end
43
+ deps.each do |k, v|
44
+ next if k.end_with? ".#{hdr_sfx}"
45
+
46
+ if should_recompile?(k, build_time)
47
+ files << k
48
+ @processed[k] = true
49
+ @recompiles[k] = true
50
+ next
51
+ end
52
+ v.each do |f|
53
+ next unless mark(f, build_time, deps) || mark(f.sub(".#{hdr_sfx}", ".#{src_sfx}"), build_time, deps)
54
+
55
+ files << k
56
+ @processed[k] = true
57
+ @recompiles[k] = true
58
+ break
59
+ end
60
+ end
61
+ files
49
62
  end
50
- end
51
- files
52
- end
53
63
 
54
- private
55
- def self.mark(file, build_time, deps)
56
- return false unless File.exists? file
57
- if should_recompile?(file, build_time)
58
- return true
59
- else
60
- deps[file].each do |f|
61
- next if @processed[f]
62
- @processed[f] = true
63
- return true if mark(f, build_time, deps)
64
+ private
65
+
66
+ def mark(file, build_time, deps)
67
+ ret = false
68
+ return false unless File.exist? file
69
+ return true if should_recompile?(file, build_time)
70
+
71
+ deps[file].each do |f|
72
+ if @processed[f]
73
+ ret |= @recompiles[f]
74
+ next
75
+ end
76
+ @processed[f] = true
77
+ if mark(f, build_time, deps)
78
+ @recompiles[f] = true
79
+ return true
80
+ end
81
+ end
82
+ ret
83
+ end
84
+
85
+ def should_recompile?(file, build_time)
86
+ judge = build_time
87
+ if build_time == Time.new(0)
88
+ objfile = file_to_obj(file)
89
+ return true unless File.exist? objfile
90
+
91
+ judge = File.mtime(objfile)
92
+ end
93
+ File.mtime(file) > judge
64
94
  end
65
95
  end
66
- false
67
- end
68
96
 
69
- def self.should_recompile?(file, build_time)
70
- judge = build_time
71
- if build_time == Time.new(0)
72
- objfile = "./obj/#{File.basename(file, ".*")}.o"
73
- return true unless File.exists? objfile
74
- judge = File.mtime(objfile)
97
+ def initialize(dir, src_sfx = 'cpp', hdr_sfx = 'hpp')
98
+ @dir = dir
99
+ @deps = Hash.new []
100
+ @source_suffix = src_sfx
101
+ @header_suffix = hdr_sfx
75
102
  end
76
- File.mtime(file) > judge
77
- end
78
103
 
79
- public
80
- def initialize(dir, src_sfx='cpp', hdr_sfx='hpp')
81
- @dir = dir
82
- @deps = Hash.new []
83
- @source_suffix = src_sfx
84
- @header_suffix = hdr_sfx
85
- end
104
+ def build_dependence(include_path)
105
+ files = SourceFiles.get_all(@dir) do |f|
106
+ f.end_with?(".#{@source_suffix}") || f.end_with?(".#{@header_suffix}")
107
+ end
86
108
 
87
- def build_dependence(include_path)
88
- files = SourceFiles.get_all(@dir) do |f|
89
- f.end_with?(".#{@source_suffix}") || f.end_with?(".#{@header_suffix}")
90
- end
109
+ @deps = Hash.new []
110
+ files.each do |fname|
111
+ @deps[fname] = get_all_headers include_path, fname, @header_suffix
112
+ end
91
113
 
92
- @deps = Hash.new []
93
- files.each do |fname|
94
- @deps[fname] = get_all_headers include_path, fname, @header_suffix
114
+ @deps
95
115
  end
96
116
 
97
- @deps
98
- end
117
+ def build_to_file(include_path, filename)
118
+ build_dependence include_path
99
119
 
100
- def build_to_file(include_path, filename)
101
- build_dependence include_path
102
-
103
- File.open(filename, "w") do |f|
104
- @deps.each do |k, v|
105
- f.write "#{k}: #{v.join(" ")}\n"
120
+ File.open(filename, 'w') do |f|
121
+ @deps.each do |k, v|
122
+ f.write "#{k}: #{v.join(' ')}\n"
123
+ end
106
124
  end
125
+
126
+ @deps
107
127
  end
108
-
109
- @deps
110
- end
111
128
 
112
- private
113
- def get_all_headers(include_path, file, suffix='hpp')
114
- File.open(file, "r") do |f|
115
- ret = []
116
- if file.end_with?(".#{@source_suffix}")
117
- header = file.sub(".#{@source_suffix}", ".#{@header_suffix}")
118
- ret += [header] if File.exists?(header)
119
- end
120
-
121
- f.each_line do |line|
122
- if mat = line.match(/include "(.+\.#{suffix})"/)
123
- include_path.each do |path|
124
- dep = "#{path}/#{mat[1]}"
125
- ret += [dep] if File.exists? dep
129
+ private
130
+
131
+ def get_all_headers(include_path, file, suffix = 'hpp')
132
+ File.open(file, 'r') do |f|
133
+ ret = []
134
+ if file.end_with?(".#{@source_suffix}")
135
+ header = file.sub(".#{@source_suffix}", ".#{@header_suffix}")
136
+ ret += [header] if File.exist?(header)
137
+ end
138
+
139
+ f.each_line do |line|
140
+ if (mat = line.match(/include "(.+\.#{suffix})"/))
141
+ include_path.each do |path|
142
+ dep = "#{path}/#{mat[1]}"
143
+ ret += [dep] if File.exist? dep
144
+ end
126
145
  end
127
146
  end
147
+
148
+ ret.uniq
128
149
  end
129
-
130
- ret.uniq
131
150
  end
132
151
  end
133
152
  end
data/lib/source_files.rb CHANGED
@@ -5,27 +5,28 @@ class SourceFiles
5
5
  class << self
6
6
  def get_all(dir, &block)
7
7
  @files = []
8
- get_all_helper(dir, &block)
8
+ get_all_helper(dir, &block)
9
9
  @files
10
10
  end
11
11
 
12
- def get_in(dir, &block)
12
+ def get_in(dir)
13
13
  @files = []
14
14
  Dir.each_child(dir) do |f|
15
15
  file = "#{dir}/#{f}"
16
16
  if File.file? file
17
17
  if block_given?
18
- @files << "#{file}" if yield(f)
18
+ @files << file.to_s if yield(f)
19
19
  else
20
- @files << "#{file}"
20
+ @files << file.to_s
21
21
  end
22
- end
22
+ end
23
23
  end
24
-
24
+
25
25
  @files
26
26
  end
27
27
 
28
28
  private
29
+
29
30
  def get_all_helper(dir, &block)
30
31
  Dir.each_child(dir) do |f|
31
32
  file = "#{dir}/#{f}"
@@ -42,4 +43,3 @@ class SourceFiles
42
43
  end
43
44
  end
44
45
  end
45
-
data/lib/util.rb ADDED
@@ -0,0 +1,81 @@
1
+ require_relative 'coloring'
2
+
3
+ module Canoe
4
+ class Stepper
5
+ def initialize(total, togo)
6
+ @total = total.to_f
7
+ @togo = togo.to_f
8
+ end
9
+
10
+ def progress_as_str
11
+ progress = ((@total - @togo) / @total).round(2) * 100
12
+ "[#{progress.to_i}%%]"
13
+ end
14
+
15
+ def step
16
+ @togo -= 1 if @togo.positive?
17
+ end
18
+ end
19
+
20
+ ##
21
+ # wrapping workspace related functionality to expose to other modules
22
+ module WorkSpaceUtil
23
+ def get_current_workspace
24
+ abort_on_err 'not in a canoe workspace' unless File.exist? '.canoe'
25
+ config = ConfigReader.extract_flags('config.json')
26
+
27
+ src_sfx = config['source-suffix'] || 'cpp'
28
+ hdr_sfx = config['header-suffix'] || 'hpp'
29
+
30
+ name = Dir.pwd.split('/')[-1]
31
+ mode = File.exist?("src/main.#{src_sfx}") ? :bin : :lib
32
+
33
+ WorkSpace.new(name, mode, src_sfx, hdr_sfx)
34
+ end
35
+
36
+ def src_to_obj(src)
37
+ get_current_workspace.src_to_obj(src)
38
+ end
39
+
40
+ def comp_to_obj(comp)
41
+ get_current_workspace.comp_to_obj(comp)
42
+ end
43
+
44
+ def file_to_obj(file)
45
+ get_current_workspace.file_to_obj(file)
46
+ end
47
+
48
+ def extract_one_file(file, deps)
49
+ get_current_workspace.extract_one_file(file, deps)
50
+ end
51
+
52
+ def extract_one_file_obj(file, deps)
53
+ get_current_workspace.extract_one_file_obj(file, deps)
54
+ end
55
+ end
56
+
57
+ module SystemCommand
58
+ def issue_command(cmd_str)
59
+ puts cmd_str
60
+ system cmd_str
61
+ end
62
+ end
63
+
64
+ module Err
65
+ def warn_on_err(err)
66
+ puts <<~ERR
67
+ #{'Waring: '.yellow}
68
+ #{err}
69
+ try 'canoe help' for more information
70
+ ERR
71
+ end
72
+
73
+ def abort_on_err(err)
74
+ abort <<~ERR
75
+ #{'Fatal: '.red}
76
+ #{err}
77
+ try 'canoe help' for more information
78
+ ERR
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,29 @@
1
+ module Canoe
2
+ class WorkSpace
3
+ def add(args)
4
+ args.each do |i|
5
+ dir = @components
6
+ filenames = i.split '/'
7
+ prefix = []
8
+ filenames.each do |filename|
9
+ dir += "/#{filename}"
10
+ prefix << filename
11
+ next if Dir.exist? dir
12
+
13
+ FileUtils.mkdir dir
14
+ Dir.chdir(dir) do
15
+ puts "created + #{Dir.pwd.blue}"
16
+ create_working_files prefix.join('__'), filename
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def create_working_files(prefix, filename)
25
+ DefaultFiles.create_cpp filename, @source_suffix, @header_suffix
26
+ DefaultFiles.create_hpp @name, prefix, filename, @header_suffix
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,150 @@
1
+ module Canoe
2
+ class WorkSpace
3
+ def src_to_obj(src)
4
+ @obj_prefix + File.basename(src, ".*") + ".o"
5
+ end
6
+
7
+ def comp_to_obj(comp)
8
+ @obj_prefix + comp.delete_suffix(File.extname(comp))[@components_prefix.length..].gsub("/", "_") + ".o"
9
+ end
10
+
11
+ # the if else order is important because tests are regarded as sources
12
+ def file_to_obj(file)
13
+ if file.start_with?(@components_prefix)
14
+ comp_to_obj file
15
+ else
16
+ src_to_obj file
17
+ end
18
+ end
19
+
20
+ # args are commandline parameters passed to `canoe build`,
21
+ # could be 'all', 'test', 'target' or empty
22
+ def build(args)
23
+ options = {[] => 'target', ['all'] => 'all', ['test'] => 'test'}
24
+ if options.include?(args)
25
+ send "build_#{options[args]}"
26
+ else
27
+ abort_on_err "Unkown subcommand #{args.join(" ").red}"
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def build_flags(flags, config)
34
+ config.values.each do |v|
35
+ case v
36
+ when String
37
+ flags << v
38
+ when Array
39
+ v.each do |o|
40
+ flags << o
41
+ end
42
+ else
43
+ abort_on_err "unknown options in config.json, #{v}"
44
+ end
45
+ end
46
+ end
47
+
48
+ def build_compiler_from_config
49
+ flags = ConfigReader.extract_flags "config.json"
50
+ compiler_name = flags["compiler"] ? flags["compiler"] : "clang++"
51
+
52
+ abort_on_err "compiler #{compiler_name} not found" unless system "which #{compiler_name} > /dev/null"
53
+ compiler_flags = ["-Isrc/components"]
54
+ linker_flags = []
55
+
56
+ c_flags, l_flags = flags["flags"]["compile"], flags["flags"]["link"]
57
+ build_flags(compiler_flags, c_flags)
58
+ build_flags(linker_flags, l_flags)
59
+
60
+ @compiler = Compiler.new compiler_name, compiler_flags, linker_flags
61
+ end
62
+
63
+ def compile(f, o)
64
+ @compiler.compile f, o
65
+ end
66
+
67
+ def link_exectutable(odir, objs)
68
+ puts "#{"[100%]".green} linking"
69
+ @compiler.link_executable "#{odir}/#{@name}", objs
70
+ end
71
+
72
+ def link_shared(odir, objs)
73
+ puts "#{"[100%]".green} linking"
74
+ @compiler.link_shared "#{odir}/lib#{@name}", objs
75
+ end
76
+
77
+ def build_bin(files)
78
+ if build_common(files) &&
79
+ link_exectutable(@target_short, Dir.glob("obj/*.o").reject { |f| f.start_with? 'obj/test_' })
80
+ puts "BUILDING SUCCEEDED".green
81
+ return true
82
+ else
83
+ puts "building target FAILED".red
84
+ return false
85
+ end
86
+ end
87
+
88
+ def build_lib(files)
89
+ @compiler.append_compiling_flag "-fPIC"
90
+ if build_common(files) &&
91
+ link_shared(@target_short, Dir.glob("obj/*.o").reject { |f| f.start_with? 'obj/test_'})
92
+ puts "BUILDING SUCCEEDED".green
93
+ else
94
+ puts "building target FAILED".red
95
+ end
96
+ end
97
+
98
+ def build_common(files)
99
+ all = SourceFiles.get_all(@src_short) { |f| f.end_with? @source_suffix }
100
+ stepper = Stepper.new all.size, files.size
101
+ flag = true
102
+
103
+ files.each do |f|
104
+ progress = stepper.progress_as_str.green
105
+ printf "#{progress.green} compiling #{f.yellow}: "
106
+ o = file_to_obj(f)
107
+ flag = false unless compile f, o
108
+ stepper.step
109
+ end
110
+ flag
111
+ end
112
+
113
+ def build_all
114
+ build_target
115
+ build_test
116
+ end
117
+
118
+ def get_deps(dep_file, source_dir, include_dirs)
119
+ File.exist?(dep_file) ? DepAnalyzer.read_from(dep_file) :
120
+ DepAnalyzer.new(source_dir, @source_suffix, @header_suffix).build_to_file(include_dirs, dep_file)
121
+ end
122
+
123
+ def target_deps
124
+ get_deps @deps, @src_short, [@src_short, @components_short]
125
+ end
126
+
127
+ # contain only headers
128
+ # sources in ./src/components are not included
129
+ def tests_deps
130
+ get_deps @test_deps, @tests_short, [@src_short, @components_short]
131
+ end
132
+
133
+ def build_target
134
+ puts "#{'[BUILDING TARGET]'.magenta}..."
135
+ deps = get_deps @deps, @src, [@src_short, @components_short]
136
+ target = "#{@target}/#{@name}"
137
+ build_time = File.exist?(target) ? File.mtime(target) : Time.new(0)
138
+ files = DepAnalyzer.compiling_filter deps, build_time, @source_suffix, @header_suffix
139
+
140
+ build_compiler_from_config
141
+
142
+ if files.empty? && File.exist?(target)
143
+ puts "nothing to do, all up to date"
144
+ return
145
+ end
146
+
147
+ self.send "build_#{@mode.to_s}", files
148
+ end
149
+ end
150
+ end