canoe 0.2.3 → 0.3.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.
data/lib/err.rb CHANGED
@@ -1,7 +1,9 @@
1
+ require_relative "coloring"
2
+
1
3
  module Err
2
4
  def warn_on_err(err)
3
5
  puts <<~ERR
4
- Warning:
6
+ #{"Waring: ".yellow}
5
7
  #{err}
6
8
  try 'canoe help' for more information
7
9
  ERR
@@ -9,10 +11,10 @@ module Err
9
11
 
10
12
  def abort_on_err(err)
11
13
  abort <<~ERR
12
- Fatal:
14
+ #{"Fatal: ".red}
13
15
  #{err}
14
16
  try 'canoe help' for more information
15
- ERR
17
+ ERR
16
18
  end
17
19
 
18
20
  module_function :warn_on_err, :abort_on_err
data/lib/source_files.rb CHANGED
@@ -1,8 +1,11 @@
1
+ ##
2
+ # class SourceFiles
3
+ # A simple class to assist collect all files or some files in a directory.
1
4
  class SourceFiles
2
5
  class << self
3
6
  def get_all(dir, &block)
4
7
  @files = []
5
- get_all_helper(dir, &block)
8
+ get_all_helper(dir, &block)
6
9
  @files
7
10
  end
8
11
 
@@ -16,13 +19,14 @@ class SourceFiles
16
19
  else
17
20
  @files << "#{file}"
18
21
  end
19
- end
22
+ end
20
23
  end
21
-
24
+
22
25
  @files
23
26
  end
24
27
 
25
28
  private
29
+
26
30
  def get_all_helper(dir, &block)
27
31
  Dir.each_child(dir) do |f|
28
32
  file = "#{dir}/#{f}"
@@ -39,4 +43,3 @@ class SourceFiles
39
43
  end
40
44
  end
41
45
  end
