canoe 0.3.1.1 → 0.3.2.4

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,6 +1,5 @@
1
- require_relative "source_files"
2
- require_relative "err"
3
-
1
+ require_relative 'source_files'
2
+ require_relative 'util'
4
3
  ##
5
4
  # class DepAnalyzer
6
5
  # This class is the key component of canoe, which offers file dependency analysis functionality.
@@ -14,139 +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
16
+ module Canoe
17
+ class DepAnalyzer
18
+ include Err
19
+ include SystemCommand
20
+
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
26
33
  end
27
- ret
28
- end
29
- end
30
34
 
31
- def self.compiling_filter(deps, build_time, src_sfx = "cpp", hdr_sfx = "hpp")
32
- files = []
33
- @processed = {}
34
- @recompiles = {}
35
- deps.keys.each do |k|
36
- @processed[k] = false
37
- @recompiles[k] = false
38
- end
39
- deps.each do |k, v|
40
- next if k.end_with? ".#{hdr_sfx}"
41
- if should_recompile?(k, build_time)
42
- files << k
43
- @processed[k] = true
44
- @recompiles[k] = true
45
- next
46
- end
47
- v.each do |f|
48
- if mark(f, build_time, deps) || mark(f.sub(".#{hdr_sfx}", ".#{src_sfx}"), build_time, deps)
49
- files << k
50
- @processed[k] = true
51
- @recompiles[k] = true
52
- break
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
53
42
  end
54
- end
55
- end
56
- files
57
- 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)
58
54
 
59
- private
60
-
61
- def self.mark(file, build_time, deps)
62
- ret = false
63
- return false unless File.exists? file
64
- if should_recompile?(file, build_time)
65
- return true
66
- else
67
- deps[file].each do |f|
68
- if @processed[f]
69
- ret |= @recompiles[f]
70
- next
55
+ files << k
56
+ @processed[k] = true
57
+ @recompiles[k] = true
58
+ break
59
+ end
71
60
  end
72
- @processed[f] = true
73
- if mark(f, build_time, deps)
74
- @recompiles[f] = true
75
- return true
61
+ files
62
+ end
63
+
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
76
81
  end
82
+ ret
77
83
  end
78
- end
79
- ret
80
- end
81
84
 
82
- def self.should_recompile?(file, build_time)
83
- judge = build_time
84
- if build_time == Time.new(0)
85
- objfile = if file.start_with?("./src/components")
86
- "./obj/" + file.delete_suffix(File.extname(file))["./src/components/".length..].gsub("/", "_") + ".o"
87
- else
88
- "./obj/#{File.basename(file, ".*")}.o"
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)
89
92
  end
90
- return true unless File.exists? objfile
91
- judge = File.mtime(objfile)
93
+ File.mtime(file) > judge
94
+ end
92
95
  end
93
- File.mtime(file) > judge
94
- end
95
96
 
96
- public
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
102
+ end
97
103
 
98
- def initialize(dir, src_sfx = "cpp", hdr_sfx = "hpp")
99
- @dir = dir
100
- @deps = Hash.new []
101
- @source_suffix = src_sfx
102
- @header_suffix = hdr_sfx
103
- 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
104
108
 
105
- def build_dependence(include_path)
106
- files = SourceFiles.get_all(@dir) do |f|
107
- f.end_with?(".#{@source_suffix}") || f.end_with?(".#{@header_suffix}")
108
- end
109
+ @deps = Hash.new []
110
+ files.each do |fname|
111
+ @deps[fname] = get_all_headers include_path, fname, @header_suffix
112
+ end
109
113
 
110
- @deps = Hash.new []
111
- files.each do |fname|
112
- @deps[fname] = get_all_headers include_path, fname, @header_suffix
114
+ @deps
113
115
  end
114
116
 
115
- @deps
116
- end
117
+ def build_to_file(include_path, filename)
118
+ build_dependence include_path
117
119
 
