Class: Canoe::WorkSpace

Inherits:
Object
  • Object
show all
Includes:
Err, SystemCommand
Defined in:
lib/workspace/add.rb,
lib/workspace/dep.rb,
lib/workspace/new.rb,
lib/workspace/run.rb,
lib/workspace/help.rb,
lib/workspace/make.rb,
lib/workspace/test.rb,
lib/workspace/build.rb,
lib/workspace/clean.rb,
lib/workspace/update.rb,
lib/workspace/version.rb,
lib/workspace/generate.rb,
lib/workspace/workspace.rb

Overview

A workspace resents a C/C++ project This class is responsible for the main functionality of canoe, such as building and cleaning TODO

add a command to generate compile_commands.json so users won't have to install bear

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from SystemCommand

#issue_command, #run_command

Methods included from Err

#abort_on_err, #warn_on_err

Constructor Details

#initialize(name, mode, src_suffix = 'cpp', hdr_suffix = 'hpp', nu = false) ⇒ WorkSpace

Returns a new instance of WorkSpace.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/workspace/workspace.rb', line 23

def initialize(name, mode, src_suffix = 'cpp', hdr_suffix = 'hpp', nu = false)
  @name = name
  @compiler = Compiler.new 'clang++', ['-Isrc/components'], []
  @cwd = Dir.new(Dir.pwd)
  @workspace = Dir.pwd.to_s + (nu ? "/#{@name}" : '')
  @src = "#{@workspace}/src"
  @components = "#{@src}/components"
  @obj = "#{@workspace}/obj"
  @third = "#{@workspace}/third-party"
  @target = "#{@workspace}/target"
  @tests = "#{@workspace}/tests"
  @mode = mode
  @deps = '.canoe.deps'
  @test_deps = '.canoe.test.deps'

  @target_short = './target'
  @src_short = './src'
  @components_short = "#{@src_short}/components"
  @obj_short = './obj'
  @tests_short = './tests'

  @src_prefix = './src/'
  @components_prefix = './src/components/'
  @obj_prefix = './obj/'

  @source_suffix = src_suffix
  @header_suffix = hdr_suffix
end

Instance Attribute Details

#components_prefixObject (readonly)

Returns the value of attribute components_prefix.



19
20
21
# File 'lib/workspace/workspace.rb', line 19

def components_prefix
  @components_prefix
end

#components_shortObject (readonly)

Returns the value of attribute components_short.



19
20
21
# File 'lib/workspace/workspace.rb', line 19

def components_short
  @components_short
end

#cwdObject (readonly)

Returns the value of attribute cwd.



19
20
21
# File 'lib/workspace/workspace.rb', line 19

def cwd
  @cwd
end

#header_suffixObject (readonly)

Returns the value of attribute header_suffix.



19
20
21
# File 'lib/workspace/workspace.rb', line 19

def header_suffix
  @header_suffix
end

#modeObject (readonly)

Returns the value of attribute mode.



19
20
21
# File 'lib/workspace/workspace.rb', line 19

def mode
  @mode
end

#nameObject (readonly)

Returns the value of attribute name.



19
20
21
# File 'lib/workspace/workspace.rb', line 19

def name
  @name
end

#obj_prefixObject (readonly)

Returns the value of attribute obj_prefix.



19
20
21
# File 'lib/workspace/workspace.rb', line 19

def obj_prefix
  @obj_prefix
end

#obj_shortObject (readonly)

Returns the value of attribute obj_short.



19
20
21
# File 'lib/workspace/workspace.rb', line 19

def obj_short
  @obj_short
end

#source_suffixObject (readonly)

Returns the value of attribute source_suffix.



19
20
21
# File 'lib/workspace/workspace.rb', line 19

def source_suffix
  @source_suffix
end

#src_prefixObject (readonly)

Returns the value of attribute src_prefix.



19
20
21
# File 'lib/workspace/workspace.rb', line 19

def src_prefix
  @src_prefix
end

#src_shortObject (readonly)

Returns the value of attribute src_short.



19
20
21
# File 'lib/workspace/workspace.rb', line 19

def src_short
  @src_short
end

#target_shortObject (readonly)

Returns the value of attribute target_short.



19
20
21
# File 'lib/workspace/workspace.rb', line 19

def target_short
  @target_short
end

#tests_shortObject (readonly)

Returns the value of attribute tests_short.



19
20
21
# File 'lib/workspace/workspace.rb', line 19

def tests_short
  @tests_short
end

Class Method Details

.helpObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/workspace/help.rb', line 3

