canoe 0.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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 78d6318d9757575b6e8f3f118e161ca06bb08655f26e7531e33c31d63f26aa18
4
+ data.tar.gz: 5cb8230a80e4bb2f03b8afb3dbebea90fccfd4b97bafa652502b2161e70ce374
5
+ SHA512:
6
+ metadata.gz: 9501ebdd1d4ef09e6bfaede0c712044d665650b2a9d07dcfe490fe35afc378dae1d66429dc5919e217b48f4d3df9156611a4c496c64ebe3916870fc2da48453a
7
+ data.tar.gz: 848272aacd562ef7bc05ae50bd948a4221721b9432be46acb669e8ee6bb4f5a27e7e7ed57d5e6b64d83fce52be6a882955324f0c782b9e9252611ed29ba50cf8
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'canoe'
3
+
4
+ Canoe.new.parse ARGV
@@ -0,0 +1,14 @@
1
+ require_relative "workspace"
2
+ require_relative "cmd"
3
+ require_relative "source_files"
4
+
5
+ class Canoe
6
+ def initialize
7
+ options = ["new", "build", "run", "clean", "help", "add", "generate", "deps", "version"]
8
+ @cmd = CmdParser.new options
9
+ end
10
+
11
+ def parse(args)
12
+ @cmd.parse args
13
+ end
14
+ end
@@ -0,0 +1,107 @@
1
+ require_relative "workspace"
2
+ require_relative "err"
3
+ require_relative "config_reader"
4
+
5
+ class CmdParser
6
+ include Err
7
+ def initialize(options)
8
+ @options = options
9
+ end
10
+
11
+ def parse(args)
12
+ if args.size < 1
13
+ abort_on_err "please give one command among #{@options.join(', ')}"
14
+ end
15
+
16
+ unless @options.include?(args[0])
17
+ abort_on_err "unknown command #{args[0]}"
18
+ end
19
+
20
+ self.send "parse_#{args[0]}", args[1..]
21
+ end
22
+
23
+ private
24
+ def get_current_workspace
25
+ abort_on_err "not in a canoe workspace" unless File.exists? ".canoe"
26
+ config = ConfigReader.extract_flags("config.json")
27
+
28
+ src_sfx = config["source-suffix"] ? config["source-suffix"] : "cpp"
29
+ hdr_sfx = config["header-suffix"] ? config["header-suffix"] : "hpp"
30
+
31
+ name = Dir.pwd.split("/")[-1]
32
+ mode = File.exists?("src/main.#{src_sfx}") ? :bin : :lib
33
+
34
+ Dir.chdir('..') do
35
+ return WorkSpace.new(name, mode, src_sfx, hdr_sfx)
36
+ end
37
+ end
38
+
39
+ def parse_new(args)
40
+ abort_on_err "not enough arguments to canoe new" if args.size < 1
41
+
42
+ name, mode = nil, "bin"
43
+ suffixes = ["cpp", "hpp"]
44
+
45
+ args.each do |arg|
46
+ case arg
47
+ when '--bin', '--lib'
48
+ mode = arg[2..]
49
+ when /--suffix=(\w+)\:(\w+)/
50
+ suffixes[0], suffixes[1] = $1, $2
51
+ else
52
+ name = arg unless name
53
+ end
54
+ end
55
+
56
+ abort_on_err("please give a name to this project") unless name
57
+ WorkSpace.new(name, mode.to_sym, suffixes[0], suffixes[1]).new
58
+ end
59
+
60
+ def parse_add(args)
61
+ if args.size < 1
62
+ abort_on_err "it's not reasonable to add a component with no name given"
63
+ end
64
+
65
+ get_current_workspace.add args
66
+ end
67
+
68
+ def parse_build(args)
69
+ get_current_workspace.build args
70
+ end
71
+
72
+ def parse_generate(args)
73
+ get_current_workspace.generate
74
+ end
75
+
76
+ def parse_run(args)
77
+ get_current_workspace.run args
78
+ end
79
+
80
+ def parse_dep(args)
81
+ get_current_workspace.dep
82
+ end
83
+
84
+ def parse_clean(args)
85
+ get_current_workspace.clean
86
+ end
87
+
88
+ def parse_version(args)
89
+ puts <<~VER
90
+ canoe v0.2.1
91
+ For features in this version, please visit https://github.com/Dicridon/canoe
92
+ Currently, canoe can do below:
93
+ - project creation
94
+ - project auto build and run (works like Cargo for Rust)
95
+ - project structure management
96
+ by XIONG Ziwei
97
+ VER
98
+ end
99
+
100
+ def parse_help(args)
101
+ WorkSpace.help
102
+ end
103
+
104
+ def parse_update(args)
105
+ get_current_workspace.update
106
+ end
107
+ end
@@ -0,0 +1,31 @@
1
+ class Compiler
2
+ attr_reader :name, :flags
3
+ def initialize(name, flgs)
4
+ @name = name
5
+ @flags = flgs
6
+ end
7
+
8
+ def flags_as_str
9
+ flags.join " "
10
+ end
11
+
12
+ def append_flag(flag)
13
+ @flags << flag
14
+ end
15
+
16
+ def compile(src, out)
17
+ puts "#{name} -o #{out} #{flags_as_str} -c #{src}"
18
+ system "#{name} -o #{out} #{flags_as_str} -c #{src}"
19
+ end
20
+
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(" ")}"
25
+ end
26
+
27
+ def inspect
28
+ puts "compiler name: #{name.inspect}"
29
+ puts "compiler flags: #{flags.inspect}"
30
+ end
31
+ end
@@ -0,0 +1,8 @@
1
+ require 'json'
2
+
3
+ class ConfigReader
4
+ def self.extract_flags(file)
5
+ abort_on_err("config file #{file} does not exsit") unless File.exists? file
6
+ JSON.parse(File.read(file))
7
+ end
8
+ end
@@ -0,0 +1,70 @@
1
+ class DefaultFiles
2
+ def self.open_file_and_write(filename, content)
3
+ File.open(filename, "w") {|f|
4
+ f.write(content)
5
+ }
6
+ end
7
+
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
25
+
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
37
+
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
49
+
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
58
+
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}__
65
+
66
+ #endif
67
+ DOC
68
+ )
69
+ end
70
+ end
@@ -0,0 +1,113 @@
1
+ require_relative 'source_files'
2
+ require_relative 'err'
3
+
4
+ class DepAnalyzer
5
+ include Err
6
+ def self.read_from(filename)
7
+ File.open(filename, "r") do |f|
8
+ ret = Hash.new []
9
+ 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
12
+ ret[entry[0]] = entry[1].split
13
+ end
14
+ ret
15
+ end
16
+ end
17
+
18
+ def self.compiling_filter(deps, build_time, src_sfx='cpp', hdr_sfx='hpp')
19
+ files = []
20
+ deps.each do |k, v|
21
+ next if k.end_with? ".#{hdr_sfx}"
22
+ if should_recompile?(k, build_time)
23
+ files << k
24
+ next
25
+ end
26
+ v.each do |f|
27
+ if mark(f, build_time, deps) || mark(f.sub(".#{hdr_sfx}", ".#{src_sfx}"), build_time, deps)
28
+ files << k
29
+ break
30
+ end
31
+ end
32
+ end
33
+ files
34
+ end
35
+
36
+ private
37
+ def self.mark(file, build_time, deps)
38
+ return false unless File.exists? file
39
+ if should_recompile?(file, build_time)
40
+ return true
41
+ else
42
+ deps[file].each do |f|
43
+ return true if mark(f, build_time, deps)
44
+ end
45
+ end
46
+ false
47
+ end
48
+
49
+ def self.should_recompile?(file, build_time)
50
+ judge = build_time
51
+ if build_time == Time.new(0)
52
+ objfile = "./obj/#{File.basename(file, ".*")}.o"
53
+ return true unless File.exists? objfile
54
+ judge = File.mtime(objfile)
55
+ end
56
+ File.mtime(file) > judge
57
+ end
58
+
59
+ public
60
+ def initialize(dir, src_sfx='cpp', hdr_sfx='hpp')
61
+ @dir = dir
62
+ @deps = Hash.new []
63
+ @source_suffix = src_sfx
64
+ @header_suffix = hdr_sfx
65
+ end
66
+
67
+ def build_dependence(include_path)
68
+ files = SourceFiles.get_all(@dir) do |f|
69
+ f.end_with?(".#{@source_suffix}") || f.end_with?(".#{@header_suffix}")
70
+ end
71
+
72
+ @deps = Hash.new []
73
+ files.each do |fname|
74
+ @deps[fname] = get_all_headers include_path, fname, @header_suffix
75
+ end
76
+
77
+ @deps
78
+ end
79
+
80
+ def build_to_file(include_path, filename)
81
+ build_dependence include_path
82
+
83
+ File.open(filename, "w") do |f|
84
+ @deps.each do |k, v|
85
+ f.write "#{k}: #{v.join(" ")}\n"
86
+ end
87
+ end
88
+
89
+ @deps
90
+ end
91
+
92
+ private
93
+ def get_all_headers(include_path, file, suffix='hpp')
94
+ File.open(file, "r") do |f|
95
+ ret = []
96
+ if file.end_with?(".#{@source_suffix}")
97
+ header = file.sub(".#{@source_suffix}", ".#{@header_suffix}")
98
+ ret += [header] if File.exists?(header)
99
+ end
100
+
101
+ f.each_line do |line|
102
+ if mat = line.match(/include "(.+\.#{suffix})"/)
103
+ include_path.each do |path|
104
+ dep = "#{path}/#{mat[1]}"
105
+ ret += [dep] if File.exists? dep
106
+ end
107
+ end
108
+ end
109
+
110
+ ret.uniq
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,19 @@
1
+ module Err
2
+ def warn_on_err(err)
3
+ puts <<~ERR
4
+ Warning:
5
+ #{err}
6
+ try 'canoe help' for more information
7
+ ERR
8
+ end
9
+
10
+ def abort_on_err(err)
11
+ abort <<~ERR
12
+ Fatal:
13
+ #{err}
14
+ try 'canoe help' for more information
15
+ ERR
16
+ end
17
+
18
+ module_function :warn_on_err, :abort_on_err
19
+ end
@@ -0,0 +1,42 @@
1
+ class SourceFiles
2
+ class << self
3
+ def get_all(dir, &block)
4
+ @files = []
5
+ get_all_helper(dir, &block)
6
+ @files
7
+ end
8
+
9
+ def get_in(dir, &block)
10
+ @files = []
11
+ Dir.each_child(dir) do |f|
12
+ file = "#{dir}/#{f}"
13
+ if File.file? file
14
+ if block_given?
15
+ @files << "#{file}" if yield(f)
16
+ else
17
+ @files << "#{file}"
18
+ end
19
+ end
20
+ end
21
+
22
+ @files
23
+ end
24
+
25
+ private
26
+ def get_all_helper(dir, &block)
27
+ Dir.each_child(dir) do |f|
28
+ file = "#{dir}/#{f}"
29
+ if File.file? file
30
+ if block_given?
31
+ @files << "#{file}" if yield(f)
32
+ else
33
+ @files << "#{file}"
34
+ end
35
+ else
36
+ get_all_helper("#{file}", &block)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
@@ -0,0 +1,272 @@
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
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: canoe
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - XIONG Ziwei
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-06-23 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |
14
+ Tired of writing Makefile, CMakeList and even SConstruct? Let Canoe help you wipe them out.
15
+ 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
17
+ email: noahxiong@outlook.com
18
+ executables:
19
+ - canoe
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - bin/canoe
24
+ - lib/canoe.rb
25
+ - lib/cmd.rb
26
+ - lib/compiler.rb
27
+ - lib/config_reader.rb
28
+ - lib/default_files.rb
29
+ - lib/dependence.rb
30
+ - lib/err.rb
31
+ - lib/source_files.rb
32
+ - lib/workspace.rb
33
+ homepage: https://github.com/Dicridon/canoe
34
+ licenses:
35
+ - MIT
36
+ metadata: {}
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubygems_version: 3.1.2
53
+ signing_key:
54
+ specification_version: 4
55
+ summary: a C/C++ project management and build tool
56
+ test_files: []