canoe 0.2.4 → 0.3.1.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/dependence.rb CHANGED
@@ -1,31 +1,54 @@
1
- require_relative 'source_files'
2
- require_relative 'err'
1
+ require_relative "source_files"
2
+ require_relative "err"
3
3
 
4
+ ##
5
+ # class DepAnalyzer
6
+ # This class is the key component of canoe, which offers file dependency analysis functionality.
7
+ # A DepAnalyzer takes a directory as input, sources files and corresponding header files in this
8
+ # directory should have same name, e.g. test.cpp and test.hpp.
9
+ # DepAnalyzer would read every source file and recursively process user header files included in this source file to
10
+ # find out all user header files this source file depends on.
11
+ # Based on dependencies built in previous stage, DepAnalyzer determines which files should be recompiled and return
12
+ # these files to caller.
13
+ #
14
+ # Dependencies could be written to a file to avoid wasting time parsing all files, Depanalyzer would read from
15
+ # this file to construct dependencies. But if sources files included new headers or included headers are revmoed,
16
+ # Depanalyzer should rebuild the whole dependencies.
4
17
  class DepAnalyzer
5
18
  include Err
6
19
  def self.read_from(filename)
7
20
  File.open(filename, "r") do |f|
8
21
  ret = Hash.new []
9
22
  f.each_with_index do |line, i|
10
- entry = line.split(': ')
11
- Err.abort_on_err("Bad .canoe.deps format, line #{i+1}") unless entry.length == 2
23
+ entry = line.split(": ")
24
+ Err.abort_on_err("Bad .canoe.deps format, line #{i + 1}") unless entry.length == 2
12
25
  ret[entry[0]] = entry[1].split
13
- end
26
+ end
14
27
  ret
15
28
  end
16
29
  end
17
30
 
18
- def self.compiling_filter(deps, build_time, src_sfx='cpp', hdr_sfx='hpp')
19
- files = []
31
+ def self.compiling_filter(deps, build_time, src_sfx = "cpp", hdr_sfx = "hpp")
32
+ files = []
33
+ @processed = {}
34
+ @recompiles = {}
35
+ deps.keys.each do |k|
36
+ @processed[k] = false
37
+ @recompiles[k] = false
38
+ end
20
39
  deps.each do |k, v|
21
40
  next if k.end_with? ".#{hdr_sfx}"
22
41
  if should_recompile?(k, build_time)
23
42
  files << k
43
+ @processed[k] = true
44
+ @recompiles[k] = true
24
45
  next
25
46
  end
26
47
  v.each do |f|
27
48
  if mark(f, build_time, deps) || mark(f.sub(".#{hdr_sfx}", ".#{src_sfx}"), build_time, deps)
28
49
  files << k
50
+ @processed[k] = true
51
+ @recompiles[k] = true
29
52
  break
30
53
  end
31
54
  end
@@ -34,22 +57,36 @@ class DepAnalyzer
34
57
  end
35
58
 
36
59
  private
60
+
37
61
  def self.mark(file, build_time, deps)
62
+ ret = false
38
63
  return false unless File.exists? file
39
64
  if should_recompile?(file, build_time)
40
65
  return true
41
66
  else
42
67
  deps[file].each do |f|
43
- return true if mark(f, build_time, deps)
68
+ if @processed[f]
69
+ ret |= @recompiles[f]
70
+ next
71
+ end
72
+ @processed[f] = true
73
+ if mark(f, build_time, deps)
74
+ @recompiles[f] = true
75
+ return true
76
+ end
44
77
  end
45
78
  end
46
- false
79
+ ret
47
80
  end
48
81
 
49
- def self.should_recompile?(file, build_time)
82
+ def self.should_recompile?(file, build_time)
50
83
  judge = build_time
51
84
  if build_time == Time.new(0)
52
- objfile = "./obj/#{File.basename(file, ".*")}.o"
85
+ objfile = if file.start_with?("./src/components")
86
+ "./obj/" + file.delete_suffix(File.extname(file))["./src/components/".length..].gsub("/", "_") + ".o"
87
+ else
88
+ "./obj/#{File.basename(file, ".*")}.o"
89
+ end
53
90
  return true unless File.exists? objfile
54
91
  judge = File.mtime(objfile)
55
92
  end
@@ -57,7 +94,8 @@ class DepAnalyzer
57
94
  end
58
95
 
59
96
  public
60
- def initialize(dir, src_sfx='cpp', hdr_sfx='hpp')
97
+
98
+ def initialize(dir, src_sfx = "cpp", hdr_sfx = "hpp")
61
99
  @dir = dir
62
100
  @deps = Hash.new []
63
101
  @source_suffix = src_sfx
@@ -79,25 +117,26 @@ class DepAnalyzer
79
117
 
80
118
  def build_to_file(include_path, filename)
81
119
  build_dependence include_path
82
-
120
+
83
121
  File.open(filename, "w") do |f|
84
122
  @deps.each do |k, v|
85
123
  f.write "#{k}: #{v.join(" ")}\n"
86
124
  end
87
125
  end
88
-
126
+
89
127
  @deps
90
128
  end
91
129
 
92
130
  private
93
- def get_all_headers(include_path, file, suffix='hpp')
131
+
132
+ def get_all_headers(include_path, file, suffix = "hpp")
94
133
  File.open(file, "r") do |f|
95
134
  ret = []
96
135
  if file.end_with?(".#{@source_suffix}")
97
136
  header = file.sub(".#{@source_suffix}", ".#{@header_suffix}")
98
137
  ret += [header] if File.exists?(header)
99
138
  end
100
-
139
+
101
140
  f.each_line do |line|
102
141
  if mat = line.match(/include "(.+\.#{suffix})"/)
103
142
  include_path.each do |path|
@@ -106,7 +145,7 @@ class DepAnalyzer
106
145
  end
107
146
  end
108
147
  end
109
-
148
+
110
149
  ret.uniq
111
150
  end
112
151
  end
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