def self.help
  info = <<~INFO
  canoe is a C/C++ project manager, inspired by Rust cargo.
  usage:
      canoe new tada: create a project named 'tada' in current directory
        
      canoe build: compile current project (execute this command in project directory)

      canoe test: build and run tests
  
      canoe generate: generate dependency relationships and store it in '.canoe.deps' file. Alias: update

      canoe update: udpate dependency relationships and store it in '.canoe.deps' file. 
          
      canoe run: compile and execute current project (execute this command in project directory)
        
      canoe clean: remove all generated object files and binary files
        
      canoe help: show this help message
  
      canoe add tada: add a folder named tada under workspace/components,
        
      canoe dep: show current dependency relationships of current project
        
      canoe verion: version information

      canoe make: generate a makefile for this project
  
  new project_name [mode] [suffixes]:
      create a new project with project_name.
      In this project, four directories obj, src, target and third-party will be generated in project directory.
      in src, directory 'components' will be generated if [mode] is '--lib', an extra main.cpp will be generated if [mode] is '--bin'
  
      [mode]: --lib for a library and --bin for executable binaries
      [suffixes]: should be in 'source_suffix:header_suffix" format, notice the ':' between two suffixes
  add component_name:
      add a folder named tada under workspace/components.
      two files tada.hpp and tada.cpp would be craeted and intialized. File suffix may differ according users' specifications.
      if component_name is a path separated by '/', then canoe would create folders and corresponding files recursively.
  
  generate: 
      generate dependence relationship for each file, this may accelarate
      `canoe buid` command. It's recommanded to execute this command everytime
      headers are added or removed from any file.
      
  update:
      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.
      So when a file includes new headers or some headers are removed, users have to use 'canoe udpate'
      to update dependency relationships.
  
  build [all|test]:
      build current project, 'all' builds both target and tests, 'test' builds tests only

  test [tests] [args]:
      build and run tests
      [tests]: 'all' for all tests, or a name of a test for a single test
      [args]: args are passed to the single test
    
  run [options]:
      build current project with no specific compilation flags, and run this project, passing [options] as command line arguments to the binary
  
  clean:
      remove all generated object files and binary files
  
  help:
      show this help message
  
  verion: 
      display version information
  
  dep:
      display file dependencies in a better readable way

  make: 
      generate a Makefile for this project
  
  @author: written by XIONG Ziwei, ICT, CAS
  @contact: [email protected]
INFO
  puts info
end

.versionObject



3
4
5
6
7
8
9
10
11
12
13
# File 'lib/workspace/version.rb', line 3

def self.version
  puts <<~VER
       canoe v0.3.3.10
       For features in this version, please visit https://github.com/Dicridon/canoe
       Currently, canoe can do below:
           - project creation
           - project auto build, run and test (works like Cargo for Rust)
           - project structure management
       by XIONG Ziwei
     VER
end

Instance Method Details

#add(args) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/workspace/add.rb', line 4

def add(args)
  args.each do |i|
    dir = @components
    filenames = i.split '/'
    prefix = []
    filenames.each do |filename|
      dir += "/#{filename}"
      prefix << filename
      next if Dir.exist? dir

      FileUtils.mkdir dir
      Dir.chdir(dir) do
        puts "created + #{Dir.pwd.blue}"
        create_working_files prefix.join('__'), filename
      end
    end
  end
end

#build(arg = 'target') ⇒ Object

