canoe 0.2.2 → 0.3.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f566352f0c1f023e7d5d9481ba4656addf63dfc345efdc4cbdd2f1c5a08197ac
4
- data.tar.gz: 2b02a4504909c6ab773124c78466e6c65533d82f3e1e6d926cb3848cf74469c1
3
+ metadata.gz: acf64e52f86562fbb4103263adf6ac25d3583fdd8cc935df460d6df89ffa4367
4
+ data.tar.gz: 8f3e6ba476da88e1cd34abffb4bfa70e0ac910fb5bc93977601df49e44a75735
5
5
  SHA512:
6
- metadata.gz: 266403dfd576dcfc09757c921e49b2b4d6c49fd5dfd1666fb457ba3d29f6c7145524c511740da8e5418781012bec858b1a34bb60aea8f7b9b03cd6f7890016bd
7
- data.tar.gz: 21cbee6eb840e3d4d97cc466527819fae84c7ef6b406678a1d97ea0dc5cbd785241ce92349d3045b0c86a829b5eff2a5eb879f7341fda522f1dcbc6fcee789fd
6
+ metadata.gz: e39ce423d813c64ef43c4440990439463a99dcb724771adc2d4d5db130c8206d7a3801c9fd036ee1477cd5c162f19b3da3dbc123e6114cba5bc7d8c0bf7d03a0
7
+ data.tar.gz: fe49d0b455bb21deb30b149f08736e2cff7091d8a3d38f87ac395fa1800c8b9abdb7c17594007412f5aacce448da5ebb60fd9a986fbf2ab645737aa59cd9100f
data/bin/canoe CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # require_relative '../lib/canoe'
2
3
  require 'canoe'
3
4
 
4
5
  Canoe.new.parse ARGV
@@ -1,10 +1,20 @@
1
- require_relative "workspace"
1
+ require_relative "workspace/workspace.rb"
2
2
  require_relative "cmd"
3
3
  require_relative "source_files"
4
4
 
5
5
  class Canoe
6
6
  def initialize
7
- options = ["new", "build", "run", "clean", "help", "add", "generate", "deps", "version"]
7
+ options = ['new',
8
+ 'add',
9
+ 'build',
10
+ 'generate',
11
+ 'run',
12
+ 'dep',
13
+ 'clean',
14
+ 'version',
15
+ 'help',
16
+ 'update',
17
+ 'test']
8
18
  @cmd = CmdParser.new options
9
19
  end
10
20
 
data/lib/cmd.rb CHANGED
@@ -1,7 +1,10 @@
1
- require_relative "workspace"
1
+ require_relative "workspace/workspace"
2
2
  require_relative "err"
3
3
  require_relative "config_reader"
4
4
 
5
+ ##
6
+ # class CmdParser
7
+ # Parsing command arguments passed to canoe
5
8
  class CmdParser
6
9
  include Err
7
10
  def initialize(options)
@@ -85,9 +88,13 @@ class CmdParser
85
88
  get_current_workspace.clean
86
89
  end
87
90
 
91
+ def parse_test(args)
92
+ get_current_workspace.test args
93
+ end
94
+
88
95
  def parse_version(args)
89
96
  puts <<~VER
90
- canoe v0.2.1
97
+ canoe v0.3.0.2
91
98
  For features in this version, please visit https://github.com/Dicridon/canoe
92
99
  Currently, canoe can do below:
93
100
  - project creation
@@ -0,0 +1,23 @@
1
+ ##
2
+ # gem Colorize is a great tool, but I don't want add dependencies to Canoe
3
+ class String
4
+ def self.define_coloring_methods
5
+ colors = {
6
+ 30 => :black,
7
+ 31 => :red,
8
+ 32 => :green,
9
+ 33 => :yellow,
10
+ 34 => :blue,
11
+ 35 => :magenta,
12
+ 36 => :cyan,
13
+ 37 => :white
14
+ }
15
+ colors.each do |k, v|
16
+ define_method v do
17
+ "\033[#{k}m#{self}\033[0m"
18
+ end
19
+ end
20
+ end
21
+
22
+ define_coloring_methods
23
+ end
@@ -1,29 +1,49 @@
1
+ ##
2
+ # class Compiler
3
+ # Storing compiler name in String and flags as an array
1
4
  class Compiler
2
5
  attr_reader :name, :flags
3
- def initialize(name, flgs)
6
+ ##
7
+ # @name: String
8
+ # @flgs: Array of String
9
+ def initialize(name, compiling_flags, linking_flags)
4
10
  @name = name
5
- @flags = flgs
11
+ @linking_flags = linking_flags
12
+ @compiling_flags = compiling_flags
6
13
  end
7
14
 
