canoe 0.3.0.1 → 0.3.2.1

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.
@@ -0,0 +1,12 @@
1
+ module Canoe
2
+ class WorkSpace
3
+ def run(args)
4
+ return if @mode == :lib
5
+
6
+ return unless build []
7
+
8
+ args = args.join ' '
9
+ issue_command "#{@target_short}/#{@name} #{args}"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,136 @@
1
+ module Canoe
2
+ class WorkSpace
3
+ def test(args)
4
+ if args.empty?
5
+ test_all
6
+ return
7
+ end
8
+
9
+ args.each do |arg|
10
+ case arg
11
+ when 'all'
12
+ test_all
13
+ else
14
+ test_single arg
15
+ end
16
+ end
17
+ end
18
+
19
+ # extract one test file's dependency
20
+ def extract_one_file(file, deps)
21
+ ret = deps[file]
22
+
23
+ deps[file].each do |f|
24
+ dep = extract_one_file(f, deps)
25
+ dep.each do |d|
26
+ ret << d unless ret.include?(d)
27
+ end
28
+ end
29
+ ret.map { |f| f.gsub(".#{@header_suffix}", ".#{@source_suffix}") }
30
+ end
31
+
32
+ def extract_one_file_obj(file, deps)
33
+ extract_one_file(file, deps).map do |f|
34
+ file_to_obj(f)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def test_all
41
+ build_test
42
+ fetch_all_test_files.each do |f|
43
+ test_single File.basename(f, '.*')['test_'.length..]
44
+ end
45
+ end
46
+
47
+ def test_single(name)
48
+ bin = "#{@target_short}/test_#{name}"
49
+ file = "#{@tests_short}/test_#{name}.#{@source_suffix}"
50
+ abort_on_err "Can not find source file #{file.red} for test #{name.red}" unless File.exist?(file)
51
+ build_one_test(file, fetch_all_deps) unless File.exist?(bin)
52
+
53
+ issue_command bin
54
+ end
55
+
56
+ def fetch_all_test_files
57
+ Dir.glob("#{@tests_short}/*.#{@source_suffix}").filter do |f|
58
+ File.basename(f).start_with? 'test_'
59
+ end
60
+ end
61
+
62
+ def fetch_all_deps
63
+ target_deps.merge(tests_deps)
64
+ end
65
+
66
+ def test_build_time
67
+ fetch_all_test_files.map do |f|
68
+ obj = "#{@target_short}/#{File.basename(f, '.*')}"
69
+ File.exist?(obj) ? File.mtime(obj) : Time.new(0)
70
+ end.min
71
+ end
72
+
73
+ # @deps is the dependency hash for tests
74
+ # cyclic dependency is not handled
75
+ # compiler should first be built
76
+ def compile_one_test(test_file, deps)
77
+ extract_one_file(test_file, deps).each do |f|
78
+ o = file_to_obj(f)
79
+ next if File.exist?(o) && File.mtime(o) > File.mtime(f)
80
+
81
+ compile(f, o)
82
+ end
83
+ compile(test_file, file_to_obj(test_file))
84
+ end
85
+
86
+ def link_one_test(test_file, deps)
87
+ target = "#{@target_short}/#{File.basename(test_file, '.*')}"
88
+ @compiler.link_executable target, extract_one_file_obj(test_file, deps) + [file_to_obj(test_file)]
89
+ end
90
+
91
+ def build_one_test(test_file, deps)
92
+ compile_one_test(test_file, deps)
93
+ link_one_test(test_file, deps)
94
+ end
95
+
96
+ def compile_all_tests(deps)
97
+ files = DepAnalyzer.compiling_filter(deps, test_build_time, @source_suffix, @header_suffix).select do |f|
98
+ File.basename(f).start_with?('test_')
99
+ end
100
+
101
+ stepper = Stepper.new fetch_all_test_files.size, files.size
102
+
103
+ files.each do |f|
104
+ printf "#{stepper.progress_as_str.green} compiling #{f} "
105
+ compile_one_test(f, deps)
106
+ stepper.step
107
+ end
108
+ end
109
+
110
+ def link_all_tests(deps)
111
+ all_files = fetch_all_test_files
112
+
113
+ stepper = Stepper.new all_files.size, all_files.size
114
+ fetch_all_test_files.each do |f|
115
+ printf "#{stepper.progress_as_str.green} linking #{File.basename(f, '.*').yellow}: "
116
+ link_one_test(f, deps)
117
+ stepper.step
118
+ end
119
+ end
120
+
121
+ def build_test
122
+ puts "#{'[COMPILING TESTS]'.magenta}..."
123
+ return unless test_build_time
124
+
125
+ total_deps = fetch_all_deps
126
+ compile_all_tests(total_deps)
127
+ puts "#{'[100%]'.green} compiling done, starts linking..."
128
+ puts "#{'[LINKING TESTS]'.magenta}..."
129
+ # compilation and link are separated because they may be separated
130
+ # by unexpected interrupt like C-c, C-d, etc.
131
+ # thus unditionally link all tests
132
+ link_all_tests(total_deps)
133
+ puts "#{'[100%]'.green} linking done"
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,7 @@
1
+ module Canoe
2
+ class WorkSpace
3
+ def update
4
+ generate
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ module Canoe
2
+ class WorkSpace
3
+ def self.version
4
+ puts <<~VER
5
+ canoe v0.3.2.1
6
+ For features in this version, please visit https://github.com/Dicridon/canoe
7
+ Currently, canoe can do below:
8
+ - project creation
9
+ - project auto build, run and test (works like Cargo for Rust)
10
+ - project structure management
11
+ by XIONG Ziwei
12
+ VER
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,63 @@
1
+ require 'fileutils'
2
+ require_relative '../source_files'
3
+ require_relative '../compiler'
4
+ require_relative '../config_reader'
5
+ require_relative '../default_files'
6
+ require_relative '../util'
7
+ require_relative '../dependence'
8
+ require_relative '../coloring'
9
+
10
+ module Canoe
11
+ ##
12
+ # A workspace resents a C/C++ project
13
+ # This class is responsible for the main functionality of canoe, such as building and cleaning
14
+ class WorkSpace
15
+ include Err
16
+ include SystemCommand
17
+ attr_reader :name, :cwd, :src_prefix, :components_prefix, :obj_prefix,
18
+ :source_suffix, :header_suffix, :mode, :target_short,
19
+ :src_short, :components_short, :obj_short, :tests_short
20
+
21
+ def initialize(name, mode, src_suffix = 'cpp', hdr_suffix = 'hpp', nu = false)
22
+ @name = name
23
+ @compiler = Compiler.new 'clang++', ['-Isrc/components'], []
24
+ @cwd = Dir.new(Dir.pwd)
25
+ @workspace = Dir.pwd.to_s + (nu ? "/#{@name}" : '')
26
+ @src = "#{@workspace}/src"
27
+ @components = "#{@src}/components"
28
+ @obj = "#{@workspace}/obj"
29
+ @third = "#{@workspace}/third-party"
30
+ @target = "#{@workspace}/target"
31
+ @tests = "#{@workspace}/tests"
32
+ @mode = mode
33
+ @deps = '.canoe.deps'
34
+ @test_deps = '.canoe.test.deps'
35
+
36
+ @target_short = './target'
37
+ @src_short = './src'
38
+ @components_short = "#{@src_short}/components"
39
+ @obj_short = './obj'
40
+ @tests_short = './tests'
41
+
42
+ @src_prefix = './src/'
43
+ @components_prefix = './src/components/'
44
+ @obj_prefix = './obj/'
45
+
46
+ @source_suffix = src_suffix
47
+ @header_suffix = hdr_suffix
48
+ end
49
+ end
50
+ end
51
+
52
+ require_relative 'help'
53
+ require_relative 'version'
54
+ require_relative 'new'
55
+ require_relative 'add'
56
+ require_relative 'build'
57
+ require_relative 'generate'
58
+ require_relative 'run'
59
+ require_relative 'dep'
60
+ require_relative 'clean'
61
+ require_relative 'update'
62
+ require_relative 'test'
63
+ require_relative 'make'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: canoe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0.1
4
+ version: 0.3.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - XIONG Ziwei
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-11 00:00:00.000000000 Z
11
+ date: 2021-05-13 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |+
14
14
  Canoe offers project management and building facilities to C/C++ projects.