118
- def build_to_file(include_path, filename)
119
- build_dependence include_path
120
-
121
- File.open(filename, "w") do |f|
122
- @deps.each do |k, v|
123
- 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
124
124
  end
125
- end
126
125
 
127
- @deps
128
- end
126
+ @deps
127
+ end
129
128
 
130
- private
129
+ private
131
130
 
132
- def get_all_headers(include_path, file, suffix = "hpp")
133
- File.open(file, "r") do |f|
134
- ret = []
135
- if file.end_with?(".#{@source_suffix}")
136
- header = file.sub(".#{@source_suffix}", ".#{@header_suffix}")
137
- ret += [header] if File.exists?(header)
138
- end
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
139
138
 
140
- f.each_line do |line|
141
- if mat = line.match(/include "(.+\.#{suffix})"/)
142
- include_path.each do |path|
143
- dep = "#{path}/#{mat[1]}"
144
- ret += [dep] if File.exists? dep
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
145
145
  end
146
146
  end
147
- end
148
147
 
149
- ret.uniq
148
+ ret.uniq
149
+ end
150
150
  end
151
151
  end
152
152
  end
data/lib/source_files.rb CHANGED
@@ -9,15 +9,15 @@ class SourceFiles
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
22
  end
23
23
  end
data/lib/util.rb ADDED
@@ -0,0 +1,84 @@
1
+ require_relative 'coloring'
2
+
3
+ module Canoe
4
+ ##
5
+ # Stepper record the progress of a task
6
+ # progress is obtained via #'progress_as_str
7
+ class Stepper
8
+ def initialize(total, togo)
9
+ @total = total.to_f
10
+ @togo = togo.to_f
11
+ end
12
+
13
+ def progress_as_str
14
+ progress = ((@total - @togo) / @total).round(2) * 100
15
+ "[#{progress.to_i}%%]"
16
+ end
17
+
18
+ def step
19
+ @togo -= 1 if @togo.positive?
20
+ end
21
+ end
22
+
23
+ ##
24
+ # wrapping workspace related functionality to expose to other modules
25
+ module WorkSpaceUtil
26
+ def current_workspace
27
+ abort_on_err 'not in a canoe workspace' unless File.exist? '.canoe'
28
+ config = ConfigReader.new('config.json').extract_flags
29
+
30
+ src_sfx = config['source-suffix'] || 'cpp'
31
+ hdr_sfx = config['header-suffix'] || 'hpp'
32
+
33
+ name = Dir.pwd.split('/')[-1]
34
+ mode = File.exist?("src/main.#{src_sfx}") ? :bin : :lib
35
+
36
+ WorkSpace.new(name, mode, src_sfx, hdr_sfx)
37
+ end
38
+
39
+ def src_to_obj(src)
40
+ current_workspace.src_to_obj(src)
41
+ end
42
+
43
+ def comp_to_obj(comp)
44
+ current_workspace.comp_to_obj(comp)
45
+ end
46
+
47
+ def file_to_obj(file)
48
+ current_workspace.file_to_obj(file)
49
+ end
50
+
51
+ def extract_one_file(file, deps)
52
+ current_workspace.extract_one_file(file, deps)
53
+ end
54
+
55
+ def extract_one_file_obj(file, deps)
56
+ current_workspace.extract_one_file_obj(file, deps)
57
+ end
58
+ end
59
+
60
+ module SystemCommand
61
+ def issue_command(cmd_str)
62
+ puts cmd_str
63
+ system cmd_str
64
+ end
65
+ end
66
+
67
+ module Err
68
+ def warn_on_err(err)
69
+ puts <<~ERR
70
+ #{'Waring: '.yellow}
71
+ #{err}
72
+ try 'canoe help' for more information
73
+ ERR
74
+ end
75
+
76
+ def abort_on_err(err)
77
+ abort <<~ERR
78
+ #{'Fatal: '.red}
79
+ #{err}
80
+ try 'canoe help' for more information
81
+ ERR
82
+ end
83
+ end
84
+ end
data/lib/workspace/add.rb CHANGED
@@ -1,27 +1,29 @@
1
- class WorkSpace
2
- def add(args)
3
- args.each do |i|
4
- dir = @components
5
- filenames = i.split("/")
6
- prefix = []
7
- filenames.each do |filename|
8
- dir += "/#{filename}"
9
- prefix << filename
10
- unless Dir.exist? dir
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
+
11
13
  FileUtils.mkdir dir
