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.
@@ -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
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
4
+ version: 0.3.2
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-10 00:00:00.000000000 Z
11
+ date: 2021-05-12 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |+
14
14
  Canoe offers project management and building facilities to C/C++ projects.
@@ -28,13 +28,26 @@ files:
28
28
  - bin/canoe
29
29
  - lib/canoe.rb
30
30
  - lib/cmd.rb
31
+ - lib/coloring.rb
31
32
  - lib/compiler.rb
32
33
  - lib/config_reader.rb
33
34
  - lib/default_files.rb
34
35
  - lib/dependence.rb
35
- - lib/err.rb
36
36
  - lib/source_files.rb
37
- - 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
38
51
  homepage: https://github.com/Dicridon/canoe
39
52
  licenses:
40
53
  - MIT
@@ -54,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
54
67
  - !ruby/object:Gem::Version
55
68
  version: '0'
56
69
  requirements: []
57
- rubygems_version: 3.1.2
70
+ rubygems_version: 3.2.3
58
71
  signing_key:
59
72
  specification_version: 4
60
73
  summary: a C/C++ project management and building tool
data/lib/err.rb DELETED
@@ -1,19 +0,0 @@
1
- module Err
2
- def warn_on_err(err)
3
- puts <<~ERR
4
- Warning:
5
- #{err}
6
- try 'canoe help' for more information
7
- ERR
8
- end
9
-
10
- def abort_on_err(err)
11
- abort <<~ERR
12
- Fatal:
13
- #{err}
14
- try 'canoe help' for more information
15
- ERR
16
- end
17
-
18
- module_function :warn_on_err, :abort_on_err
19
- end
data/lib/workspace.rb DELETED
@@ -1,328 +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
-
9
- class WorkSpace
10
- include Err
11
- attr_reader :name, :cwd
12
- def self.help
13
- info = <<~INFO
14
- canoe is a C/C++ project manager, inspired by Rust cargo.
15
- usage:
16
- canoe new tada: create a project named 'tada' in current directory
17
-
18
- canoe build: compile current project (execute this command in project directory)
19
-
20
- canoe generate: generate dependency relationships and store it in '.canoe.deps' file. Alias: update
21
-
22
- canoe update: udpate dependency relationships and store it in '.canoe.deps' file.
23
-
24
- canoe run: compile and execute current project (execute this command in project directory)
25
-
26
- canoe clean: remove all generated object files and binary files
27
-
28
- canoe help: show this help message
29
-
30
- canoe add tada: add a folder named tada under workspace/components,
31
-
32
- canoe dep: show current dependency relationships of current project
33
-
34
- canoe verion: version information
35
-
36
- new project_name [mode] [suffixes]:
37
- create a new project with project_name.
38
- In this project, four directories obj, src, target and third-party will be generated in project directory.
39
- in src, directory 'components' will be generated if [mode] is '--lib', an extra main.cpp will be generated if [mode] is '--bin'
40
-
41
- [mode]: --lib for a library and --bin for executable binaries
42
- [suffixes]: should be in 'source_suffix:header_suffix" format, notice the ':' between two suffixes
43
- add component_name:
44
- add a folder named tada under workspace/components.
45
- two files tada.hpp and tada.cpp would be craeted and intialized. File suffix may differ according users' specifications.
46
- if component_name is a path separated by '/', then canoe would create folders and corresponding files recursively.
47
-
48
- generate:
49
- generate dependence relationship for each file, this may accelarate
50
- `canoe buid` command. It's recommanded to execute this command everytime
51
- headers are added or removed from any file.
52
-
53
- update:
54
- this command is needed because '.canoe.deps' is actually a cache of dependency relationships so that
55
- 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
- Dir.mkdir(@name)
108
- Dir.mkdir(@src)
109
- Dir.mkdir(@components)
110
- Dir.mkdir("#{@workspace}/obj")
111
- if @mode == :bin
112
- DefaultFiles.create_main(@src, @source_suffix)
113
- else
114
- DefaultFiles.create_lib_header(@src, @name, @header_suffix)
115
- end
116
- File.new("#{@workspace}/.canoe", "w")
117
- DefaultFiles.create_config @workspace, @source_suffix, @header_suffix
118
- # DefaultFiles.create_emacs_dir_local @workspace
119
-
120
- Dir.mkdir(@third)
121
- Dir.mkdir(@target)
122
- Dir.chdir(@workspace) do
123
- system "git init"
124
- end
125
- puts "workspace #{@workspace} is created"
126
- end
127
-
128
- # args are commandline parameters passed to `canoe build`
129
- def build(args)
130
- deps = File.exist?(@deps) ?
131
- DepAnalyzer.read_from(@deps) :
132
- DepAnalyzer.new('./src').build_to_file(['./src', './src/components'], @deps)
133
- target = "./target/#{@name}"
134
- build_time = File.exist?(target) ? File.mtime(target) : Time.new(0)
135
- files = DepAnalyzer.compiling_filter(deps, build_time, @source_suffix, @header_suffix)
136
-
137
- if files.empty?
138
- puts "nothing to do, all up to date"
139
- return
140
- end
141
-
142
- self.send "build_#{@mode.to_s}", files, args
143
- end
144
-
145
- def generate
146
- DepAnalyzer.new('./src', @source_suffix, @header_suffix)
147
- .build_to_file ['./src', './src/components'], @deps
148
- end
149
-
150
- def update
151
- generate
152
- end
153
-
154
- def clean
155
- self.send "clean_#{@mode.to_s}"
156
- end
157
-
158
- def run(args)
159
- return if @mode == :lib
160
- build []
161
- args = args.join " "
162
- puts "./target/#{@name} #{args}"
163
- exec "./target/#{@name} #{args}"
164
- end
165
-
166
- def add(args)
167
- args.each do |i|
168
- dir = @components
169
- filenames = i.split("/")
170
- prefix = []
171
- filenames.each do |filename|
172
- dir += "/#{filename}"
173
- prefix << filename
174
- unless Dir.exist? dir
175
- FileUtils.mkdir dir
176
- Dir.chdir(dir) do
177
- puts "created " + Dir.pwd
178
- create_working_files prefix.join('__'), filename
179
- end
180
- end
181
- end
182
- end
183
- end
184
-
185
- def dep
186
- deps = DepAnalyzer.read_from(@deps) if File.exist?(@deps)
187
- deps.each do |k, v|
188
- unless v.empty?
189
- puts "#{k} depends on: "
190
- v.each {|f| puts " #{f}"}
191
- puts ""
192
- end
193
- end
194
- end
195
-
196
- def test(args)
197
- args.each do |arg|
198
- case arg
199
- when "all"
200
- test_all
201
- else
202
- test_single arg
203
- end
204
- end
205
- end
206
-
207
- private
208
- def create_working_files(prefix, filename)
209
- DefaultFiles.create_cpp filename, @source_suffix, @header_suffix
210
- DefaultFiles.create_hpp @name, prefix, filename, @header_suffix
211
- end
212
-
213
- def build_compiler_from_config(args)
214
- Dir.chdir(@workspace) do
215
- flags = ConfigReader.extract_flags "config.json"
216
- compiler_name = flags['compiler'] ? flags['compiler'] : "clang++"
217
- abort_on_err "compiler #{compiler_name} not found" unless File.exists?("/usr/bin/#{compiler_name}")
218
- compiler_flags = ['-Isrc/components'] + args
219
-
220
- if opts = flags['flags']
221
- opts.each do |k, v|
222
- case v
223
- when String
224
- compiler_flags << v
225
- when Array
226
- v.each do |o|
227
- compiler_flags << o
228
- end
229
- else
230
- abort_on_err "unknown options in config.json, #{v}"
231
- end
232
- end
233
- end
234
-
235
- @compiler = Compiler.new compiler_name, compiler_flags
236
- end
237
- end
238
-
239
- def compile(f, o)
240
- @compiler.compile f, o
241
- end
242
-
243
- def link_exectutable(odir, objs)
244
- puts "[100%] linking"
245
- @compiler.link_executable "#{odir}/#{@name}", objs
246
- end
247
-
248
- def link_shared(odir, objs)
249
- puts "[100%] linking"
250
- @compiler.link_shared "#{odir}/lib#{@name}", objs
251
- end
252
-
253
- def build_bin(files, args)
254
- return if files.empty?
255
- build_compiler_from_config args
256
- if build_common(files, args)
257
- link_exectutable('./target', Dir.glob("obj/*.o"))
258
- puts "canoe: building succeeded"
259
- else
260
- puts "canoe: building failed"
261
- end
262
- end
263
-
264
- def build_lib(files, args)
265
- return if files.empty?
266
- build_compiler_from_config args
267
- @compiler.append_compiling_flag '-fPIC'
268
- if (build_common files, args)
269
- link_shared('./target', Dir.glob("obj/*.o"))
270
- puts "canoe: building succeeded"
271
- else
272
- puts "canoe: building failed"
273
- end
274
- end
275
-
276
- def build_common(files, args)
277
- all = SourceFiles.get_all('./src') {|f| f.end_with? @source_suffix}
278
- total = all.size.to_f
279
- compiled = total - files.size
280
- comps = files.select {|f| f.start_with? @components_prefix}
281
- srcs = files - comps
282
- flag = true;
283
- srcs.each do |f|
284
- progress = (compiled / total).round(2) * 100
285
- printf "[#{progress.to_i}%%] compiling #{f}: "
286
- fname = f.split("/")[-1]
287
- o = @obj_prefix + fname.delete_suffix(File.extname(fname)) + '.o'
288
- flag = false unless compile f, o
289
- compiled += 1
290
- end
291
-
292
- comps.each do |f|
293
- progress = (compiled / total).round(2) * 100
294
- printf "[#{progress.to_i}%%] compiling #{f}: "
295
- o = @obj_prefix + f.delete_suffix(File.extname(f))[@components_prefix.length..]
296
- .gsub('/', '_') + '.o'
297
- flag = false unless compile f, o
298
- compiled += 1
299
- end
300
- flag
301
- end
302
-
303
- def clean_obj
304
- puts "rm -f ./obj/*.o"
305
- system "rm -f ./obj/*.o"
306
- end
307
-
308
- def clean_target
309
- puts "rm -f ./target/*"
310
- system "rm -f ./target/*"
311
- end
312
-
313
- def clean_bin
314
- clean_obj
315
- clean_target
316
- end
317
-
318
- def clean_lib
319
- clean_obj
320
- clean_target
321
- end
322
-
323
- public
324
- def inspect
325
- puts "name is #{@name}"
326
- puts "name is #{@workspace}"
327
- end
328
- end