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.
- checksums.yaml +4 -4
- data/bin/canoe +1 -1
- data/lib/canoe.rb +22 -18
- data/lib/cmd.rb +73 -88
- data/lib/coloring.rb +9 -9
- data/lib/compiler.rb +44 -46
- data/lib/config_reader.rb +1 -1
- data/lib/default_files.rb +18 -25
- data/lib/dependence.rb +115 -106
- data/lib/source_files.rb +7 -7
- data/lib/util.rb +81 -0
- data/lib/workspace/add.rb +29 -0
- data/lib/workspace/build.rb +150 -0
- data/lib/workspace/clean.rb +36 -0
- data/lib/workspace/dep.rb +14 -0
- data/lib/workspace/generate.rb +10 -0
- data/lib/workspace/help.rb +84 -0
- data/lib/workspace/make.rb +280 -0
- data/lib/workspace/new.rb +31 -0
- data/lib/workspace/run.rb +12 -0
- data/lib/workspace/test.rb +136 -0
- data/lib/workspace/update.rb +7 -0
- data/lib/workspace/version.rb +15 -0
- data/lib/workspace/workspace.rb +63 -0
- metadata +17 -5
- data/lib/err.rb +0 -20
- data/lib/workspace.rb +0 -330
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Canoe
|
|
2
|
+
class WorkSpace
|
|
3
|
+
# valid options: none, 'all', 'target', 'tests'
|
|
4
|
+
def clean(args)
|
|
5
|
+
options = {
|
|
6
|
+
[] => 'all', ['all'] => 'all',
|
|
7
|
+
['target'] => 'target', ['tests'] => 'tests', ['obj'] => 'obj'
|
|
8
|
+
}
|
|
9
|
+
if options.include?(args)
|
|
10
|
+
send "clean_#{options[args]}"
|
|
11
|
+
else
|
|
12
|
+
abort_on_err "Unkown subcommand #{args.join(' ').red}"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def clean_all
|
|
19
|
+
clean_target
|
|
20
|
+
clean_obj
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def clean_target
|
|
24
|
+
issue_command 'rm ./target/* -rf'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def clean_obj
|
|
28
|
+
issue_command 'rm ./obj/* -rf'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def clean_tests
|
|
32
|
+
issue_command 'rm ./obj/test_* -rf'
|
|
33
|
+
issue_command 'rm ./target/test_* -rf'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module Canoe
|
|
2
|
+
class WorkSpace
|
|
3
|
+
def generate
|
|
4
|
+
DepAnalyzer.new(@src_short, @source_suffix, @header_suffix)
|
|
5
|
+
.build_to_file [@src_short, @components_short], @deps
|
|
6
|
+
DepAnalyzer.new(@tests_short, @source_suffix, @header_suffix)
|
|
7
|
+
.build_to_file [@src_short, @components_short], @test_deps
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module Canoe
|
|
2
|
+
class WorkSpace
|
|
3
|
+
def self.help
|
|
4
|
+
info = <<~INFO
|
|
5
|
+
canoe is a C/C++ project manager, inspired by Rust cargo.
|
|
6
|
+
usage:
|
|
7
|
+
canoe new tada: create a project named 'tada' in current directory
|
|
8
|
+
|
|
9
|
+
canoe build: compile current project (execute this command in project directory)
|
|
10
|
+
|
|
11
|
+
canoe test: build and run tests
|
|
12
|
+
|
|
13
|
+
canoe generate: generate dependency relationships and store it in '.canoe.deps' file. Alias: update
|
|
14
|
+
|
|
15
|
+
canoe update: udpate dependency relationships and store it in '.canoe.deps' file.
|
|
16
|
+
|
|
17
|
+
canoe run: compile and execute current project (execute this command in project directory)
|
|
18
|
+
|
|
19
|
+
canoe clean: remove all generated object files and binary files
|
|
20
|
+
|
|
21
|
+
canoe help: show this help message
|
|
22
|
+
|
|
23
|
+
canoe add tada: add a folder named tada under workspace/components,
|
|
24
|
+
|
|
25
|
+
canoe dep: show current dependency relationships of current project
|
|
26
|
+
|
|
27
|
+
canoe verion: version information
|
|
28
|
+
|
|
29
|
+
canoe make: generate a makefile for this project
|
|
30
|
+
|
|
31
|
+
new project_name [mode] [suffixes]:
|
|
32
|
+
create a new project with project_name.
|
|
33
|
+
In this project, four directories obj, src, target and third-party will be generated in project directory.
|
|
34
|
+
in src, directory 'components' will be generated if [mode] is '--lib', an extra main.cpp will be generated if [mode] is '--bin'
|
|
35
|
+
|
|
36
|
+
[mode]: --lib for a library and --bin for executable binaries
|
|
37
|
+
[suffixes]: should be in 'source_suffix:header_suffix" format, notice the ':' between two suffixes
|
|
38
|
+
add component_name:
|
|
39
|
+
add a folder named tada under workspace/components.
|
|
40
|
+
two files tada.hpp and tada.cpp would be craeted and intialized. File suffix may differ according users' specifications.
|
|
41
|
+
if component_name is a path separated by '/', then canoe would create folders and corresponding files recursively.
|
|
42
|
+
|
|
43
|
+
generate:
|
|
44
|
+
generate dependence relationship for each file, this may accelarate
|
|
45
|
+
`canoe buid` command. It's recommanded to execute this command everytime
|
|
46
|
+
headers are added or removed from any file.
|
|
47
|
+
|
|
48
|
+
update:
|
|
49
|
+
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.
|
|
50
|
+
So when a file includes new headers or some headers are removed, users have to use 'canoe udpate'
|
|
51
|
+
to update dependency relationships.
|
|
52
|
+
|
|
53
|
+
build [all|test]:
|
|
54
|
+
build current project, 'all' builds both target and tests, 'test' builds tests only
|
|
55
|
+
|
|
56
|
+
test [tests]:
|
|
57
|
+
build and run tests
|
|
58
|
+
[tests]: 'all' for all tests, or a name of a test for a single test
|
|
59
|
+
|
|
60
|
+
run [options]:
|
|
61
|
+
build current project with no specific compilation flags, and run this project, passing [options] as command line arguments to the binary
|
|
62
|
+
|
|
63
|
+
clean:
|
|
64
|
+
remove all generated object files and binary files
|
|
65
|
+
|
|
66
|
+
help:
|
|
67
|
+
show this help message
|
|
68
|
+
|
|
69
|
+
verion:
|
|
70
|
+
display version information
|
|
71
|
+
|
|
72
|
+
dep:
|
|
73
|
+
display file dependencies in a better readable way
|
|
74
|
+
|
|
75
|
+
make:
|
|
76
|
+
generate a Makefile for this project
|
|
77
|
+
|
|
78
|
+
@author: written by XIONG Ziwei, ICT, CAS
|
|
79
|
+
@contact: noahxiong@outlook.com
|
|
80
|
+
INFO
|
|
81
|
+
puts info
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# If the project has circular dependency, this command would fail
|
|
2
|
+
module Canoe
|
|
3
|
+
class Makefile
|
|
4
|
+
include WorkSpaceUtil
|
|
5
|
+
def initialize(workspace)
|
|
6
|
+
@workspace = workspace
|
|
7
|
+
@all_names = []
|
|
8
|
+
@common_variables = {}
|
|
9
|
+
@src_variables = {}
|
|
10
|
+
@hdr_variables = {}
|
|
11
|
+
@obj_variables = {}
|
|
12
|
+
@config = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def configure(config)
|
|
16
|
+
@config = config
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def make!(deps)
|
|
20
|
+
File.open('Makefile', 'w') do |f|
|
|
21
|
+
if cxx?(get_compiler)
|
|
22
|
+
make_cxx(f, deps)
|
|
23
|
+
else
|
|
24
|
+
make_c(f, deps)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def get_compiler
|
|
32
|
+
@config['compiler']
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def get_header_suffix
|
|
36
|
+
@workspace.header_suffix
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def get_source_suffix
|
|
40
|
+
@workspace.source_suffix
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def get_compiling_flags
|
|
44
|
+
flags = @config['flags']['compile'].values.join ' '
|
|
45
|
+
flags + ' -I./src/components'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def get_ldflags
|
|
49
|
+
@config['flags']['link'].values.select { |v| v.start_with?('-L') }.join ' '
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def get_ldlibs
|
|
53
|
+
(@config['flags']['link'].values - (get_ldflags.split)).join ' '
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def cxx?(name)
|
|
57
|
+
return get_compiler.end_with? '++'
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def make_cxx(makefile, deps)
|
|
61
|
+
make_common(makefile, 'CXX', deps)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def make_c(makefile, deps)
|
|
65
|
+
make_common(makefile, 'CC', deps)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def make_common(makefile, compiler_prefix, deps)
|
|
69
|
+
make_compiling_info(makefile, compiler_prefix)
|
|
70
|
+
define_variables(makefile, deps)
|
|
71
|
+
make_rules(makefile, deps)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def make_compiling_info(makefile, compiler_prefix)
|
|
75
|
+
makefile.puts("#{compiler_prefix}=#{get_compiler}")
|
|
76
|
+
makefile.puts("#{compiler_prefix}FLAGS=#{get_compiling_flags}")
|
|
77
|
+
makefile.puts("LDFLAGS=#{get_ldflags}")
|
|
78
|
+
makefile.puts("LDLIBS=#{get_ldlibs}")
|
|
79
|
+
makefile.puts ''
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def define_variables(makefile, deps)
|
|
83
|
+
define_dirs(makefile)
|
|
84
|
+
src_files = deps.keys.select { |f| f.end_with? get_source_suffix }
|
|
85
|
+
|
|
86
|
+
generate_all_names(src_files)
|
|
87
|
+
define_srcs(makefile, src_files)
|
|
88
|
+
makefile.puts ''
|
|
89
|
+
define_hdrs(makefile, src_files)
|
|
90
|
+
makefile.puts ''
|
|
91
|
+
define_objs(makefile, src_files)
|
|
92
|
+
makefile.puts ''
|
|
93
|
+
define_tests(makefile, src_files)
|
|
94
|
+
makefile.puts ''
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def extract_name(name, _)
|
|
98
|
+
File.basename(file_to_obj(name), '.*')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def generate_all_names(files)
|
|
102
|
+
files.each do |f|
|
|
103
|
+
name = extract_name(f, @workspace.components_prefix).upcase
|
|
104
|
+
@all_names << name
|
|
105
|
+
@src_variables[name] = f
|
|
106
|
+
@hdr_variables[name] = f.gsub @workspace.source_suffix, @workspace.header_suffix
|
|
107
|
+
@obj_variables[name] = file_to_obj(f)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def define_dirs(makefile)
|
|
112
|
+
makefile.puts("TARGET_DIR=./target")
|
|
113
|
+
if @workspace.mode == :bin
|
|
114
|
+
makefile.puts("TARGET=$(TARGET_DIR)/#{@workspace.name}")
|
|
115
|
+
else
|
|
116
|
+
makefile.puts("TARGET=$(TARGET_DIR)/lib#{@workspace.name.downcase}.so")
|
|
117
|
+
end
|
|
118
|
+
# note the ending slash
|
|
119
|
+
makefile.puts("OBJ_DIR=#{@workspace.obj_prefix[..-2]}")
|
|
120
|
+
makefile.puts("SRC_DIR=#{@workspace.src_prefix[..-2]}")
|
|
121
|
+
makefile.puts("COMPONENTS_DIR=#{@workspace.components_prefix[..-2]}")
|
|
122
|
+
makefile.puts ""
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def define_srcs(makefile, files)
|
|
126
|
+
@src_variables.each do |k, v|
|
|
127
|
+
makefile.puts("SRC_#{k}=#{v}")
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def define_hdrs(makefile, files)
|
|
132
|
+
@hdr_variables.each do |k, v|
|
|
133
|
+
next if k == "MAIN"
|
|
134
|
+
makefile.puts("HDR_#{k}=#{v}") if File.exist? v
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def define_objs(makefile, files)
|
|
139
|
+
@obj_variables.each do |k, v|
|
|
140
|
+
makefile.puts("OBJ_#{k}=#{v}")
|
|
141
|
+
end
|
|
142
|
+
objs = @obj_variables.keys.map { |k| "$(OBJ_#{k})" }
|
|
143
|
+
bin_objs = objs.reject { |o| o.start_with? '$(OBJ_TEST'}
|
|
144
|
+
test_objs = objs - bin_objs
|
|
145
|
+
makefile.puts ''
|
|
146
|
+
makefile.puts("OUT_OBJS=#{bin_objs.join ' '}")
|
|
147
|
+
makefile.puts("TEST_OBJS=#{test_objs.join ' '}")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def define_tests(makefile, files)
|
|
151
|
+
test_files = files.select { |f| File.basename(f, '.*').start_with? 'test_'}
|
|
152
|
+
test_files.each do |f|
|
|
153
|
+
basename = File.basename(f, '.*')
|
|
154
|
+
test = "#{@workspace.target_short}/#{basename}"
|
|
155
|
+
makefile.puts("#{basename.upcase}=#{test}")
|
|
156
|
+
end
|
|
157
|
+
tests = test_files.map do |f|
|
|
158
|
+
"$(#{File.basename(f, '.*').upcase})"
|
|
159
|
+
end
|
|
160
|
+
makefile.puts("TESTS=#{tests.join ' '}")
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def get_all_dep_name(file_name, deps)
|
|
164
|
+
dep = deps[file_name]
|
|
165
|
+
if dep.empty?
|
|
166
|
+
[]
|
|
167
|
+
else
|
|
168
|
+
tmp = dep.map { |n| extract_name(n, @workspace.components_prefix).upcase }
|
|
169
|
+
dep.each do |d|
|
|
170
|
+
tmp += get_all_dep_name(d, deps)
|
|
171
|
+
end
|
|
172
|
+
tmp
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def emit_dependencies(makefile, name, deps)
|
|
177
|
+
as_str = deps.map do |n|
|
|
178
|
+
if n == name
|
|
179
|
+
["$(SRC_#{n})"] + ["$(HDR_#{n})"] * (name == "MAIN" ? 0 : 1)
|
|
180
|
+
else
|
|
181
|
+
"$(#{n}_DEP)"
|
|
182
|
+
end
|
|
183
|
+
end.flatten.join " "
|
|
184
|
+
makefile.puts("#{name}_DEP=#{as_str}")
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def make_dependencies(makefile, deps)
|
|
188
|
+
dep_variables = Hash[@all_names.map { |n| [n, []] }]
|
|
189
|
+
reference = Hash[@all_names.map { |n| [n, []] }]
|
|
190
|
+
@all_names.each do |n|
|
|
191
|
+
dep_variables[n] = ([n] + get_all_dep_name(@src_variables[n], deps)).uniq
|
|
192
|
+
reference[n] = ([n] + get_all_dep_name(@src_variables[n], deps)).uniq
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# deduplication
|
|
196
|
+
dep_variables.each do |k, v|
|
|
197
|
+
v.each do |n|
|
|
198
|
+
next if n == k
|
|
199
|
+
v = v - reference[n] + [n] if v.include? n
|
|
200
|
+
end
|
|
201
|
+
dep_variables[k] = v
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
dep_variables.each do |k, v|
|
|
205
|
+
emit_dependencies(makefile, k, v)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def make_obj_rules(makefile, deps)
|
|
210
|
+
cmplr = cxx?(get_compiler) ? 'CXX' : 'CC'
|
|
211
|
+
makefile.puts("all: OUT\n")
|
|
212
|
+
makefile.puts ''
|
|
213
|
+
|
|
214
|
+
@all_names.each do |n|
|
|
215
|
+
makefile.puts("$(OBJ_#{n}): $(#{n}_DEP)\n\t$(#{cmplr}) $(#{cmplr}FLAGS) -o $@ -c $(SRC_#{n})")
|
|
216
|
+
makefile.puts ''
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def make_out_rules(makefile, deps)
|
|
222
|
+
cmplr = cxx?(get_compiler) ? 'CXX' : 'CC'
|
|
223
|
+
if @workspace.mode == :bin
|
|
224
|
+
makefile.puts("OUT: $(OUT_OBJS)\n\t$(#{cmplr}) $(#{cmplr}FLAGS) -o $(TARGET) $(OUT_OBJS) $(LDFLAGS) $(LDLIBS)")
|
|
225
|
+
else
|
|
226
|
+
makefile.puts("OUT: $(OUT_OBJS)\n\t$(#{cmplr}) $(#{cmplr}FLAGS) -shared -o $(TARGET) $(OUT_OBJS) -fPIC $(LDFLAGS) $(LDLIBS)")
|
|
227
|
+
end
|
|
228
|
+
makefile.puts ''
|
|
229
|
+
makefile.puts("test: $(TESTS)")
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def make_tests_rules(makefile, deps)
|
|
233
|
+
cmplr = cxx?(get_compiler) ? 'CXX' : 'CC'
|
|
234
|
+
@all_names.each do |n|
|
|
235
|
+
next unless n.start_with? 'TEST_'
|
|
236
|
+
filename = "#{@workspace.tests_short}/#{n.downcase}.#{@workspace.source_suffix}"
|
|
237
|
+
objs = ["$(OBJ_#{n})"] + extract_one_file_obj(filename, deps).map do |o|
|
|
238
|
+
"$(OBJ_#{File.basename(o, '.*').upcase})"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
makefile.puts("$(#{n}): #{objs.join ' '}\n\t$(#{cmplr}) $(#{cmplr}FLAGS) -o $@ $^ $(LDFLAGS) $(LDLIBS)")
|
|
242
|
+
makefile.puts ''
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def make_clean(makefile)
|
|
247
|
+
clean = <<~DOC
|
|
248
|
+
.PHONY: clean
|
|
249
|
+
clean:
|
|
250
|
+
\trm ./target/*
|
|
251
|
+
\trm ./obj/*.o
|
|
252
|
+
DOC
|
|
253
|
+
makefile.puts(clean)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def make_rules(makefile, deps)
|
|
257
|
+
make_dependencies makefile, deps
|
|
258
|
+
makefile.puts ''
|
|
259
|
+
make_obj_rules makefile, deps
|
|
260
|
+
makefile.puts ''
|
|
261
|
+
make_out_rules makefile, deps
|
|
262
|
+
makefile.puts ''
|
|
263
|
+
make_tests_rules makefile, deps
|
|
264
|
+
makefile.puts ''
|
|
265
|
+
make_clean makefile
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
class WorkSpace
|
|
270
|
+
def make
|
|
271
|
+
config = ConfigReader.extract_flags "config.json"
|
|
272
|
+
|
|
273
|
+
deps = target_deps.merge tests_deps
|
|
274
|
+
|
|
275
|
+
makefile = Makefile.new self
|
|
276
|
+
makefile.configure config
|
|
277
|
+
makefile.make! deps
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Canoe
|
|
2
|
+
class WorkSpace
|
|
3
|
+
def new
|
|
4
|
+
begin
|
|
5
|
+
Dir.mkdir(@name)
|
|
6
|
+
rescue SystemCallError
|
|
7
|
+
abort_on_err "workspace #{@name} already exsits"
|
|
8
|
+
end
|
|
9
|
+
Dir.mkdir(@src)
|
|
10
|
+
Dir.mkdir(@components)
|
|
11
|
+
Dir.mkdir("#{@workspace}/obj")
|
|
12
|
+
if @mode == :bin
|
|
13
|
+
DefaultFiles.create_main(@src, @source_suffix)
|
|
14
|
+
else
|
|
15
|
+
DefaultFiles.create_lib_header(@src, @name, @header_suffix)
|
|
16
|
+
end
|
|
17
|
+
File.new("#{@workspace}/.canoe", 'w')
|
|
18
|
+
DefaultFiles.create_config @workspace, @source_suffix, @header_suffix
|
|
19
|
+
# DefaultFiles.create_emacs_dir_local @workspace
|
|
20
|
+
|
|
21
|
+
Dir.mkdir(@third)
|
|
22
|
+
Dir.mkdir(@target)
|
|
23
|
+
Dir.mkdir(@tests)
|
|
24
|
+
Dir.chdir(@workspace) do
|
|
25
|
+
system 'git init'
|
|
26
|
+
system 'canoe add tests'
|
|
27
|
+
end
|
|
28
|
+
puts "workspace #{@workspace.blue} is created"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|