canoe 0.3.1 → 0.3.2.3

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.extract_flags('config.json')
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,39 +1,52 @@
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
28
18
  end
29
19
  end
30
- end
31
20
 
32
- def build_compiler_from_config
33
- Dir.chdir(@workspace) do
21
+ # args are commandline parameters passed to `canoe build`,
22
+ # could be 'all', 'test', 'target', 'base' or empty
23
+ def build(arg = 'target')
24
+ build_compiler_from_config
25
+ send "build_#{arg}"
26
+ end
27
+
28
+ private
29
+
30
+ def build_flags(flags, config)
31
+ config.values.each do |v|
32
+ case v
33
+ when String
34
+ flags << v
35
+ when Array
36
+ v.each do |o|
37
+ flags << o
38
+ end
39
+ else
40
+ abort_on_err "unknown options in config.json, #{v}"
41
+ end
42
+ end
43
+ end
44
+
45
+ def build_compiler_from_config
34
46
  flags = ConfigReader.extract_flags "config.json"
35
47
  compiler_name = flags["compiler"] ? flags["compiler"] : "clang++"
36
- abort_on_err "compiler #{compiler_name} not found" unless File.exists?("/usr/bin/#{compiler_name}")
48
+
49
+ abort_on_err "compiler #{compiler_name} not found" unless system "which #{compiler_name} > /dev/null"
37
50
  compiler_flags = ["-Isrc/components"]
38
51
  linker_flags = []
39
52
 
@@ -43,89 +56,133 @@ class WorkSpace
43
56
 
44
57
  @compiler = Compiler.new compiler_name, compiler_flags, linker_flags
45
58
  end
46
- end
47
59
 
48
- def compile(f, o)
49
- @compiler.compile f, o
50
- end
60
+ def compile(f, o)
61
+ @compiler.compile f, o
62
+ end
51
63
 
52
- def link_exectutable(odir, objs)
53
- puts "#{"[100%]".green} linking"
54
- @compiler.link_executable "#{odir}/#{@name}", objs
55
- end
64
+ def link_exectutable(odir, objs)
65
+ puts "#{"[100%]".green} linking"
66
+ @compiler.link_executable "#{odir}/#{@name}", objs
67
+ end
56
68
 
57
- def link_shared(odir, objs)
58
- puts "#{"[100%]".green} linking"
59
- @compiler.link_shared "#{odir}/lib#{@name}", objs
60
- end
69
+ def link_shared(odir, objs)
70
+ puts "#{"[100%]".green} linking"
71
+ @compiler.link_shared "#{odir}/lib#{@name}", objs
72
+ end
61
73
 
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
74
+ def build_bin(files)
75
+ if build_common(files) &&
76
+ link_exectutable(@target_short, Dir.glob("obj/*.o").reject { |f| f.start_with? 'obj/test_' })
77
+ puts "BUILDING SUCCEEDED".green
78
+ return true
79
+ else
80
+ puts "building target FAILED".red
81
+ return false
82
+ end
69
83
  end
70
- end
71
84
 
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
85
+ def build_lib(files)
86
+ @compiler.append_compiling_flag "-fPIC"
87
+ if build_common(files) &&
88
+ link_shared(@target_short, Dir.glob("obj/*.o").reject { |f| f.start_with? 'obj/test_'})
89
+ puts "BUILDING SUCCEEDED".green
90
+ else
91
+ puts "building target FAILED".red
92
+ end
80
93
  end
81
- end
82
94
 
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
95
+ def build_common(files)
96
+ all = SourceFiles.get_all(@src_short) { |f| f.end_with? @source_suffix }
97
+ stepper = Stepper.new all.size, files.size
98
+ flag = true
99
+
100
+ files.each do |f|
101
+ progress = stepper.progress_as_str.green
102
+ printf "#{progress.green} compiling #{f.yellow}: "
103
+ o = file_to_obj(f)
104
+ flag = false unless compile f, o
105
+ stepper.step
106
+ end
107
+ flag
108
+ end
109
+
110
+ def build_all
111
+ build_target
112
+ build_test
113
+ end
114
+
115
+ def get_deps(dep_file, source_dir, include_dirs)
116
+ File.exist?(dep_file) ? DepAnalyzer.read_from(dep_file) :
117
+ DepAnalyzer.new(source_dir, @source_suffix, @header_suffix).build_to_file(include_dirs, dep_file)
118
+ end
119
+
120
+ def target_deps
121
+ get_deps @deps, @src_short, [@src_short, @components_short]
122
+ end
110
123
 
111
- def build_all
112
- build_target
113
- build_test
124
+ # contain only headers
125
+ # sources in ./src/components are not included
126
+ def tests_deps
127
+ get_deps @test_deps, @tests_short, [@src_short, @components_short]
128
+ end
129
+
130
+ def build_target
131
+ puts "#{'[BUILDING TARGET]'.magenta}..."
132
+ target = "#{@target}/#{@name}"
133
+ build_time = File.exist?(target) ? File.mtime(target) : Time.new(0)
134
+ files = DepAnalyzer.compiling_filter target_deps, build_time, @source_suffix, @header_suffix
135
+
136
+ if files.empty? && File.exist?(target)
137
+ puts "nothing to do, all up to date"
138
+ return true
139
+ end
140
+
141
+ self.send "build_#{@mode.to_s}", files
142
+ end
143
+
144
+ # generate a compile_commands.json file
145
+ def build_base
146
+ deps = target_deps.merge tests_deps
147
+ database = CompilationDatabase.new
148
+ deps.each_key do |k|
149
+ next if k.end_with? @header_suffix
150
+ c = @compiler.name.end_with?('++') ? 'c++' : 'c'
151
+
152
+ arg = [c] + @compiler.compiling_flags_as_str.split + [k] + [file_to_obj(k)] + ['-c', '-o']
153
+ database.add_command_object(@workspace, arg, k)
154
+ end
155
+ File.open('compile_commands.json', 'w') do |f|
156
+ f.puts database.pretty_to_s
157
+ end
158
+ end
114
159
  end
115
160
 
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)
161
+ class CompilationDatabase
162
+ attr_reader :database
163
+ def initialize
164
+ @database = []
165
+ end
166
+
167
+ def add_command_object(dir, arguments, file)
168
+ temp = {
169
+ "arguments" => arguments,
170
+ "directory" => dir,
171
+ "file" => file
172
+ }
173
+ @database << temp
174
+ end
123
175
 
124
- if files.empty? && File.exist?(target)
125
- puts "nothing to do, all up to date"
126
- return
176
+ def to_s
177
+ @database.to_s
127
178
  end
128
179
 
129
- self.send "build_#{@mode.to_s}", files
180
+ def pretty_to_s
181
+ JSON.pretty_generate(@database)
182
+ end
183
+
184
+ def to_json
185
+ @database.to_json
186
+ end
130
187
  end
131
188
  end