8
- def flags_as_str
9
- flags.join " "
15
+ def compiling_flags_as_str
16
+ @compiling_flags.join " "
10
17
  end
11
18
 
12
- def append_flag(flag)
13
- @flags << flag
19
+ def linking_flags_as_str
20
+ @linking_flags.join " "
21
+ end
22
+
23
+
24
+ def append_compiling_flag(flag)
25
+ @compiling_flags << flag
26
+ end
27
+
28
+ def append_linking_flag(flag)
29
+ @linking_flags << flag
14
30
  end
15
31
 
16
32
  def compile(src, out)
17
- puts "#{name} -o #{out} #{flags_as_str} -c #{src}"
18
- system "#{name} -o #{out} #{flags_as_str} -c #{src}"
33
+ puts "#{name} -o #{out} #{compiling_flags_as_str} -c #{src}"
34
+ system "#{name} -o #{out} #{compiling_flags_as_str} -c #{src}"
19
35
  end
20
36
 
21
- def link(out, objs)
22
- libs = flags.select {|f| f.start_with?('-l')}
23
- puts "#{name} -o #{out} #{objs.join(" ")} #{libs.join(" ")}"
24
- system "#{name} -o #{out} #{objs.join(" ")} #{libs.join(" ")}"
37
+ def link_executable(out, objs)
38
+ puts "#{name} -o #{out} #{objs.join(" ")} #{linking_flags_as_str}"
39
+ system "#{name} -o #{out} #{objs.join(" ")} #{linking_flags_as_str}"
25
40
  end
26
41
 
42
+ def link_shared(out, objs)
43
+ puts "#{name} -shared -o #{out}.so #{objs.join(" ")} #{linking_flags_as_str}"
44
+ system "#{name} -shared -o #{out}.so #{objs.join(" ")} #{linking_flags_as_str}"
45
+ end
46
+
27
47
  def inspect
28
48
  puts "compiler name: #{name.inspect}"
29
49
  puts "compiler flags: #{flags.inspect}"
@@ -1,5 +1,8 @@
1
1
  require 'json'
2
2
 
3
+ ##
4
+ # class ConfigReader
5
+ # Just read a json file
3
6
  class ConfigReader
4
7
  def self.extract_flags(file)
5
8
  abort_on_err("config file #{file} does not exsit") unless File.exists? file
@@ -1,70 +1,93 @@
1
+ ##
2
+ # class DefaultFiles
3
+ # A singleton class to generate header and souce files.
4
+ # TODO: consider using class source_file.rb in Pareater
1
5
  class DefaultFiles
2
- def self.open_file_and_write(filename, content)
3
- File.open(filename, "w") {|f|
4
- f.write(content)
5
- }
6
- end
6
+ class << self
7
+ def open_file_and_write(filename, content)
8
+ File.open(filename, "w") do |f|
9
+ f.write(content)
10
+ end
11
+ end
7
12
 
8
- def self.create_config(path, src_sfx='cpp', hdr_sfx='hpp')
9
- open_file_and_write(
10
- "#{path}/config.json",
11
- <<~CONFIG
12
- {
13
- "compiler": "clang++",
14
- "header-suffix": "#{hdr_sfx}",
15
- "source-suffix": "#{src_sfx}",
16
- "flags": {
17
- "opt": "-O2",
18
- "debug": "-g",
19
- "std": "-std=c++17"
20
- }
21
- }
22
- CONFIG
23
- )
24
- end
13
+ def create_config(path, src_sfx='cpp', hdr_sfx='hpp')
14
+ open_file_and_write(
15
+ "#{path}/config.json",
16
+ <<~CONFIG
17
+ {
18
+ "compiler": "clang++",
19
+ "header-suffix": "#{hdr_sfx}",
20
+ "source-suffix": "#{src_sfx}",
21
+ "flags": {
22
+ "compile": {
23
+ "opt": "-O2",
24
+ "debug": "-g",
25
+ "std": "-std=c++17"
26
+ },
27
+ "link": {
28
+
29
+ }
30
+ }
31
+ }
32
+ CONFIG
33
+ )
34
+ end
25
35
 
26
- def self.create_main(path, suffix='cpp')
27
- open_file_and_write(
28
- "#{path}/main.#{suffix}",
29
- <<~DOC
30
- #include <iostream>
31
- int main(int argc, char *argv[]) {
32
- std::cout << "hello world!" << std::endl;
33
- }
34
- DOC
35
- )
36
- end
36
+ def create_main(path, suffix='cpp')
37
+ open_file_and_write(
38
+ "#{path}/main.#{suffix}",
39
+ <<~DOC
40
+ #include <iostream>
41
+ int main(int argc, char *argv[]) {
42
+ std::cout << "hello world!" << std::endl;
43
+ }
44
+ DOC
45
+ )
46
+ end
37
47
 
