rbuildsys 0.0.1.pre

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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/rbuildsys.rb +410 -0
  3. metadata +47 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 907496c16fb0c9c5b7ee4fcc5a177cdf4d25f58603c952e012b2d9304355fb0d
4
+ data.tar.gz: 20044f4d5b9ff2627780e2c43c3d623adf1137e3171ce9945cdee35d787c6c4e
5
+ SHA512:
6
+ metadata.gz: 81c674e67e81835bcae6a17bbd632c31f25b99aa81b5d4f5421b5fc655ddf269976ecf6944309dd36306133f8b26810aae2222dece06d683807bc3cdcccb0eeb
7
+ data.tar.gz: 6b882e5244940eff79357dcff6df5bc10b8d9c96a3fb8e5e18432f78feb4aa0d4ed9a47058fd853ea8072f30cecc20ec0f761c018a25a59a56fbf46455f3959b
@@ -0,0 +1,410 @@
1
+ require "optparse"
2
+ require "fileutils"
3
+
4
+ # Namespace for all code for RBuildSys
5
+ module RBuildSys
6
+
7
+ # Class to describe a buildable RBuildSys project
8
+ #
9
+ # @author Mai-Lapyst
10
+ class Project
11
+ # Returns the name of the project
12
+ # @return [String]
13
+ attr_reader :name
14
+
15
+ # Returns the array of source directorys for this project
16
+ # @return [Array<String>]
17
+ attr_reader :src_dirs
18
+
19
+ # Returns the array of include directorys for this project
20
+ # @return [Array<String>]
21
+ attr_reader :inc_dirs
22
+
23
+ # Returns the array of library directorys for this project
24
+ # @return [Array<String>]
25
+ attr_reader :lib_dirs
26
+
27
+ # Returns the array of librarys for this project
28
+ # @return [Array<String>]
29
+ attr_reader :librarys
30
+
31
+ # Returns the array of flags for this project; will be added to the compiler call
32
+ # @return [Array<String>]
33
+ attr_reader :flags
34
+
35
+ # Returns true if the project is install-able, false otherwise
36
+ # @return [true, false]
37
+ attr_accessor :no_install
38
+
39
+ # Returns the library type of this project; if nil, this project isn't a library
40
+ # @return [:static, :dynamic, :s, :dyn]
41
+ attr_accessor :libType
42
+
43
+ # Returns the dependencys of this project
44
+ # @return [Array<Project>]
45
+ attr_reader :dependencys
46
+
47
+ # @param name [String] name of the project; see {#name}
48
+ # @param options [Hash] various options for the project
49
+ # @option options :lang [Symbol] language used for this project. Available: +:c+, +:cpp+
50
+ # @option options :srcFile_endings [Array<String>] additional fileendings that should be used for finding sourcefiles
51
+ # @option options :no_install [true, false] flag that tells if the project is install-able or not; see {#no_install}
52
+ # @option options :libType [:static, :dynamic, :s, :dyn, nil] type of library; see {#libType}
53
+ # @option options :compiler [String, nil] compiler to be used to transform sourcefiles to objectfiles; if no is supplied, +"gcc"+ or +"g++"+ is used (based on the language)
54
+ def initialize(name, options)
55
+ @name = name;
56
+ @src_dirs = [];
57
+ @inc_dirs = [];
58
+ @lib_dirs = [];
59
+ @dependencys = [];
60
+ @librarys = [];
61
+ @flags = [];
62
+
63
+ check_lang(options[:lang] || "c");
64
+
65
+ @compiler = "gcc" if (@lang == :c);
66
+ @compiler = "g++" if (@lang == :cpp);
67
+ if (options[:compiler]) then
68
+ @compiler = options[:compiler];
69
+ end
70
+
71
+ @src_file_endings = [];
72
+ @src_file_endings = ["cpp", "cc", "c++", "cxx"] if (@lang == :cpp);
73
+ @src_file_endings = ["c"] if (@lang == :c);
74
+
75
+ if (options[:srcFile_endings]) then
76
+ @src_file_endings.push(*options[:srcFile_endings]);
77
+ @src_file_endings.uniq!;
78
+ end
79
+
80
+ @no_install = options[:no_install] || false;
81
+ @libType = options[:libType];
82
+ end
83
+
84
+ private
85
+ # lang must be a valid string for the gcc/g++ -std option:
86
+ # https://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html
87
+ # or simply the language: c, c++, gnu, gnu++
88
+ def check_lang(lang)
89
+ if ((m = lang.match(/^(c\+\+|gnu\+\+)([\dxyza]+)?$/)) != nil) then
90
+ @lang = :cpp;
91
+ @c_standard = lang if (m[2]);
92
+ return;
93
+ end
94
+
95
+ if ((m = lang.match(/^(c|gnu)([\dx]+)?$/)) != nil) then
96
+ @lang = :c;
97
+ @c_standard = lang if (m[2]);
98
+ return;
99
+ end
100
+
101
+ if (lang.match(/^iso(\d+):([\dx]+)$/) != nil) then
102
+ @lang = :c;
103
+ @c_standard = lang;
104
+ return;
105
+ end
106
+
107
+ raise ArgumentError.new("argument #2 contains key lang, but cannot validate it: '#{lang}'");
108
+ end
109
+
110
+ public
111
+ # cleans the project's output directory
112
+ def clean()
113
+ buildDir = "./build/#{name}";
114
+ FileUtils.remove_dir(buildDir) if File.directory?(buildDir)
115
+ end
116
+
117
+ public
118
+ # builds the project
119
+ # @return [true, false] state of the build, true means success, false means failure
120
+ def build()
121
+ for dep in dependencys do
122
+ if (!dep.build()) then
123
+ return false;
124
+ end
125
+ end
126
+
127
+ puts "Now building '#{@name}'";
128
+ puts "- using language #{@lang}";
129
+
130
+ for dep in dependencys do
131
+ @inc_dirs.push(*dep.inc_dirs).uniq!;
132
+ @lib_dirs.push("./build/#{dep.name}");
133
+ @librarys.push(dep.name);
134
+ if (dep.libType == :static) then
135
+ @lib_dirs.push(*dep.lib_dirs).uniq!
136
+ @librarys.push(*dep.librarys).uniq!
137
+ end
138
+ end
139
+
140
+ @flags.push("-g") if (OPTIONS[:debug]);
141
+
142
+ # create the project build dir
143
+ buildDir = "./build/#{name}";
144
+ system("mkdir -p #{buildDir}"); # todo: dont use system!
145
+
146
+ # make the includes
147
+ #inc_dirs.map! { |incDir| incDir + "/*.h" }
148
+ #includes = Dir.glob(inc_dirs);
149
+ includes = @inc_dirs.map{|incDir| "-I #{incDir}"}.join(" ");
150
+ libs = @lib_dirs.map{|libDir| "-L #{libDir}"}.join(" ") + " " + @librarys.map{|lib| "-l #{lib}"}.join(" ");
151
+ std = @c_standard ? ("-std=#{@c_standard}") : "";
152
+
153
+ # for now, just ignore all global and relative paths for sources
154
+ src_dirs.select! { |srcDir|
155
+ srcDir[0] != "/" && !srcDir.start_with?("../")
156
+ }
157
+
158
+ source_modified = false;
159
+ build_failed = false;
160
+
161
+ src_dirs.each { |srcDir|
162
+ globStr = File.join(srcDir, "**/*.{#{@src_file_endings.join(",")}}");
163
+ srcFiles = Dir.glob(globStr);
164
+
165
+ # do the incremental build!
166
+ srcFiles.each { |srcFile|
167
+ objFile = File.join(buildDir, srcFile.gsub(srcDir, ""));
168
+ objFile = objFile.gsub(Regexp.new("\.(#{@src_file_endings.join("|")})$"), ".o");
169
+ srcTime = File.mtime(srcFile);
170
+
171
+ if (OPTIONS[:cleanBuild] || !File.exists?(objFile) || (srcTime > File.mtime(objFile))) then
172
+ source_modified = true;
173
+ FileUtils.mkdir_p(File.dirname(objFile)); # ensure we have the parent dir(s)
174
+
175
+ # build the source!
176
+ cmd = "#{@compiler} #{std} -Wall #{includes} #{@flags.join(" ")} -c -o #{objFile} #{srcFile}";
177
+ puts "- $ #{cmd}";
178
+ f = system(cmd);
179
+ if (f) then
180
+ FileUtils.touch(objFile, :mtime => srcTime);
181
+ else
182
+ build_failed = true;
183
+ end
184
+ end
185
+ }
186
+ }
187
+
188
+ if (build_failed) then
189
+ return false;
190
+ end
191
+
192
+ if (!source_modified) then
193
+ puts "- No need for building library/executable, nothing changed!";
194
+ return true;
195
+ end
196
+
197
+ objFiles = Dir.glob(buildDir + "/**/*.o");
198
+ if (libType == :static) then
199
+ puts "- Building static library lib#{name}!";
200
+ libname = File.join(buildDir, "lib#{name}.a");
201
+ cmd = "ar rcs #{libname} #{objFiles.join(" ")}";
202
+ puts " - $ #{cmd}";
203
+ f = system(cmd);
204
+ return f;
205
+ elsif (libType == :dynamic) then
206
+ raise NotImplementedError.new("");
207
+ else
208
+ # build runable binary
209
+ binname = File.join(buildDir, "#{name}.run");
210
+ puts "- Building executable #{binname}!";
211
+ cmd = "#{@compiler} #{includes} #{@flags.join(" ")} -o #{binname} #{objFiles.join(" ")} #{libs}";
212
+ puts " - $ #{cmd}";
213
+ f = system(cmd);
214
+ return f;
215
+ end
216
+
217
+ return true;
218
+ end
219
+ end
220
+
221
+ # Stores all projects that are defined by the user
222
+ PROJECTS = {};
223
+
224
+ # Holds the current project that is currently being configured.
225
+ # Only non-nil inside the block for {#newProject}
226
+ @@cur_proj = nil;
227
+
228
+ # Stores all options; will be modified through the commandline option parser
229
+ # @see @@optparse
230
+ OPTIONS = {
231
+ # true if the current build should be a debug build
232
+ :debug => true,
233
+
234
+ # true if we only want to clean our build directory
235
+ :clean => false,
236
+
237
+ # true if we want to clean all buildfiles before building
238
+ :cleanBuild => false,
239
+
240
+ # true if we want to install instead of building
241
+ :install => false,
242
+ };
243
+
244
+ # Option parser of RBuildSys, used to provide standard operations such as clean, clean build, install...
245
+ @@optparse = OptionParser.new do |opts|
246
+ opts.banner = "Usage: #{$PROGRAM_NAME} [options] <projects>";
247
+
248
+ opts.on("-r","--release", "Build for release") do
249
+ # todo: if compiled for debug before, the system cannot detect the change
250
+ OPTIONS[:debug] = false;
251
+ end
252
+ opts.on("-b", "--build-clean", "Make a clean build") do
253
+ OPTIONS[:cleanBuild] = true;
254
+ end
255
+ opts.on("-c", "--clean", "Clean all projects build dirs") do
256
+ OPTIONS[:clean] = true;
257
+ end
258
+ opts.on("-i", "--install", "Install all project(s) that are installable") do
259
+ OPTIONS[:install] = true;
260
+ end
261
+ end
262
+
263
+ # creates a new project to build
264
+ # takes a block to execute, that defines the project. it is immediately executed,
265
+ # and has aaccess to the current project (that is created with this method) in {RBuildSys::@@cur_proj RBuildSys::@@cur_proj}.
266
+ #
267
+ # @yield Block to configure the project
268
+ #
269
+ # @param name [String] name of the project, also used for the final output of the project
270
+ # @param options [Hash] various options; for details see {Project#initialize}
271
+ def newProject(name, options = {})
272
+ raise ArgumentError.new("name need to be a string") if (name.class != String);
273
+ @@cur_proj = Project.new(name, options);
274
+ yield
275
+ PROJECTS[name] = @@cur_proj;
276
+ @@cur_proj = nil;
277
+ end
278
+
279
+ # adds include directorys to use for the current project
280
+ #
281
+ # @note should only be called inside the block for {#newProject}
282
+ # @param dir [String, Array<String>] directory path; root of the include path. if its a Array, each element is used in a call to {#incDir}
283
+ # @param more [Array<String>] additional include paths; each element is used in a call to {#incDir}
284
+ def incDir(dir, *more)
285
+ if (dir.class == String) then
286
+ raise ArgumentError.new("argument #1 is no directory: '#{dir}'") if (!Dir.exists?(dir));
287
+ @@cur_proj.inc_dirs.push(dir);
288
+ elsif (dir.class == Array) then
289
+ dir.each { |d| incDir(d) }
290
+ else
291
+ raise ArgumentError.new("argument #1 must be a String or an Array");
292
+ end
293
+ more.each { |d| incDir(d) }
294
+ end
295
+
296
+ # adds source directorys to use for the current project
297
+ #
298
+ # @note should only be called inside the block for {#newProject}
299
+ # @param dir [String, Array<String>] directory path; root of the source path. if its a Array, each element is used in a call to {#srcDir}
300
+ # @param more [Array<String>] additional source paths; each element is used in a call to {#srcDir}
301
+ def srcDir(dir, *more)
302
+ if (dir.class == String) then
303
+ raise ArgumentError.new("argument #1 is no directory: '#{dir}'") if (!Dir.exists?(dir));
304
+ @@cur_proj.src_dirs.push(dir);
305
+ elsif (dir.class == Array) then
306
+ dir.each { |d| srcDir(d) }
307
+ else
308
+ raise ArgumentError.new("argument #1 must be a String or an Array");
309
+ end
310
+ more.each { |d| srcDir(d) }
311
+ end
312
+
313
+ # tells the current project that it is not install-able
314
+ # this means that if you run an install of your projects, this one will not be installed
315
+ #
316
+ # @note should only be called inside the block for {#newProject}
317
+ def noInstall()
318
+ @@cur_proj.no_install = true;
319
+ end
320
+
321
+ # sets the current project as a lib
322
+ #
323
+ # @note should only be called inside the block for {#newProject}
324
+ # @param type [:static, :dynamic, :s, :dyn] the type of the library: static (*.a / *.lib) or dynamic (*.so / *.dll)
325
+ def isLib(type)
326
+ @@cur_proj.libType = :static if ([:static, :s].include?(type));
327
+ @@cur_proj.libType = :dynamic if ([:dynamic, :dyn, :d].include?(type));
328
+ end
329
+
330
+ # links a RBuildSys project to the current project
331
+ # this has the effect that the given project is build before the current project, and
332
+ # if the given project is a lib, it is also linked to the current project
333
+ #
334
+ # @note should only be called inside the block for {#newProject}
335
+ # @param name [String] can be a local project (inside current file) or any installed project
336
+ def use(name)
337
+ if (PROJECTS[name] == nil) then
338
+ # use installed project
339
+ raise NotImplementedError.new("");
340
+ else
341
+ # use local
342
+ proj = PROJECTS[name];
343
+ end
344
+
345
+ if (!proj.libType) then
346
+ raise ArgumentError.new("argument #1 can't be used cause it is not a library");
347
+ end
348
+
349
+ @@cur_proj.dependencys.push(proj);
350
+ end
351
+
352
+ # adds a external library to the current project
353
+ #
354
+ # @note should only be called inside the block for {#newProject}
355
+ # @note there is currently no check in place to verify that the given library exists
356
+ # @param name [String] name of the library that should be used. in a c/c++ context, it should be without the leading "lib"
357
+ # @param path [String, nil] optional search path for this library
358
+ def useLib(name, path = nil)
359
+ @@cur_proj.librarys.push(name);
360
+ @@cur_proj.lib_dirs.push(path) if (path != nil && Dir.exists?(path));
361
+ end
362
+
363
+ # adds an option to the option parser of RBuildSys
364
+ # @see OptionParser#on
365
+ def onOption(*opts, &block)
366
+ @@optparse.on(*opts, block);
367
+ end
368
+
369
+ # adds an option to the option parser of RBuildSys
370
+ # @see OptionParser#on_tail
371
+ def onTailOption(*opts, &block)
372
+ @@optparse.on_tail(*opts, block);
373
+ end
374
+
375
+ # adds an option to the option parser of RBuildSys
376
+ # @see OptionParser#on_head
377
+ def onHeadOption(*opts, &block)
378
+ @@optparse.on_head(*opts, block);
379
+ end
380
+
381
+ # adds a flag to the flags of the current project.
382
+ #
383
+ # @note should only be called inside the block for {#newProject}
384
+ # @param flag [String] the flag to be added
385
+ def flag(flag)
386
+ @@cur_proj.flags.push(flag);
387
+ end
388
+
389
+ # begins the build process.
390
+ # should be called after you configured all your projects with {#newProject}.
391
+ def build()
392
+ @@optparse.parse!
393
+ if (ARGV.length == 0) then
394
+ puts @@optparse;
395
+ exit(1);
396
+ end
397
+
398
+ ARGV.each { |projname|
399
+ proj = PROJECTS[projname];
400
+ if (proj) then
401
+ if (OPTIONS[:clean]) then
402
+ proj.clean();
403
+ else
404
+ proj.build();
405
+ end
406
+ end
407
+ }
408
+ end
409
+
410
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rbuildsys
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.pre
5
+ platform: ruby
6
+ authors:
7
+ - Mai-Lapyst
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-11-06 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |2
14
+ rbuildsys is a build system like cmake,
15
+ where you write your own scripts to define
16
+ how your project needs to be build
17
+ email:
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - lib/rbuildsys.rb
23
+ homepage: https://rubygems.org/gems/rbuildsys
24
+ licenses:
25
+ - GPL-3.0
26
+ metadata: {}
27
+ post_install_message:
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.3.1
41
+ requirements: []
42
+ rubyforge_project:
43
+ rubygems_version: 2.7.6.2
44
+ signing_key:
45
+ specification_version: 4
46
+ summary: Scriptable build system, written in ruby
47
+ test_files: []