args are commandline parameters passed to ‘canoe build`, could be ’all’, ‘test’, ‘target’, ‘base’ or empty



27
28
29
30
# File 'lib/workspace/build.rb', line 27

def build(arg = 'target')
  build_compiler_from_config
  send "build_#{arg}"
end

#clean(arg = 'all') ⇒ Object

valid options: none, ‘all’, ‘target’, ‘tests’



4
5
6
# File 'lib/workspace/clean.rb', line 4

def clean(arg = 'all')
  send "clean_#{arg}"
end

#comp_to_obj(comp) ⇒ Object



8
9
10
# File 'lib/workspace/build.rb', line 8

def comp_to_obj(comp)
  @obj_prefix + comp.delete_suffix(File.extname(comp))[@components_prefix.length..].gsub("/", "_") + ".o"
end

#depObject



3
4
5
6
7
8
9
10
11
12
# File 'lib/workspace/dep.rb', line 3

def dep
  deps = DepAnalyzer.read_from(@deps) if File.exist?(@deps)
  deps.each do |k, v|
    next if v.empty?

    puts "#{k.blue} depends on: "
    v.each { |f| puts "    #{f.blue}" }
    puts ''
  end
end

#extract_one_file(file, deps) ⇒ Object

extract all files the file depends on, including headers



13
14
15
16
17
18
19
20
# File 'lib/workspace/test.rb', line 13

def extract_one_file(file, deps)
  ret = []
  holder = deps[file] + deps[file].map { |f| f.gsub(".#{@header_suffix}", ".#{@source_suffix}") }
  extract_one_file_helper(file, deps, holder, ret)

  ret = ret + ret.map{ |f| f.gsub(".#{@header_suffix}", ".#{@source_suffix}") }
  ret.uniq
end

#extract_one_file_header(file, deps) ⇒ Object



29
30
31
32
33
34
# File 'lib/workspace/test.rb', line 29

def extract_one_file_header(file, deps)
  ret = extract_one_file(file, deps).map do |f|
    f.gsub(".#{@source_suffix}", ".#{@header_suffix}")
  end
  ret.uniq
end

#extract_one_file_obj(file, deps) ⇒ Object



22
23
24
25
26
27
# File 'lib/workspace/test.rb', line 22

def extract_one_file_obj(file, deps)
  ret = extract_one_file(file, deps).map do |f|
    file_to_obj(f)
  end
  ret.uniq
end

#extract_one_file_source(file, deps) ⇒ Object



36
37
38
39
40
41
# File 'lib/workspace/test.rb', line 36

def extract_one_file_source(file, deps)
  ret = extract_one_file(file, deps).map do |f|
    f.gsub(".#{@header_suffix}", ".#{@source_suffix}")
  end
  ret.uniq
end

#file_to_obj(file) ⇒ Object

the if else order is important because tests are regarded as sources



13
14
15
16
17
18
19
# File 'lib/workspace/build.rb', line 13

def file_to_obj(file)
  if file.start_with?(@components_prefix)
    comp_to_obj file
  else
    src_to_obj file
  end
end

#generateObject



3
4
5
6
7
8
# File 'lib/workspace/generate.rb', line 3

def generate
  DepAnalyzer.new(@src_short, @source_suffix, @header_suffix)
             .build_to_file [@src_short, @components_short], @deps
  DepAnalyzer.new(@tests_short, @source_suffix, @header_suffix)
             .build_to_file [@src_short, @components_short], @test_deps
end

#hdr_of_src(file) ⇒ Object



21
22
23
# File 'lib/workspace/build.rb', line 21

def hdr_of_src(file)
  file.gsub(".#{@source_suffix}", ".#{@header_suffix}")
end

#makeObject



274
275
276
277
278
279
280
281
282
# File 'lib/workspace/make.rb', line 274

def make
  config = ConfigReader.new('config.json').extract_flags

  deps = target_deps.merge tests_deps

  makefile = CanoeMakefile.new self
  makefile.configure config
  makefile.make! deps
end

#newObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/workspace/new.rb', line 3

def new
  begin
    Dir.mkdir(@name)
  rescue SystemCallError
    abort_on_err "workspace #{@name} already exsits"
  end
  Dir.mkdir(@src)
  Dir.mkdir(@components)
  Dir.mkdir(@obj)
  add_gitignore @obj
  if @mode == :bin
    DefaultFiles.create_main(@src, @source_suffix)
  else
    DefaultFiles.create_lib_header(@src, @name, @header_suffix)
  end
  File.new("#{@workspace}/.canoe", 'w')
  compiler = @source_suffix == 'c' ? 'clang' : 'clang++'
  DefaultFiles.create_config @workspace, compiler, @source_suffix, @header_suffix

  Dir.mkdir(@third)
  Dir.mkdir(@target)
  add_gitignore @target      
  Dir.mkdir(@tests)
  Dir.chdir(@workspace) do
    issue_command 'git init'
    issue_command 'canoe add tests'
    DefaultFiles.create_clang_format @workspace
  end
  puts "workspace #{@workspace.blue} is created"
end

#run(args) ⇒ Object



3
4
5
6
7
8
9
10
# File 'lib/workspace/run.rb', line 3

def run(args)
  return if @mode == :lib

  return unless build
  
  args = args.join ' '
  run_command "#{@target_short}/#{@name} #{args}"
end

#src_to_obj(src) ⇒ Object



4
5
6
# File 'lib/workspace/build.rb', line 4

def src_to_obj(src)
  @obj_prefix + File.basename(src, ".*") + ".o"
end

#test(args) ⇒ Object



3
4
5
6
7
8
9
10
# File 'lib/workspace/test.rb', line 3

def test(args)
  if args.empty?
    test_all
    return
  end
  # we don't handle spaces
  test_single(args[0], args[1..].join(" "))
end

#updateObject



3
4
5
# File 'lib/workspace/update.rb', line 3

def update
  generate
end