38
- def self.create_emacs_dir_local(path)
39
- open_file_and_write(
40
- "#{path}/.dir-locals.el",
41
- <<~DOC
42
- ((nil . ((company-clang-arguments . ("-I./src/components/"
43
- "-I./components/"))))
44
- (nil . ((company-c-headers-path-user . ("./src/components/"
45
- "./components/")))))
46
- DOC
47
- )
48
- end
48
+ def create_lib_header(path, lib_name, suffix='hpp')
49
+ open_file_and_write(
50
+ "#{path}/#{lib_name}.#{suffix}",
51
+ <<~DOC
52
+ #ifndef __#{lib_name.upcase}__
53
+ #define __#{lib_name.upcase}__
54
+
55
+ #endif
56
+ DOC
57
+ )
58
+ end
49
59
 
50
- def self.create_cpp(filename, src_sfx='cpp', hdr_sfx='hpp')
51
- open_file_and_write(
52
- "#{filename}.#{src_sfx}",
53
- <<~DOC
54
- #include "#{filename}.#{hdr_sfx}"
55
- DOC
56
- )
57
- end
60
+ # def create_emacs_dir_local(path)
61
+ # open_file_and_write(
62
+ # "#{path}/.dir-locals.el",
63
+ # <<~DOC
64
+ # ((nil . ((company-clang-arguments . ("-I./src/components/"
65
+ # "-I./components/"))))
66
+ # (nil . ((company-c-headers-path-user . ("./src/components/"
67
+ # "./components/")))))
68
+ # DOC
69
+ # )
70
+ # end
58
71
 
59
- def self.create_hpp(workspace, prefix, filename, hdr_sfx='hpp')
60
- open_file_and_write(
61
- "#{filename}.#{hdr_sfx}",
62
- <<~DOC
63
- #ifndef __#{workspace.upcase}__#{prefix.upcase}__#{filename.upcase}__
64
- #define __#{workspace.upcase}__#{prefix.upcase}__#{filename.upcase}__
72
+ def create_cpp(filename, src_sfx='cpp', hdr_sfx='hpp')
73
+ open_file_and_write(
74
+ "#{filename}.#{src_sfx}",
75
+ <<~DOC
76
+ #include "#{filename}.#{hdr_sfx}"
77
+ DOC
78
+ )
79
+ end
65
80
 
66
- #endif
67
- DOC
68
- )
81
+ def create_hpp(workspace, prefix, filename, hdr_sfx='hpp')
82
+ open_file_and_write(
83
+ "#{filename}.#{hdr_sfx}",
84
+ <<~DOC
85
+ #ifndef __#{workspace.upcase}__#{prefix.upcase}__#{filename.upcase}__
86
+ #define __#{workspace.upcase}__#{prefix.upcase}__#{filename.upcase}__
87
+
88
+ #endif
89
+ DOC
90
+ )
91
+ end
69
92
  end
70
93
  end
@@ -1,6 +1,19 @@
1
1
  require_relative 'source_files'
2
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, i.e. 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)
@@ -16,16 +29,26 @@ class DepAnalyzer
16
29
  end
17
30
 
18
31
  def self.compiling_filter(deps, build_time, src_sfx='cpp', hdr_sfx='hpp')
19
- files = []
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
@@ -35,21 +58,34 @@ class DepAnalyzer
35
58
 
36
59
  private
37
60
  def self.mark(file, build_time, deps)
61
+ ret = false
38
62
  return false unless File.exists? file
39
63
  if should_recompile?(file, build_time)
40
64
  return true
41
65
  else
42
66
  deps[file].each do |f|
43
- return true if mark(f, build_time, deps)
67
+ if @processed[f]
68
+ ret |= @recompiles[f]
69
+ next
70
+ end
71
+ @processed[f] = true
72
+ if mark(f, build_time, deps)
73
+ @recompiles[f] = true
74
+ return true
75
+ end
44
76
  end
45
77
  end
46
- false
78
+ ret
47
79
  end
48
80
 
49
81
  def self.should_recompile?(file, build_time)
50
82
  judge = build_time
51
83
  if build_time == Time.new(0)
52
- objfile = "./obj/#{File.basename(file, ".*")}.o"
84
+ objfile = if file.start_with?("./src/components")
85
+ './obj/' + file.delete_suffix(File.extname(file))['./src/components/'.length..].gsub('/', '_') + '.o'
86
+ else
87
+ "./obj/#{File.basename(file, ".*")}.o"
88
+ end
53
89
  return true unless File.exists? objfile
54
90
  judge = File.mtime(objfile)
55
91
  end
data/lib/err.rb CHANGED
@@ -1,7 +1,8 @@
1
+ require_relative 'coloring'
1
2
  module Err