12
14
  Dir.chdir(dir) do
13
- puts "created " + Dir.pwd.blue
14
- create_working_files prefix.join("__"), filename
15
+ puts "created + #{Dir.pwd.blue}"
16
+ create_working_files prefix.join('__'), filename
15
17
  end
16
18
  end
17
19
  end
18
20
  end
19
- end
20
21
 
21
- private
22
+ private
22
23
 
23
- def create_working_files(prefix, filename)
24
- DefaultFiles.create_cpp filename, @source_suffix, @header_suffix
25
- DefaultFiles.create_hpp @name, prefix, filename, @header_suffix
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
26
28
  end
27
29
  end
@@ -1,131 +1,192 @@
1
- class WorkSpace
2
- # args are commandline parameters passed to `canoe build`,
3
- # could be 'all', 'test', 'target' or empty
4
- def build(args)
5
- case args[0]
6
- when "all"
7
- build_all
8
- when "test"
9
- build_test
10
- else
11
- build_target
1
+ require 'json'
2
+ module Canoe
3
+ class WorkSpace
4
+ def src_to_obj(src)
5
+ @obj_prefix + File.basename(src, ".*") + ".o"
12
6
  end
13
- end
14
7
 
15
- private
8
+ def comp_to_obj(comp)
9
+ @obj_prefix + comp.delete_suffix(File.extname(comp))[@components_prefix.length..].gsub("/", "_") + ".o"
10
+ end
16
11
 
17
- def build_flags(flags, config)
18
- config.values.each do |v|
19
- case v
20
- when String
21
- flags << v
22
- when Array
23
- v.each do |o|
24
- flags << o
25
- end
12
+ # the if else order is important because tests are regarded as sources
13
+ def file_to_obj(file)
14
+ if file.start_with?(@components_prefix)
15
+ comp_to_obj file
26
16
  else
27
- abort_on_err "unknown options in config.json, #{v}"
17
+ src_to_obj file
18
+ end
19
+ end
20
+
21
+ def hdr_of_src(file)
22
+ file.gsub(".#{@source_suffix}", ".#{@header_suffix}")
23
+ end
24
+
25
+ # args are commandline parameters passed to `canoe build`,
26
+ # could be 'all', 'test', 'target', 'base' or empty
27
+ def build(arg = 'target')
28
+ build_compiler_from_config
29
+ send "build_#{arg}"
30
+ end
31
+
32
+ private
33
+
34
+ def build_flags(flags, config)
35
+ config.values.each do |v|
36
+ case v
37
+ when String
38
+ flags << v
39
+ when Array
40
+ v.each do |o|
41
+ flags << o
42
+ end
43
+ else
44
+ abort_on_err "unknown options in config.json, #{v}"
45
+ end
28
46
  end
29
47
  end
30
- end
31
48
 
32
- def build_compiler_from_config
33
- Dir.chdir(@workspace) do
34
- flags = ConfigReader.extract_flags "config.json"
35
- compiler_name = flags["compiler"] ? flags["compiler"] : "clang++"
36
- abort_on_err "compiler #{compiler_name} not found" unless File.exists?("/usr/bin/#{compiler_name}")
37
- compiler_flags = ["-Isrc/components"]
49
+ def build_compiler_from_config
50
+ flags = ConfigReader.new('config.json').extract_flags
51
+ compiler_name = flags['compiler'] ? flags['compiler'] : 'clang++'
52
+
53
+ abort_on_err "compiler #{compiler_name} not found" unless system "which #{compiler_name} > /dev/null"
54
+ compiler_flags = ['-Isrc/components']
38
55
  linker_flags = []