42
-
@@ -0,0 +1,27 @@
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
11
+ FileUtils.mkdir dir
12
+ Dir.chdir(dir) do
13
+ puts "created " + Dir.pwd.blue
14
+ create_working_files prefix.join("__"), filename
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
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
26
+ end
27
+ end
@@ -0,0 +1,131 @@
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
12
+ end
13
+ end
14
+
15
+ private
16
+
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
26
+ else
27
+ abort_on_err "unknown options in config.json, #{v}"
28
+ end
29
+ end
30
+ end
31
+
32
+ def build_compiler_from_config
33
+ Dir.chdir(@workspace) do
34
+ flags = ConfigReader.extract_flags "config.json"
35
+ compiler_name = flags["compiler"] ? flags["compiler"] : "clang++"
36
+ abort_on_err "compiler #{compiler_name} not found" unless File.exists?("/usr/bin/#{compiler_name}")
37
+ compiler_flags = ["-Isrc/components"]
38
+ linker_flags = []
39
+
40
+ c_flags, l_flags = flags["flags"]["compile"], flags["flags"]["link"]
41
+ build_flags(compiler_flags, c_flags)
42
+ build_flags(linker_flags, l_flags)
43
+
44
+ @compiler = Compiler.new compiler_name, compiler_flags, linker_flags
45
+ end
46
+ end
47
+
48
+ def compile(f, o)
49
+ @compiler.compile f, o
50
+ end
51
+
52
+ def link_exectutable(odir, objs)
53
+ puts "#{"[100%]".green} linking"
54
+ @compiler.link_executable "#{odir}/#{@name}", objs
55
+ end
56
+
57
+ def link_shared(odir, objs)
58
+ puts "#{"[100%]".green} linking"
59
+ @compiler.link_shared "#{odir}/lib#{@name}", objs
60
+ end
61
+
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
69
+ end
70
+ end
71
+
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
80
+ end
81
+ end
82
+
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
110
+
111
+ def build_all
112
+ build_target
113
+ build_test
114
+ end
115
+
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)
123
+
124
+ if files.empty? && File.exist?(target)
125
+ puts "nothing to do, all up to date"
126
+ return
127
+ end
128
+
129
+ self.send "build_#{@mode.to_s}", files
130
+ end
131
+ end
@@ -0,0 +1,27 @@
1
+ class WorkSpace
2
+ def clean
3
+ self.send "clean_#{@mode.to_s}"
4
+ end
5
+
6
+ private
7
+
8
+ def clean_obj
9
+ puts "rm -f ./obj/*.o"
10
+ system "rm -f ./obj/*.o"
11
+ end
12
+
13
+ def clean_target
14
+ puts "rm -f ./target/*"
15
+ system "rm -f ./target/*"
16
+ end
17
+
18
+ def clean_bin
19
+ clean_obj
20
+ clean_target
21
+ end
22
+
23
+ def clean_lib
24
+ clean_obj
25
+ clean_target
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ class WorkSpace
2
+ def dep
3
+ deps = DepAnalyzer.read_from(@deps) if File.exist?(@deps)
4
+ deps.each do |k, v|
5
+ unless v.empty?
6
+ puts "#{k.blue} depends on: "
7
+ v.each { |f| puts " #{f.blue}" }
8
+ puts ""
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ class WorkSpace
2
+ def generate
3
+ DepAnalyzer.new("./src", @source_suffix, @header_suffix).build_to_file ["./src", "./src/components"], @deps
4
+ end
5
+ end
@@ -0,0 +1,76 @@
1
+ class WorkSpace
2
+ def self.help
3
+ info = <<~INFO
4
+ canoe is a C/C++ project manager, inspired by Rust cargo.
5
+ usage:
6
+ canoe new tada: create a project named 'tada' in current directory
7
+
8
+ canoe build: compile current project (execute this command in project directory)
9
+
10
+ canoe generate: generate dependency relationships and store it in '.canoe.deps' file. Alias: update
11
+
12
+ canoe update: udpate dependency relationships and store it in '.canoe.deps' file.
13
+
14
+ canoe run: compile and execute current project (execute this command in project directory)
15
+
16
+ canoe clean: remove all generated object files and binary files
17
+
18
+ canoe help: show this help message
19
+
20
+ canoe add tada: add a folder named tada under workspace/components,
21
+
22
+ canoe dep: show current dependency relationships of current project
23
+
24
+ canoe verion: version information
25
+
26
+ canoe make: generate a makefile for this project
27
+
28
+ new project_name [mode] [suffixes]:
29
+ create a new project with project_name.
30
+ In this project, four directories obj, src, target and third-party will be generated in project directory.
31
+ in src, directory 'components' will be generated if [mode] is '--lib', an extra main.cpp will be generated if [mode] is '--bin'
32
+
33
+ [mode]: --lib for a library and --bin for executable binaries
34
+ [suffixes]: should be in 'source_suffix:header_suffix" format, notice the ':' between two suffixes
35
+ add component_name:
36
+ add a folder named tada under workspace/components.
37
+ two files tada.hpp and tada.cpp would be craeted and intialized. File suffix may differ according users' specifications.
38
+ if component_name is a path separated by '/', then canoe would create folders and corresponding files recursively.
39
+
40
+ generate:
41
+ generate dependence relationship for each file, this may accelarate
42
+ `canoe buid` command. It's recommanded to execute this command everytime
43
+ headers are added or removed from any file.
44
+
45
+ update:
46
+ 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.
47
+ So when a file includes new headers or some headers are removed, users have to use 'canoe udpate'
48
+ to update dependency relationships.
49
+
50
+ build [options]:
51
+ build current project, arguments in [options] will be passed to C++ compiler
52
+
53
+ run [options]:
54
+ build current project with no specific compilation flags, and run this project, passing [options] as command line arguments to the binary
55
+
56
+ clean:
57
+ remove all generated object files and binary files
58
+
59
+ help:
60
+ show this help message
61
+
62
+ verion:
63
+ display version information
64
+
65
+ dep:
66
+ display file dependencies in a better readable way
67
+
68
+ make:
69
+ generate a Makefile for this project
70
+
71
+ @author: written by XIONG Ziwei, ICT, CAS
72
+ @contact: noahxiong@outlook.com
73
+ INFO
74
+ puts info
75
+ end
76
+ end
@@ -0,0 +1,232 @@
1
+ class Makefile
2
+ def initialize(workspace)
3
+ @workspace = workspace
4
+ @all_names = []
5
+ @common_variables = {}
6
+ @src_variables = {}
7
+ @hdr_variables = {}
8
+ @obj_variables = {}
9
+ @config = {}
10
+ end
11
+
12
+ def configure(config)
13
+ @config = config
14
+ end
15
+
16
+ def make!(deps)
17
+ File.open("Makefile", "w") do |f|
18
+ if cxx?(get_compiler)
19
+ make_cxx(f, deps)
20
+ else
21
+ make_c(f, deps)
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def get_compiler
29
+ @config["compiler"]
30
+ end
31
+
32
+ def get_header_suffix
33
+ @workspace.header_suffix
34
+ end
35
+
36
+ def get_source_suffix
37
+ @workspace.source_suffix
38
+ end
39
+
40
+ def get_compiling_flags
41
+ flags = @config["flags"]["compile"].values.join " "
42
+ flags + " -I./src/components"
43
+ end
44
+
45
+ def get_ldflags
46
+ @config["flags"]["link"].values.select { |v| v.start_with?("-L") }.join " "
47
+ end
48
+
49
+ def get_ldlibs
50
+ (@config["flags"]["link"].values - (get_ldflags.split)).join " "
51
+ end
52
+
53
+ def cxx?(name)
54
+ return get_compiler.end_with? "++"
55
+ end
56
+
57
+ def make_cxx(makefile, deps)
58
+ make_common(makefile, "CXX", deps)
59
+ end
60
+
61
+ def make_c(makefile, deps)
62
+ make_common(makefile, "CC", deps)
63
+ end
64
+
65
+ def make_common(makefile, compiler_prefix, deps)
66
+ make_compiling_info(makefile, compiler_prefix)
67
+ define_variables(makefile, deps)
68
+ make_rules(makefile, deps)
69
+ end
70
+
71
+ def make_compiling_info(makefile, compiler_prefix)
72
+ makefile.puts("#{compiler_prefix}=#{get_compiler}")
73
+ makefile.puts("#{compiler_prefix}FLAGS=#{get_compiling_flags}")
74
+ makefile.puts("LDFLAGS=#{get_ldflags}")
75
+ makefile.puts("LDLIBS=#{get_ldlibs}")
76
+ makefile.puts ""
77
+ end
78
+
79
+ def define_variables(makefile, deps)
80
+ define_dirs(makefile)
81
+
82
+ src_files = deps.keys.select { |f| f.end_with? get_source_suffix }
83
+ generate_all_names(src_files)
84
+ define_srcs(makefile, src_files)
85
+ makefile.puts ""
86
+ define_hdrs(makefile, src_files)
87
+ makefile.puts ""
88
+ define_objs(makefile, src_files)
89
+ makefile.puts ""
90
+ end
91
+
92
+ def extract_name(name, prefix)
93
+ if name.start_with?(prefix)
94
+ name.delete_suffix(File.extname(name))[prefix.length..].gsub("/", "_")
95
+ else
96
+ File.basename(name.split("/")[-1], ".*")
97
+ end
98
+ end
99
+
100
+ def generate_all_names(files)
101
+ files.each do |f|
102
+ name = extract_name(f, @workspace.components_prefix).upcase
103
+ @all_names << name
104
+ @src_variables[name] = f
105
+ @hdr_variables[name] = f.gsub(@workspace.source_suffix, @workspace.header_suffix)
106
+ @obj_variables[name] = @workspace.obj_prefix + extract_name(f, @workspace.components_prefix) + ".o"
107
+ end
108
+ end
109
+
110
+ def define_dirs(makefile)
111
+ makefile.puts("TARGET_DIR=./target")
112
+ if @workspace.mode == :bin
113
+ makefile.puts("TARGET=$(TARGET_DIR)/#{@workspace.name}")
114
+ else
115
+ makefile.puts("TARGET=$(TARGET_DIR)/lib#{@workspace.name.downcase}.so")
116
+ end
117
+ # note the ending slash
118
+ makefile.puts("OBJ_DIR=#{@workspace.obj_prefix[..-2]}")
119
+ makefile.puts("SRC_DIR=#{@workspace.src_prefix[..-2]}")
120
+ makefile.puts("COMPONENTS_DIR=#{@workspace.components_prefix[..-2]}")
121
+ makefile.puts ""
122
+ end
123
+
124
+ def define_srcs(makefile, files)
125
+ @src_variables.each do |k, v|
126
+ makefile.puts("SRC_#{k}=#{v}")
127
+ end
128
+ end
129
+
130
+ def define_hdrs(makefile, files)
131
+ @hdr_variables.each do |k, v|
132
+ next if k == "MAIN"
133
+ makefile.puts("HDR_#{k}=#{v}")
134
+ end
135
+ end
136
+
137
+ def define_objs(makefile, files)
138
+ @obj_variables.each do |k, v|
139
+ makefile.puts("OBJ_#{k}=#{v}")
140
+ end
141
+ objs = @obj_variables.keys.map { |k| "$(OBJ_#{k})" }.join " "
142
+ makefile.puts("OBJS=#{objs}")
143
+ makefile.puts ""
144
+ end
145
+
146
+ def get_all_dep_name(file_name, deps)
147
+ dep = deps[file_name]
148
+ if dep.empty?
149
+ []
150
+ else
151
+ tmp = dep.map { |n| extract_name(n, @workspace.components_prefix).upcase }
152
+ dep.each do |d|
153
+ tmp += get_all_dep_name(d, deps)
154
+ end
155
+ tmp
156
+ end
157
+ end
158
+
159
+ def emit_dependencies(makefile, name, deps)
160
+ as_str = deps.map do |n|
161
+ if n == name
162
+ ["$(SRC_#{n})"] + ["$(HDR_#{n})"] * (name == "MAIN" ? 0 : 1)
163
+ else
164
+ "$(#{n}_DEP)"
165
+ end
166
+ end.flatten.join " "
167
+ makefile.puts("#{name}_DEP=#{as_str}")
168
+ end
169
+
170
+ def make_dependencies(makefile, deps)
171
+ dep_variables = Hash[@all_names.map { |n| [n, []] }]
172
+ reference = Hash[@all_names.map { |n| [n, []] }]
173
+ @all_names.each do |n|
174
+ dep_variables[n] = ([n] + get_all_dep_name(@src_variables[n], deps)).uniq
175
+ reference[n] = ([n] + get_all_dep_name(@src_variables[n], deps)).uniq
176
+ end
177
+
178
+ # deduplication
179
+ dep_variables.each do |k, v|
180
+ v.each do |n|
181
+ next if n == k
182
+ v = v - reference[n] + [n] if v.include? n
183
+ end
184
+ dep_variables[k] = v
185
+ end
186
+
187
+ dep_variables.each do |k, v|
188
+ emit_dependencies(makefile, k, v)
189
+ end
190
+ end
191
+
192
+ def make_rules(makefile, deps)
193
+ make_dependencies(makefile, deps)
194
+ makefile.puts ""
195
+ makefile.puts("all: BIN\n")
196
+ makefile.puts ""
197
+ cmplr = cxx?(get_compiler) ? "CXX" : "CC"
198
+ @all_names.each do |n|
199
+ makefile.puts("$(OBJ_#{n}): $(#{n}_DEP)\n\t$(#{cmplr}) $(#{cmplr}FLAGS) -o $(OBJ_#{n}) -c $(SRC_#{n})")
200
+ makefile.puts ""
201
+ end
202
+
203
+ if @workspace.mode == :bin
204
+ makefile.puts("BIN: $(OBJS)\n\t$(#{cmplr}) $(#{cmplr}FLAGS) -o $(TARGET) $(wildcard ./obj/*.o) $(LDFLAGS) $(LDLIBS)")
205
+ else
206
+ makefile.puts("LIB: $(OBJS)\n\t$(#{cmplr}) $(#{cmplr}FLAGS) -shared -o $(TARGET) $(wildcard ./obj/*.o) -fPIC $(LDFLAGS) $(LDLIBS)")
207
+ end
208
+ makefile.puts ""
209
+
210
+ clean = <<~DOC
211
+ .PHONY: clean
212
+ clean:
213
+ \trm $(TARGET)
214
+ \trm ./obj/*.o
215
+ DOC
216
+ makefile.puts(clean)
217
+ end
218
+ end
219
+
220
+ class WorkSpace
221
+ def make
222
+ config = ConfigReader.extract_flags "config.json"
223
+
224
+ deps = File.exist?(@deps) ?
225
+ DepAnalyzer.read_from(@deps) :
226
+ DepAnalyzer.new("./src").build_to_fil
227
+
228
+ makefile = Makefile.new(self)
229
+ makefile.configure(config)
230
+ makefile.make!(deps)
231
+ end
232
+ end