2
3
  def warn_on_err(err)
3
4
  puts <<~ERR
4
- Warning:
5
+ #{"Waring: ".yellow}
5
6
  #{err}
6
7
  try 'canoe help' for more information
7
8
  ERR
@@ -9,11 +10,11 @@ module Err
9
10
 
10
11
  def abort_on_err(err)
11
12
  abort <<~ERR
12
- Fatal:
13
+ #{"Fatal: ".red}
13
14
  #{err}
14
15
  try 'canoe help' for more information
15
16
  ERR
16
17
  end
17
18
 
18
19
  module_function :warn_on_err, :abort_on_err
19
- end
20
+ end
@@ -1,3 +1,6 @@
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)
@@ -0,0 +1,26 @@
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
+ def create_working_files(prefix, filename)
23
+ DefaultFiles.create_cpp filename, @source_suffix, @header_suffix
24
+ DefaultFiles.create_hpp @name, prefix, filename, @header_suffix
25
+ end
26
+ end
@@ -0,0 +1,130 @@
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
+ def build_flags(flags, config)
17
+ config.values.each do |v|
18
+ case v
19
+ when String
20
+ flags << v
21
+ when Array
22
+ v.each do |o|
23
+ flags << o
24
+ end
25
+ else
26
+ abort_on_err "unknown options in config.json, #{v}"
27
+ end
28
+ end
29
+ end
30
+
31
+ def build_compiler_from_config
32
+ Dir.chdir(@workspace) do
33
+ flags = ConfigReader.extract_flags "config.json"
34
+ compiler_name = flags['compiler'] ? flags['compiler'] : "clang++"
35
+ abort_on_err "compiler #{compiler_name} not found" unless File.exists?("/usr/bin/#{compiler_name}")
36
+ compiler_flags = ['-Isrc/components']
37
+ linker_flags = []
38
+
39
+ c_flags, l_flags = flags['flags']['compile'], flags['flags']['link']
40
+ build_flags(compiler_flags, c_flags)
41
+ build_flags(linker_flags, l_flags)
42
+
43
+ @compiler = Compiler.new compiler_name, compiler_flags, linker_flags
44
+ end
45
+ end
46
+
47
+ def compile(f, o)
48
+ @compiler.compile f, o
49
+ end
50
+
51
+ def link_exectutable(odir, objs)
52
+ puts "#{"[100%]".green} linking"
53
+ @compiler.link_executable "#{odir}/#{@name}", objs
54
+ end
55
+
56
+ def link_shared(odir, objs)
57
+ puts "#{"[100%]".green} linking"
58
+ @compiler.link_shared "#{odir}/lib#{@name}", objs
59
+ end
60
+
61
+ def build_bin(files)
62
+ # return if files.empty?
63
+ build_compiler_from_config
64
+ if build_common(files) && link_exectutable('./target', Dir.glob("obj/*.o"))
65
+ puts "BUILDING SUCCEEDED".green
66
+ else
67
+ puts "building FAILED".red
68
+ end
69
+ end
70
+
71
+ def build_lib(files)
72
+ # return if files.empty?
73
+ build_compiler_from_config
74
+ @compiler.append_compiling_flag '-fPIC'
75
+ if build_common(files) && link_shared('./target', Dir.glob("obj/*.o"))
76
+ puts "BUILDING SUCCEEDED".green
77
+ else
78
+ puts "building FAILED".red
79
+ end
80
+ end
81
+
82
+ def build_common(files)
83
+ all = SourceFiles.get_all('./src') {|f| f.end_with? @source_suffix}
84
+ total = all.size.to_f
85
+ compiled = total - files.size
86
+ comps = files.select {|f| f.start_with? @components_prefix}
87
+ srcs = files - comps
88
+ flag = true;
89
+
90
+ srcs.each do |f|
91
+ progress = (compiled / total).round(2) * 100
92
+ printf "[#{progress.to_i}%%]".green + " compiling #{f}: "
93
+ fname = f.split("/")[-1]
94
+ o = @obj_prefix + File.basename(fname, ".*") + '.o'
95
+ flag = false unless compile f, o
96
+ compiled += 1
97
+ end
98
+
99
+ comps.each do |f|
100
+ progress = (compiled / total).round(2) * 100
101
+ printf "[#{progress.to_i}%%]".green + " compiling #{f}: "
102
+ o = @obj_prefix + f.delete_suffix(File.extname(f))[@components_prefix.length..]
103
+ .gsub('/', '_') + '.o'
104
+ flag = false unless compile f, o
105
+ compiled += 1
106
+ end
107
+ flag
108
+ end
109
+
110
+ def build_all
111
+ build_target
112
+ build_test
113
+ end
114
+
115
+ def build_target
116
+ deps = File.exist?(@deps) ?
117
+ DepAnalyzer.read_from(@deps) :
118
+ DepAnalyzer.new('./src').build_to_file(['./src', './src/components'], @deps)
119
+ target = "./target/#{@name}"
120
+ build_time = File.exist?(target) ? File.mtime(target) : Time.new(0)
121
+ files = DepAnalyzer.compiling_filter(deps, build_time, @source_suffix, @header_suffix)
122
+
123
+ if files.empty? && File.exist?(target)
124
+ puts "nothing to do, all up to date"
125
+ return
126
+ end
127
+
128
+ self.send "build_#{@mode.to_s}", files
129
+ end
130
+ end
@@ -0,0 +1,26 @@
1
+ class WorkSpace
2
+ def clean
3
+ self.send "clean_#{@mode.to_s}"
4
+ end
5
+
6
+ private
7
+ def clean_obj
8
+ puts "rm -f ./obj/*.o"
9
+ system "rm -f ./obj/*.o"
10
+ end
11
+
12
+ def clean_target
13
+ puts "rm -f ./target/*"
14
+ system "rm -f ./target/*"
15
+ end
16
+
17
+ def clean_bin
18
+ clean_obj
19
+ clean_target
20
+ end
21
+
22
+ def clean_lib
23
+ clean_obj
24
+ clean_target
25
+ end
26
+ 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,6 @@
1
+ class WorkSpace
2
+ def generate
3
+ DepAnalyzer.new('./src', @source_suffix, @header_suffix)
4
+ .build_to_file ['./src', './src/components'], @deps
5
+ end
6
+ end
@@ -0,0 +1,71 @@
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
+ new project_name [mode] [suffixes]:
27
+ create a new project with project_name.
28
+ In this project, four directories obj, src, target and third-party will be generated in project directory.
29
+ in src, directory 'components' will be generated if [mode] is '--lib', an extra main.cpp will be generated if [mode] is '--bin'
30
+
31
+ [mode]: --lib for a library and --bin for executable binaries
32
+ [suffixes]: should be in 'source_suffix:header_suffix" format, notice the ':' between two suffixes
33
+ add component_name:
34
+ add a folder named tada under workspace/components.
35
+ two files tada.hpp and tada.cpp would be craeted and intialized. File suffix may differ according users' specifications.
36
+ if component_name is a path separated by '/', then canoe would create folders and corresponding files recursively.
37
+
38
+ generate:
39
+ generate dependence relationship for each file, this may accelarate
40
+ `canoe buid` command. It's recommanded to execute this command everytime
41
+ headers are added or removed from any file.
42
+
43
+ update:
44
+ 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.
45
+ So when a file includes new headers or some headers are removed, users have to use 'canoe udpate'
46
+ to update dependency relationships.
47
+
48
+ build [options]:
49
+ build current project, arguments in [options] will be passed to C++ compiler
50
+
51
+ run [options]:
52
+ build current project with no specific compilation flags, and run this project, passing [options] as command line arguments to the binary
53
+
54
+ clean:
55
+ remove all generated object files and binary files
56
+
57
+ help:
58
+ show this help message
59
+
60
+ verion:
61
+ display version information
62
+
63
+ dep:
64
+ display file dependencies in a better readable way
65
+
66
+ @author: written by XIONG Ziwei, ICT, CAS
67
+ @contact: noahxiong@outlook.com
68
+ INFO
69
+ puts info
70
+ end
71
+ end
@@ -0,0 +1,28 @@
1
+ class WorkSpace
2
+ def new
3
+ begin
4
+ Dir.mkdir(@name)
5
+ rescue
6
+ abort_on_err "workspace #{@name} already exsits"
7
+ end
8
+ Dir.mkdir(@src)
9
+ Dir.mkdir(@components)
10
+ Dir.mkdir("#{@workspace}/obj")
11
+ if @mode == :bin
12
+ DefaultFiles.create_main(@src, @source_suffix)
13
+ else
14
+ DefaultFiles.create_lib_header(@src, @name, @header_suffix)
15
+ end
16
+ File.new("#{@workspace}/.canoe", "w")
17
+ DefaultFiles.create_config @workspace, @source_suffix, @header_suffix
18
+ # DefaultFiles.create_emacs_dir_local @workspace
19
+
20
+ Dir.mkdir(@third)
21
+ Dir.mkdir(@target)
22
+ Dir.mkdir(@tests)
23
+ Dir.chdir(@workspace) do
24
+ system "git init"
25
+ end
26
+ puts "workspace #{@workspace.blue} is created"
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ class WorkSpace
2
+ def run(args)
3
+ return if @mode == :lib
4
+ build []
5
+ args = args.join " "
6
+ puts "./target/#{@name} #{args}"
7
+ exec "./target/#{@name} #{args}"
8
+ end
9
+ end
@@ -0,0 +1,39 @@
1
+ class WorkSpace
2
+ public
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
+ private
20
+ def test_all
21
+ puts "tests all"
22
+ end
23
+
24
+ def test_single(name)
25
+ puts "#{@tests}/bin/test_#{name}"
26
+ # system "./#{@tests}/bin/test_#{name}"
27
+ end
28
+
29
+ ##
30
+ # how to build:
31
+ # each test file tests one or more components, indicated by included headers
32
+ # find corresponding object file in ../obj and link them to test file
33
+ # TODO
34
+ def build_test
35
+ build
36
+ deps = DepAnalyzer.new('./tests').build_to_file(['./src', './src/components', './tests', './tests/common'], './tests/.canoe.deps')
37
+ puts deps
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ class WorkSpace
2
+ def update
3
+ generate
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ class WorkSpace
2
+ def version
3
+ puts <<~VER
4
+ canoe v0.3.0.2
5
+ For features in this version, please visit https://github.com/Dicridon/canoe
6
+ Currently, canoe can do below:
7
+ - project creation
8
+ - project auto build and run (works like Cargo for Rust)
9
+ - project structure management
10
+ by XIONG Ziwei
11
+ VER
12
+ end
13
+ end
@@ -0,0 +1,53 @@
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
+ require_relative '../coloring'
9
+
10
+
11
+ class WorkSpace
12
+ include Err
13
+ attr_reader :name, :cwd
14
+
15
+ def initialize(name, mode, src_suffix='cpp', hdr_suffix='hpp')
16
+ @name = name
17
+ @compiler = Compiler.new 'clang++', ['-Isrc/components'], []
18
+ @cwd = Dir.new(Dir.pwd)
19
+ @workspace = "#{Dir.pwd}/#{@name}"
20
+ @src = "#{@workspace}/src"
21
+ @components = "#{@src}/components"
22
+ @obj = "#{@workspace}/obj"
23
+ @third = "#{@workspace}/third-party"
24
+ @target = "#{@workspace}/target"
25
+ @tests = "#{@workspace}/tests"
26
+ @mode = mode
27
+ @deps = '.canoe.deps'
28
+
29
+ @src_prefix = './src/'
30
+ @components_prefix = './src/components/'
31
+ @obj_prefix = './obj/'
32
+
33
+ @source_suffix = src_suffix
34
+ @header_suffix = hdr_suffix
35
+ end
36
+
37
+ def inspect
38
+ puts "name is #{@name}"
39
+ puts "name is #{@workspace}"
40
+ end
41
+ end
42
+
43
+ require_relative 'help'
44
+ require_relative 'new'
45
+ require_relative 'add'
46
+ require_relative 'build'
47
+ require_relative 'generate'
48
+ require_relative 'run'
49
+ require_relative 'dep'
50
+ require_relative 'clean'
51
+ require_relative 'version'
52
+ require_relative 'update'
53
+ require_relative 'test'
metadata CHANGED
@@ -1,19 +1,24 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: canoe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0.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-06-23 00:00:00.000000000 Z
11
+ date: 2020-09-06 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: |
14
- Tired of writing Makefile, CMakeList and even SConstruct? Let Canoe help you wipe them out.
13
+ description: |+
14
+ Canoe offers project management and building facilities to C/C++ projects.
15
+
16
+ If you are tired of writing Makefile, CMakeList and even SConstruct, please let Canoe help you wipe them out.
17
+
15
18
  Similar to Cargo for Rust, Canoe offers commands such as new, build, run, etc. to help you generate a C/C++ project and build it automatically.