@@ -33,9 +33,21 @@ files:
33
33
  - lib/config_reader.rb
34
34
  - lib/default_files.rb
35
35
  - lib/dependence.rb
36
- - lib/err.rb
37
36
  - lib/source_files.rb
38
- - lib/workspace.rb
37
+ - lib/util.rb
38
+ - lib/workspace/add.rb
39
+ - lib/workspace/build.rb
40
+ - lib/workspace/clean.rb
41
+ - lib/workspace/dep.rb
42
+ - lib/workspace/generate.rb
43
+ - lib/workspace/help.rb
44
+ - lib/workspace/make.rb
45
+ - lib/workspace/new.rb
46
+ - lib/workspace/run.rb
47
+ - lib/workspace/test.rb
48
+ - lib/workspace/update.rb
49
+ - lib/workspace/version.rb
50
+ - lib/workspace/workspace.rb
39
51
  homepage: https://github.com/Dicridon/canoe
40
52
  licenses:
41
53
  - MIT
@@ -55,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
55
67
  - !ruby/object:Gem::Version
56
68
  version: '0'
57
69
  requirements: []
58
- rubygems_version: 3.1.2
70
+ rubygems_version: 3.2.3
59
71
  signing_key:
60
72
  specification_version: 4
61
73
  summary: a C/C++ project management and building tool
