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,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,14 @@
1
+ module Canoe
2
+ class WorkSpace
3
+ def dep
4
+ deps = DepAnalyzer.read_from(@deps) if File.exist?(@deps)
5
+ deps.each do |k, v|
6
+ next if v.empty?
7
+
8
+ puts "#{k.blue} depends on: "
9
+ v.each { |f| puts " #{f.blue}" }
10
+ puts ''
11
+ end
12
+ end
13
+ end
14
+ 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