16
- No more Makefiles, Canoe would analyze dependencies and build like our old friend make if you follow a few conventions over file names
19
+
20
+ Different from tools like Scons and Blade, Canoe requires users to write NO building scripts, Canoe would analyze dependencies and build like our old friend 'make' if a few conventions over file names are followed.
21
+
17
22
  email: noahxiong@outlook.com
18
23
  executables:
19
24
  - canoe
@@ -23,13 +28,25 @@ files:
23
28
  - bin/canoe
24
29
  - lib/canoe.rb
25
30
  - lib/cmd.rb
31
+ - lib/coloring.rb
26
32
  - lib/compiler.rb
27
33
  - lib/config_reader.rb
28
34
  - lib/default_files.rb
29
35
  - lib/dependence.rb
30
36
  - lib/err.rb
31
37
  - lib/source_files.rb
32
- - lib/workspace.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/new.rb
45
+ - lib/workspace/run.rb
46
+ - lib/workspace/test.rb
47
+ - lib/workspace/update.rb
48
+ - lib/workspace/version.rb
49
+ - lib/workspace/workspace.rb
33
50
  homepage: https://github.com/Dicridon/canoe
34
51
  licenses:
35
52
  - MIT
@@ -52,5 +69,5 @@ requirements: []
52
69
  rubygems_version: 3.1.2