data/lib/err.rb DELETED
@@ -1,20 +0,0 @@
1
- require_relative 'coloring'
2
- module Err
3
- def warn_on_err(err)
4
- puts <<~ERR
5
- #{"Waring: ".yellow}
6
- #{err}
7
- try 'canoe help' for more information
8
- ERR
9
- end
10
-
11
- def abort_on_err(err)
12
- abort <<~ERR
13
- #{"Fatal: ".red}
14
- #{err}
15
- try 'canoe help' for more information
16
- ERR
17
- end
18
-
19
- module_function :warn_on_err, :abort_on_err
20
- end
data/lib/workspace.rb DELETED
@@ -1,330 +0,0 @@
1
- require 'fileutils'
2
- require_relative 'source_files'
3
- require_relative 'compiler'
4
- require_relative 'config_reader'
5
- require_relative 'default_files'
6
- require_relative 'err'
7
- require_relative 'dependence'
8
- require_relative 'coloring'
9
-
10
- class WorkSpace
11
- include Err
12
- attr_reader :name, :cwd
13
- def self.help
14
- info = <<~INFO
15
- canoe is a C/C++ project manager, inspired by Rust cargo.
16
- usage:
17
- canoe new tada: create a project named 'tada' in current directory
18
-
19
- canoe build: compile current project (execute this command in project directory)
20
-
21
- canoe generate: generate dependency relationships and store it in '.canoe.deps' file. Alias: update
22
-
23
- canoe update: udpate dependency relationships and store it in '.canoe.deps' file.
24
-
25
- canoe run: compile and execute current project (execute this command in project directory)
26
-
27
- canoe clean: remove all generated object files and binary files
28
-
29
- canoe help: show this help message
30
-
31
- canoe add tada: add a folder named tada under workspace/components,
32
-
33
- canoe dep: show current dependency relationships of current project
34
-
35
- canoe verion: version information
36
-
37
- new project_name [mode] [suffixes]:
38
- create a new project with project_name.
39
- In this project, four directories obj, src, target and third-party will be generated in project directory.
40
- in src, directory 'components' will be generated if [mode] is '--lib', an extra main.cpp will be generated if [mode] is '--bin'
41
-
42
- [mode]: --lib for a library and --bin for executable binaries
43
- [suffixes]: should be in 'source_suffix:header_suffix" format, notice the ':' between two suffixes
44
- add component_name:
45
- add a folder named tada under workspace/components.
46
- two files tada.hpp and tada.cpp would be craeted and intialized. File suffix may differ according users' specifications.
47
- if component_name is a path separated by '/', then canoe would create folders and corresponding files recursively.
48
-
49
- generate:
50
- generate dependence relationship for each file, this may accelarate
51
- `canoe buid` command. It's recommanded to execute this command everytime
52
- headers are added or removed from any file.
53
-
54
- update:
55
- this command is needed because '.canoe.deps' is actually a cache of dependency relationships so that canoe doesn't have to analyze all the files when building a project.
56
- So when a file includes new headers or some headers are removed, users have to use 'canoe udpate'
57
- to update dependency relationships.
58
-
59
- build [options]:
60
- build current project, arguments in [options] will be passed to C++ compiler
61
-
62
- run [options]:
63
- build current project with no specific compilation flags, and run this project, passing [options] as command line arguments to the binary
64
-
65
- clean:
66
- remove all generated object files and binary files
67
-
68
- help:
69
- show this help message
70
-
71
- verion:
72
- display version information
73
-
74
- dep:
75
- display file dependencies in a better readable way
76
-
77
- @author: written by XIONG Ziwei, ICT, CAS
78
- @contact: noahxiong@outlook.com
79
- INFO
80
- puts info
81
- end
82
-
83
-
84
- def initialize(name, mode, src_suffix='cpp', hdr_suffix='hpp')
85
- @name = name
86
- @compiler = Compiler.new 'clang++', ['-Isrc/components']
87
- @cwd = Dir.new(Dir.pwd)
88
- @workspace = "#{Dir.pwd}/#{@name}"
89
- @src = "#{@workspace}/src"
90
- @components = "#{@src}/components"
91
- @obj = "#{@workspace}/obj"
92
- @third = "#{@workspace}/third-party"
93
- @target = "#{@workspace}/target"
94
- @tests = "#{@workspace}/tests"
95
- @mode = mode
96
- @deps = '.canoe.deps'
97
-
98
- @src_prefix = './src/'
99
- @components_prefix = './src/components/'
100
- @obj_prefix = './obj/'
101
-
102
- @source_suffix = src_suffix
103
- @header_suffix = hdr_suffix
104
- end
105
-
106
- def new
107
- begin
108
- Dir.mkdir(@name)
109
- rescue
110
- abort_on_err "workspace #{@name} already exsits"
111
- end
112
- Dir.mkdir(@src)
113
- Dir.mkdir(@components)
114
- Dir.mkdir("#{@workspace}/obj")
115
- if @mode == :bin
116
- DefaultFiles.create_main(@src, @source_suffix)
117
- else
118
- DefaultFiles.create_lib_header(@src, @name, @header_suffix)
119
- end
120
- File.new("#{@workspace}/.canoe", "w")
121
- DefaultFiles.create_config @workspace, @source_suffix, @header_suffix
122
- # DefaultFiles.create_emacs_dir_local @workspace
123
-
124
- Dir.mkdir(@third)
125
- Dir.mkdir(@target)
126
- Dir.chdir(@workspace) do
127
- system "git init"
128
- end
129
- puts "workspace #{@workspace.blue} is created"
130
- end
131
-
132
- # args are commandline parameters passed to `canoe build`
133
- def build(args)
134
- deps = File.exist?(@deps) ?
135
- DepAnalyzer.read_from(@deps) :
136
- DepAnalyzer.new('./src').build_to_file(['./src', './src/components'], @deps)
137
- target = "./target/#{@name}"
138
- build_time = File.exist?(target) ? File.mtime(target) : Time.new(0)
139
- files = DepAnalyzer.compiling_filter(deps, build_time, @source_suffix, @header_suffix)
140
-
141
- if files.empty? && File.exist?(target)
142
- puts "nothing to do, all up to date"
143
- return
144
- end
145
-
146
- self.send "build_#{@mode.to_s}", files, args
147
- end
148
-
149
- def generate
150
- DepAnalyzer.new('./src', @source_suffix, @header_suffix)
151
- .build_to_file ['./src', './src/components'], @deps
152
- end
153
-
154
- def update
155
- generate
156
- end
157
-
158
- def clean
159
- self.send "clean_#{@mode.to_s}"
160
- end
161
-
162
- def run(args)
163
- return if @mode == :lib
164
- build []
165
- args = args.join " "
166
- puts "./target/#{@name} #{args}"
167
- exec "./target/#{@name} #{args}"
168
- end
169
-
170
- def add(args)
171
- args.each do |i|
172
- dir = @components
173
- filenames = i.split("/")
174
- prefix = []
175
- filenames.each do |filename|
176
- dir += "/#{filename}"
177
- prefix << filename
178
- unless Dir.exist? dir
179
- FileUtils.mkdir dir
180
- Dir.chdir(dir) do
181
- puts "created " + Dir.pwd.blue
182
- create_working_files prefix.join('__'), filename
183
- end
184
- end
185
- end
186
- end
187
- end
188
-
189
- def dep
190
- deps = DepAnalyzer.read_from(@deps) if File.exist?(@deps)
191
- deps.each do |k, v|
192
- unless v.empty?
193
- puts "#{k.blue} depends on: "
194
- v.each {|f| puts " #{f.blue}"}
195
- puts ""
196
- end
197
- end
198
- end
199
-
200
- def test(args)
201
- args.each do |arg|
202
- case arg
203
- when "all"
204
- test_all
205
- else
206
- test_single arg
207
- end
208
- end
209
- end
210
-
211
- private
212
- def create_working_files(prefix, filename)
213
- DefaultFiles.create_cpp filename, @source_suffix, @header_suffix
214
- DefaultFiles.create_hpp @name, prefix, filename, @header_suffix
215
- end
216
-
217
- def build_compiler_from_config(args)
218
- Dir.chdir(@workspace) do
219
- flags = ConfigReader.extract_flags "config.json"
220
- compiler_name = flags['compiler'] ? flags['compiler'] : "clang++"
221
- abort_on_err "compiler #{compiler_name} not found" unless File.exists?("/usr/bin/#{compiler_name}")
222
- compiler_flags = ['-Isrc/components'] + args
223
-
224
- if opts = flags['flags']
225
- opts.each do |k, v|
226
- case v
227
- when String
228
- compiler_flags << v
229
- when Array
230
- v.each do |o|
231
- compiler_flags << o
232
- end
233
- else
234
- abort_on_err "unknown options in config.json, #{v}"
235
- end
236
- end
237
- end
238
-
239
- @compiler = Compiler.new compiler_name, compiler_flags
240
- end
241
- end
242
-
243
- def compile(f, o)
244
- @compiler.compile f, o
245
- end
246
-
247
- def link_exectutable(odir, objs)
248
- puts "#{"[100%]".green} linking"
249
- @compiler.link_executable "#{odir}/#{@name}", objs
250
- end
251
-
252
- def link_shared(odir, objs)
253
- puts "#{"[100%]".green} linking"
254
- @compiler.link_shared "#{odir}/lib#{@name}", objs
255
- end
256
-
257
- def build_bin(files, args)
258
- # return if files.empty?
259
- build_compiler_from_config args
260
- if build_common(files, args) && link_exectutable('./target', Dir.glob("obj/*.o"))
261
- puts "BUILDING SUCCEEDED".green
262
- else
263
- puts "building FAILED".red
264
- end
265
- end
266
-
267
- def build_lib(files, args)
268
- # return if files.empty?
269
- build_compiler_from_config args
270
- @compiler.append_compiling_flag '-fPIC'
271
- if (build_common files, args) && link_shared('./target', Dir.glob("obj/*.o"))
272
- puts "BUILDING SUCCEEDED".green
273
- else
274
- puts "building FAILED".red
275
- end
276
- end
277
-
278
- def build_common(files, args)
279
- all = SourceFiles.get_all('./src') {|f| f.end_with? @source_suffix}
280
- total = all.size.to_f
281
- compiled = total - files.size
282
- comps = files.select {|f| f.start_with? @components_prefix}
283
- srcs = files - comps
284
- flag = true;
285
- srcs.each do |f|
286
- progress = (compiled / total).round(2) * 100
287
- printf "[#{progress.to_i}%%]".green + " compiling #{f}: "
288
- fname = f.split("/")[-1]
289
- o = @obj_prefix + File.basename(fname, ".*") + '.o'
290
- flag = false unless compile f, o
291
- compiled += 1
292
- end
293
-
294
- comps.each do |f|
295
- progress = (compiled / total).round(2) * 100
296
- printf "[#{progress.to_i}%%]".green + " compiling #{f}: "
297
- o = @obj_prefix + f.delete_suffix(File.extname(f))[@components_prefix.length..]
298
- .gsub('/', '_') + '.o'
299
- flag = false unless compile f, o
300
- compiled += 1
301
- end
302
- flag
303
- end
304
-
305
- def clean_obj
306
- puts "rm -f ./obj/*.o"
307
- system "rm -f ./obj/*.o"
308
- end
309
-
310
- def clean_target
311
- puts "rm -f ./target/*"
312
- system "rm -f ./target/*"
313
- end
314
-
315
- def clean_bin
316
- clean_obj
317
- clean_target
318
- end
319
-
320
- def clean_lib
321
- clean_obj
322
- clean_target
323
- end
324
-
325
- public
326
- def inspect
327
- puts "name is #{@name}"
328
- puts "name is #{@workspace}"
329
- end
330
- end