39
56
 
40
- c_flags, l_flags = flags["flags"]["compile"], flags["flags"]["link"]
57
+ c_flags, l_flags = flags['flags']['compile'], flags['flags']['link']
41
58
  build_flags(compiler_flags, c_flags)
42
59
  build_flags(linker_flags, l_flags)
43
60
 
44
61
  @compiler = Compiler.new compiler_name, compiler_flags, linker_flags
45
62
  end
46
- end
47
63
 
48
- def compile(f, o)
49
- @compiler.compile f, o
50
- end
64
+ def compile(f, o)
65
+ @compiler.compile f, o
66
+ end
51
67
 
52
- def link_exectutable(odir, objs)
53
- puts "#{"[100%]".green} linking"
54
- @compiler.link_executable "#{odir}/#{@name}", objs
55
- end
68
+ def link_exectutable(odir, objs)
69
+ puts "#{"[100%]".green} linking"
70
+ @compiler.link_executable "#{odir}/#{@name}", objs
71
+ end
56
72
 
57
- def link_shared(odir, objs)
58
- puts "#{"[100%]".green} linking"
59
- @compiler.link_shared "#{odir}/lib#{@name}", objs
60
- end
73
+ def link_shared(odir, objs)
74
+ puts "#{"[100%]".green} linking"
75
+ @compiler.link_shared "#{odir}/lib#{@name}", objs
76
+ end
61
77
 
62
- def build_bin(files)
63
- # return if files.empty?
64
- build_compiler_from_config
65
- if build_common(files) && link_exectutable("./target", Dir.glob("obj/*.o"))
66
- puts "BUILDING SUCCEEDED".green
67
- else
68
- puts "building FAILED".red
78
+ def build_bin(files)
79
+ if build_common(files) &&
80
+ link_exectutable(@target_short, Dir.glob("obj/*.o").reject { |f| f.start_with? 'obj/test_' })
81
+ puts "BUILDING SUCCEEDED".green
82
+ return true
83
+ else
84
+ puts "building target FAILED".red
85
+ return false
86
+ end
69
87
  end
70
- end
71
88
 
72
- def build_lib(files)
73
- # return if files.empty?
74
- build_compiler_from_config
75
- @compiler.append_compiling_flag "-fPIC"
76
- if build_common(files) && link_shared("./target", Dir.glob("obj/*.o"))
77
- puts "BUILDING SUCCEEDED".green
78
- else
79
- puts "building FAILED".red
89
+ def build_lib(files)
90
+ @compiler.append_compiling_flag "-fPIC"
91
+ if build_common(files) &&
92
+ link_shared(@target_short, Dir.glob("obj/*.o").reject { |f| f.start_with? 'obj/test_'})
93
+ puts "BUILDING SUCCEEDED".green
94
+ else
95
+ puts "building target FAILED".red
96
+ end
80
97
  end
81
- end
82
98
 