53
70
  signing_key:
54
71
  specification_version: 4
55
- summary: a C/C++ project management and build tool
72
+ summary: a C/C++ project management and building tool
56
73
  test_files: []
@@ -1,272 +0,0 @@
1
- require 'fileutils'
2
- require 'open3'
3
- require_relative 'source_files'
4
- require_relative 'compiler'
5
- require_relative 'config_reader'
6
- require_relative 'default_files'
7
- require_relative 'err'
8
- require_relative 'dependence'
9
-
10
- class WorkSpace
11
- include Err
12
- attr_reader :name, :cwd
13
- def self.help
14
- info = <<~INFO
15
- canoe is a C/C++ project manager, inspired by Rust cargo.
16
- usage:
17
- canoe new tada: create a project named 'tada' in current directory
18
-
19
- canoe build: compile current project (execute this command in project directory)
20
-
21
- canoe generate: generate dependency relationship and store it in '.canoe.deps'
22
-
23
- canoe run: compile and execute current project (execute this command in project directory)
24
-
25
- canoe clean: remove all generated object files and binary files
26
-
27
- canoe help: show this help message
28
-
29
- canoe add tada: add a folder named tada under workspace/components,
30
- two files tada.hpp and tada.cpp would be craeted and intialized
31
-
32
- canoe verion: version information
33
-
34
- new project_name [mode] [suffixes]:
35
- create a new project with project_name.
36
- In this project, four directories obj, src, target and third-party will be generated in project directory.
37
- in src, directory 'components' will be generated if [mode] is '--lib', an extra main.cpp will be generated if [mode] is '--bin'
38
-
39
- [mode]: --lib for a library and --bin for executable binaries
40
- [suffixes]: should be in 'source_suffix:header_suffix" format, notice the ':' between two suffixes
41
-
42
- generate:
43
- generate dependence relationship for each file, this may accelarate
44
- `canoe buid` command. It's recommanded to execute this command everytime
45
- headers are added or removed from any file.
46
-
47
- build [options]:
48
- build current project, arguments in [options] will be passed to C++ compiler
49
-
50
- run [options]:
51
- build current project with no specific compilation flags, and run this project, passing [options] as command line arguments to the binary
52
-
53
- clean:
54
- remove all generated object files and binary files
55
-
56
- help:
57
- show this help message
58
-
59
- verion:
60
- display version information
61
-
62
- dep:
63
- display file dependencies in a better readable way
64
-
65
- @author: written by XIONG Ziwei, ICT, CAS
66
- @contact: noahxiong@outlook.com
67
- INFO
68
- puts info
69
- end
70
-
71
-
72
- def initialize(name, mode, src_suffix='cpp', hdr_suffix='hpp')
73
- @name = name
74
- @compiler = Compiler.new 'clang++', '-Isrc/components'
75
- @cwd = Dir.new(Dir.pwd)
76
- @workspace = "#{Dir.pwd}/#{@name}"
77
- @src = "#{@workspace}/src"
78
- @components = "#{@src}/components"
79
- @obj = "#{@workspace}/obj"
80
- @third = "#{@workspace}/third-party"
81
- @target = "#{@workspace}/target"
82
- @mode = mode
83
- @deps = '.canoe.deps'
84
-
85
- @src_prefix = './src/'
86
- @components_prefix = './src/components/'
87
- @obj_prefix = './obj/'
88
-
89
- @source_suffix = src_suffix
90
- @header_suffix = hdr_suffix
91
- end
92
-
93
- def new
94
- Dir.mkdir(@name)
95
- Dir.mkdir(@src)
96
- Dir.mkdir(@components)
97
- Dir.mkdir("#{@workspace}/obj")
98
- DefaultFiles.create_main(@src, @source_suffix) if @mode == :bin
99
- File.new("#{@workspace}/.canoe", "w")
100
- DefaultFiles.create_config @workspace, @source_suffix, @header_suffix
101
- DefaultFiles.create_emacs_dir_local @workspace
102
-
103
- Dir.mkdir(@third)
104
- Dir.mkdir(@target)
105
- puts "workspace #{@workspace} is created"
106
- end
107
-
108
- # args are commandline parameters passed to `canoe build`
109
- def build(args)
110
- deps = File.exist?(@deps) ?
111
- DepAnalyzer.read_from(@deps) :
112
- DepAnalyzer.new('./src').build_to_file(['./src', './src/components'], @deps)
113
- target = "./target/#{@name}"
114
- build_time = File.exist?(target) ? File.mtime(target) : Time.new(0)
115
- files = DepAnalyzer.compiling_filter(deps, build_time, @source_suffix, @header_suffix)
116
-
117
- if files.empty?
118
- puts "nothing to do, all up to date"
119
- return
120
- end
121
-
122
- self.send "build_#{@mode.to_s}", files, args
123
- end
124
-
125
- def generate
126
- DepAnalyzer.new('./src', @source_suffix, @header_suffix)
127
- .build_to_file ['./src', './src/components'], @deps
128
- end
129
-
130
- def update
131
- generate
132
- end
133
-
134
- def clean
135
- self.send "clean_#{@mode.to_s}"
136
- end
137
-
138
- def run(args)
139
- build []
140
- args = args.join " "
141
- puts "./target/#{@name} #{args}"
142
- exec "./target/#{@name} #{args}"
143
- end
144
-
145
- def add(args)
146
- args.each do |i|
147
- dir = @components
148
- filenames = i.split("/")
149
- prefix = []
150
- filenames.each do |filename|
151
- dir += "/#{filename}"
152
- prefix << filename
153
- unless Dir.exist? dir
154
- FileUtils.mkdir dir
155
- Dir.chdir(dir) do
156
- puts "created " + Dir.pwd
157
- create_working_files prefix.join('__'), filename
158
- end
159
- end
160
- end
161
- end
162
- end
163
-
164
- def dep
165
- deps = DepAnalyzer.read_from(@deps) if File.exist?(@deps)
166
- deps.each do |k, v|
167
- unless v.empty?
168
- puts "#{k} depends on: "
169
- v.each {|f| puts " #{f}"}
170
- puts ""
171
- end
172
- end
173
- end
174
-
175
- private
176
- def create_working_files(prefix, filename)
177
- DefaultFiles.create_cpp filename, @source_suffix, @header_suffix
178
- DefaultFiles.create_hpp @name, prefix, filename, @header_suffix
179
- end
180
-
181
- def build_compiler_from_config(args)
182
- Dir.chdir(@workspace) do
183
- flags = ConfigReader.extract_flags "config.json"
184
- compiler_name = flags['compiler'] ? flags['compiler'] : "clang++"
185
- abort_on_err "compiler #{compiler_name} not found" unless File.exists?("/usr/bin/#{compiler_name}")
186
- compiler_flags = ['-Isrc/components'] + args
187
-
188
- if opts = flags['flags']
189
- opts.each do |k, v|
190
- case v
191
- when String
192
- compiler_flags << v
193
- when Array
194
- v.each do |o|
195
- compiler_flags << o
196
- end
197
- else
198
- abort_on_err "unknown options in config.json, #{v}"
199
- end
200
- end
201
- end
202
-
203
- @compiler = Compiler.new compiler_name, compiler_flags
204
- end
205
- end
206
-
207
- def compile(f, o)
208
- @compiler.compile f, o
209
- end
210
-
211
- def link(odir, objs)
212
- status = system "#{@compiler} -o #{odir}/#{@name} #{objs.join(" ")}"
213
- unless status
214
- puts "compilation failed"
215
- return
216
- end
217
-
218
- @compiler.link "#{odir}/#{@name}", objs
219
- end
220
-
221
- def build_bin(files, args)
222
- build_compiler_from_config args
223
- comps = files.select {|f| f.start_with? @components_prefix}
224
- srcs = files - comps
225
-
226
- srcs.each do |f|
227
- puts "compiling #{f}"
228
- fname = f.split("/")[-1]
229
- o = @obj_prefix + fname.delete_suffix(File.extname(fname)) + '.o'
230
- compile f, o
231
- end
232
-
233
- comps.each do |f|
234
- puts "compiling #{f}"
235
- o = @obj_prefix + f.delete_suffix(File.extname(f))[@components_prefix.length..]
236
- .gsub('/', '_') + '.o'
237
- compile f, o
238
- end
239
-
240
- link('./target', Dir.glob("obj/*.o")) unless files.empty?
241
- end
242
-
243
- def build_lib
244
- puts "build a lib"
245
- end
246
-
247
- def clean_obj
248
- puts "rm -f ./obj/*.o"
249
- system "rm -f ./obj/*.o"
250
- end
251
-
252
- def clean_target
253
- puts "rm -f ./target/*"
254
- system "rm -f ./target/*"
255
- end
256
-
257
- def clean_bin
258
- clean_obj
259
- clean_target
260
- end
261
-
262
- def clean_lib
263
- clean_obj
264
- clean_target
265
- end
266
-
267
- public
268
- def inspect
269
- puts "name is #{@name}"
270
- puts "name is #{@workspace}"
271
- end
272
- end