83
- def build_common(files)
84
- all = SourceFiles.get_all("./src") { |f| f.end_with? @source_suffix }
85
- total = all.size.to_f
86
- compiled = total - files.size
87
- comps = files.select { |f| f.start_with? @components_prefix }
88
- srcs = files - comps
89
- flag = true
90
-
91
- srcs.each do |f|
92
- progress = (compiled / total).round(2) * 100
93
- printf "[#{progress.to_i}%%]".green + " compiling #{f}: "
94
- fname = f.split("/")[-1]
95
- o = @obj_prefix + File.basename(fname, ".*") + ".o"
96
- flag = false unless compile f, o
97
- compiled += 1
98
- end
99
-
100
- comps.each do |f|
101
- progress = (compiled / total).round(2) * 100
102
- printf "[#{progress.to_i}%%]".green + " compiling #{f}: "
103
- o = @obj_prefix + f.delete_suffix(File.extname(f))[@components_prefix.length..]
104
- .gsub("/", "_") + ".o"
105
- flag = false unless compile f, o
106
- compiled += 1
107
- end
108
- flag
109
- end
99
+ def build_common(files)
100
+ all = SourceFiles.get_all(@src_short) { |f| f.end_with? @source_suffix }
101
+ stepper = Stepper.new all.size, files.size
102
+ flag = true
103
+
104
+ files.each do |f|
105
+ progress = stepper.progress_as_str.green
106
+ printf "#{progress.green} compiling #{f.yellow}: "
107
+ o = file_to_obj(f)
108
+ flag = false unless compile f, o
109
+ stepper.step
110
+ end
111
+ flag
112
+ end
113
+
114
+ def build_all
115
+ build_target
116
+ build_test
117
+ end
118
+
119
+ def get_deps(dep_file, source_dir, include_dirs)
120
+ File.exist?(dep_file) ? DepAnalyzer.read_from(dep_file) :
121
+ DepAnalyzer.new(source_dir, @source_suffix, @header_suffix).build_to_file(include_dirs, dep_file)
122
+ end
123
+
124
+ def target_deps
125
+ get_deps @deps, @src_short, [@src_short, @components_short]
126
+ end
127
+
128
+ # contain only headers
129
+ # sources in ./src/components are not included
130
+ def tests_deps
131
+ get_deps @test_deps, @tests_short, [@src_short, @components_short]
132
+ end
133
+
134
+ def build_target
135
+ puts "#{'[BUILDING TARGET]'.magenta}..."
136
+ target = "#{@target}/#{@name}"
137
+ build_time = File.exist?(target) ? File.mtime(target) : Time.new(0)
138
+ files = DepAnalyzer.compiling_filter target_deps, build_time, @source_suffix, @header_suffix
110
139
 
111
- def build_all
112
- build_target
113
- build_test
140
+ if files.empty? && File.exist?(target)
141
+ puts "nothing to do, all up to date"
142
+ return true
143
+ end
144
+
145
+ self.send "build_#{@mode.to_s}", files
146
+ end
147
+
148
+ # generate a compile_commands.json file
149
+ def build_base
150
+ deps = target_deps.merge tests_deps
151
+ database = CompilationDatabase.new
152
+ deps.each_key do |k|
153
+ next if k.end_with? @header_suffix
154
+ c = @compiler.name.end_with?('++') ? 'c++' : 'c'
155
+
156
+ arg = [c] + @compiler.compiling_flags_as_str.split + [k] + [file_to_obj(k)] + ['-c', '-o']
157
+ database.add_command_object(@workspace, arg, k)
158
+ end
159
+ File.open('compile_commands.json', 'w') do |f|
160
+ f.puts database.pretty_to_s
161
+ end
162
+ end
114
163
  end
115
164
 
116
- def build_target
117
- deps = File.exist?(@deps) ?
118
- DepAnalyzer.read_from(@deps) :
119
- DepAnalyzer.new("./src").build_to_file(["./src", "./src/components"], @deps)
120
- target = "./target/#{@name}"
121
- build_time = File.exist?(target) ? File.mtime(target) : Time.new(0)
122
- files = DepAnalyzer.compiling_filter(deps, build_time, @source_suffix, @header_suffix)
165
+ class CompilationDatabase
166
+ attr_reader :database
167
+ def initialize
168
+ @database = []
169
+ end
170
+
171
+ def add_command_object(dir, arguments, file)
172
+ temp = {
173
+ "arguments" => arguments,
174
+ "directory" => dir,
175
+ "file" => file
176
+ }
177
+ @database << temp
178
+ end
123
179
 
124
- if files.empty? && File.exist?(target)
125
- puts "nothing to do, all up to date"
126
- return
180
+ def to_s
181
+ @database.to_s
127
182
  end
128
183
 
129
- self.send "build_#{@mode.to_s}", files
184
+ def pretty_to_s
185
+ JSON.pretty_generate(@database)
186
+ end
187
+
188
+ def to_json
189
+ @database.to_json
190
+ end
130
191
  end
131
192
  end