rbuildsys 0.0.1.pre → 1.0.0
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/lib/rbuildsys.rb +833 -145
- data/lib/toolchains/gnu-32.json +26 -0
- data/lib/toolchains/gnu.json +26 -0
- data/lib/toolchains/i686-mingw32-w64.json +26 -0
- data/lib/toolchains/x86_64-mingw32-w64.json +26 -0
- metadata +45 -11
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 790cbd7c15d0ba978a5e4ea3503e0fed4075f814ef402f117f05133c700e9d24
|
4
|
+
data.tar.gz: adebe238290e2db4786dbe40128c3c870af49b15def92b3c6d7a2002963de225
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7729ecf4bcc8682643226e4ad5c9d2847c66595d776d97f30c04deed260b63460a6e2a041509d05d0b2194dbc0a27e9c8004e3b9c5b673b59f3fa822cb2dfd91
|
7
|
+
data.tar.gz: b1a64205974c6c1f61b3a96f9f0833a1bcd5bdb1ee1e5a1e6e75048b9e154997c632a1438b1d2d5894970d39d80254c2f1edb5b7d729b3ab2afdf713ecdc8a1d
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data.tar.gz.sig
ADDED
Binary file
|
data/lib/rbuildsys.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "optparse"
|
2
2
|
require "fileutils"
|
3
|
+
require "json"
|
3
4
|
|
4
5
|
# Namespace for all code for RBuildSys
|
5
6
|
module RBuildSys
|
@@ -12,14 +13,31 @@ module RBuildSys
|
|
12
13
|
# @return [String]
|
13
14
|
attr_reader :name
|
14
15
|
|
16
|
+
# Returns the output name of the project; typically the same as {#name}
|
17
|
+
# @return [String]
|
18
|
+
attr_accessor :outputName
|
19
|
+
|
20
|
+
# Returns the base directory for this project
|
21
|
+
# @return [String]
|
22
|
+
attr_accessor :baseDir
|
23
|
+
|
15
24
|
# Returns the array of source directorys for this project
|
16
25
|
# @return [Array<String>]
|
17
26
|
attr_reader :src_dirs
|
18
27
|
|
28
|
+
# Returns the array of source globs for this project
|
29
|
+
# @return [Array<String>]
|
30
|
+
attr_reader :src_globs
|
31
|
+
|
19
32
|
# Returns the array of include directorys for this project
|
20
33
|
# @return [Array<String>]
|
21
34
|
attr_reader :inc_dirs
|
22
35
|
|
36
|
+
# Returns the array of "public" include directorys for this project.
|
37
|
+
# These will only be used by projects that depends on this project
|
38
|
+
# @return [Array<String>]
|
39
|
+
attr_reader :public_inc_dirs
|
40
|
+
|
23
41
|
# Returns the array of library directorys for this project
|
24
42
|
# @return [Array<String>]
|
25
43
|
attr_reader :lib_dirs
|
@@ -36,36 +54,70 @@ module RBuildSys
|
|
36
54
|
# @return [true, false]
|
37
55
|
attr_accessor :no_install
|
38
56
|
|
39
|
-
# Returns the library type of this project; if nil, this project isn't a library
|
40
|
-
#
|
57
|
+
# Returns the library type of this project; if nil, this project isn't a library.
|
58
|
+
# +:both+ is when the project can be static AND dynamic without any changes inside the sourcecode.
|
59
|
+
# @return [:static, :dynamic, :s, :dyn, :both]
|
41
60
|
attr_accessor :libType
|
42
61
|
|
43
62
|
# Returns the dependencys of this project
|
44
63
|
# @return [Array<Project>]
|
45
64
|
attr_reader :dependencys
|
46
65
|
|
47
|
-
#
|
48
|
-
# @
|
49
|
-
|
50
|
-
|
51
|
-
#
|
52
|
-
# @
|
53
|
-
|
54
|
-
|
66
|
+
# Returns the toolchain used for this project
|
67
|
+
# @return [Hash]
|
68
|
+
attr_reader :toolchain
|
69
|
+
|
70
|
+
# Returns the defined symbols and its value for this project
|
71
|
+
# @return [Hash]
|
72
|
+
attr_reader :defines
|
73
|
+
|
74
|
+
# Returns the defined symbols and its value for config files for this project
|
75
|
+
# @return [Hash]
|
76
|
+
attr_reader :config_symbols
|
77
|
+
|
78
|
+
# Returns the config files that should be configured
|
79
|
+
# @return [Array<Hash>]
|
80
|
+
attr_reader :config_files
|
81
|
+
|
82
|
+
# Initializes a new instance of this class.
|
83
|
+
# Default language is "c".
|
84
|
+
#
|
85
|
+
# @param name [String] Name of the project; see {#name}
|
86
|
+
# @param options [Hash] Various options for the project
|
87
|
+
# @option options :lang [Symbol] Language used for this project. Available: +:c+, +:cpp+
|
88
|
+
# @option options :toolchain [String] Toolchain that should be used; if no is supplied, "gnu" is used.
|
89
|
+
# @option options :srcFile_endings [Array<String>] Additional fileendings that should be used for finding sourcefiles
|
90
|
+
# @option options :no_install [true, false] Flag that tells if the project is install-able or not; see {#no_install}
|
91
|
+
# @option options :libType [:static, :dynamic, :s, :dyn, :both, nil] Type of library; see {#libType}
|
92
|
+
# @option options :outputName [String] If the output name shoud differ from the project name, specify it here
|
93
|
+
# @option options :baseDir [String] Directory that should be the base of the project; doesn't change the build directory
|
94
|
+
def initialize(name, options = {})
|
95
|
+
if (!options.is_a?(Hash)) then
|
96
|
+
raise ArgumentError.new("Argument #2 (options) need to be an hash!");
|
97
|
+
end
|
98
|
+
|
55
99
|
@name = name;
|
56
100
|
@src_dirs = [];
|
101
|
+
@src_globs = [];
|
57
102
|
@inc_dirs = [];
|
103
|
+
@public_inc_dirs = [];
|
58
104
|
@lib_dirs = [];
|
59
105
|
@dependencys = [];
|
60
106
|
@librarys = [];
|
61
107
|
@flags = [];
|
108
|
+
@defines = {};
|
109
|
+
@config_symbols = {};
|
110
|
+
@config_files = [];
|
62
111
|
|
63
112
|
check_lang(options[:lang] || "c");
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
113
|
+
if (OPTIONS[:toolchainOverride]) then
|
114
|
+
if (!load_and_check_toolchain(OPTIONS[:toolchainOverride])) then
|
115
|
+
raise RuntimeError.new("Commandline specified a toolchain override, but toolchain cannot be found: '#{@toolchain_name}'");
|
116
|
+
end
|
117
|
+
else
|
118
|
+
if (!load_and_check_toolchain(options[:toolchain] || "gnu")) then
|
119
|
+
raise ArgumentError.new("Argument #2 (options) contains key toolchain, but toolchain cannot be found: '#{@toolchain_name}'");
|
120
|
+
end
|
69
121
|
end
|
70
122
|
|
71
123
|
@src_file_endings = [];
|
@@ -79,6 +131,32 @@ module RBuildSys
|
|
79
131
|
|
80
132
|
@no_install = options[:no_install] || false;
|
81
133
|
@libType = options[:libType];
|
134
|
+
@outputName = options[:outputName] || @name;
|
135
|
+
@baseDir = options[:baseDir] || ".";
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
def load_and_check_toolchain(name)
|
140
|
+
@toolchain_name = name;
|
141
|
+
@toolchain = TOOLCHAINS[@toolchain_name];
|
142
|
+
if (!@toolchain) then
|
143
|
+
return false;
|
144
|
+
end
|
145
|
+
|
146
|
+
@compiler = @toolchain["compiler"]["c"] if (@lang == :c);
|
147
|
+
@compiler = @toolchain["compiler"]["c++"] if (@lang == :cpp);
|
148
|
+
check_binary(@compiler);
|
149
|
+
|
150
|
+
@archiver = @toolchain["archiver"];
|
151
|
+
check_binary(@archiver);
|
152
|
+
|
153
|
+
return true;
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
def check_binary(bin)
|
158
|
+
`which #{bin}`;
|
159
|
+
raise RuntimeError.new("Trying to use binary '#{bin}', but binary dosnt exists or is not in PATH") if ($?.exitstatus != 0);
|
82
160
|
end
|
83
161
|
|
84
162
|
private
|
@@ -86,6 +164,11 @@ module RBuildSys
|
|
86
164
|
# https://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html
|
87
165
|
# or simply the language: c, c++, gnu, gnu++
|
88
166
|
def check_lang(lang)
|
167
|
+
if ([:c, :cpp].include?(lang)) then
|
168
|
+
@lang = lang;
|
169
|
+
return;
|
170
|
+
end
|
171
|
+
|
89
172
|
if ((m = lang.match(/^(c\+\+|gnu\+\+)([\dxyza]+)?$/)) != nil) then
|
90
173
|
@lang = :cpp;
|
91
174
|
@c_standard = lang if (m[2]);
|
@@ -104,22 +187,133 @@ module RBuildSys
|
|
104
187
|
return;
|
105
188
|
end
|
106
189
|
|
107
|
-
raise ArgumentError.new("argument #2 contains key lang, but cannot validate it: '#{lang}'");
|
190
|
+
raise ArgumentError.new("Initializer argument #2 (options) contains key lang, but cannot validate it: '#{lang}'");
|
108
191
|
end
|
109
192
|
|
110
193
|
public
|
111
|
-
#
|
194
|
+
# Cleans the project's output directory
|
112
195
|
def clean()
|
113
196
|
buildDir = "./build/#{name}";
|
114
197
|
FileUtils.remove_dir(buildDir) if File.directory?(buildDir)
|
115
198
|
end
|
116
199
|
|
200
|
+
private
|
201
|
+
def hasSymbol(sym)
|
202
|
+
return true if (@config_symbols.keys.include?(sym));
|
203
|
+
return CONFIG_SYMBOLS.keys.include?(sym);
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
def getSymbolValue(sym)
|
208
|
+
return @config_symbols[sym] if (@config_symbols.keys.include?(sym));
|
209
|
+
return CONFIG_SYMBOLS[sym];
|
210
|
+
end
|
211
|
+
|
212
|
+
public
|
213
|
+
# Configure a specific file
|
214
|
+
#
|
215
|
+
# @param input [String] The filename of the file that should be configured
|
216
|
+
# @param output [String] The filename that should be used to save the result, cannot be the same as the input!
|
217
|
+
# @param options [Hash] Some optional options
|
218
|
+
# @option options :realUndef [Boolean] If this is true, not defined symbols will be undefined with '#undef <symbol>'
|
219
|
+
def configure_file(input, output, options = {})
|
220
|
+
# based on https://cmake.org/cmake/help/latest/command/configure_file.html
|
221
|
+
|
222
|
+
puts("- configure: #{input}");
|
223
|
+
|
224
|
+
if (input.is_a?(String)) then
|
225
|
+
data = File.read(input);
|
226
|
+
end
|
227
|
+
|
228
|
+
# replace cmake defines
|
229
|
+
cmake_defines = data.to_enum(:scan, /\#cmakedefine ([a-zA-Z0-9_]+)([^\n]+)?/).map { Regexp.last_match };
|
230
|
+
for cmake_def in cmake_defines do
|
231
|
+
if (hasSymbol(cmake_def[1])) then
|
232
|
+
data.sub!(cmake_def[0], "#define #{cmake_def[1]}");
|
233
|
+
else
|
234
|
+
if (options[:realUndef]) then
|
235
|
+
data.sub!(cmake_def[0], "#undef #{cmake_def[1]}");
|
236
|
+
else
|
237
|
+
data.sub!(cmake_def[0], "/* #undef #{cmake_def[1]} */");
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# replace variables!
|
243
|
+
matches = data.to_enum(:scan, /\@([a-zA-Z0-9_]+)\@/).map { Regexp.last_match };
|
244
|
+
for match in matches do
|
245
|
+
if (hasSymbol(match[1])) then
|
246
|
+
data.sub!(match[0], getSymbolValue(match[1]).inspect);
|
247
|
+
else
|
248
|
+
puts "[WARN] in file #{input}: #{match[0]} found, but no value for it defined!";
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
File.write(output, data);
|
253
|
+
end
|
254
|
+
|
255
|
+
public
|
256
|
+
# Tests if the toolchain for this project is for windows.
|
257
|
+
#
|
258
|
+
# @return [Boolean] Returns true if windows, false otherwise
|
259
|
+
def isWindows?()
|
260
|
+
return @toolchain["os"] == "windows";
|
261
|
+
end
|
262
|
+
|
263
|
+
public
|
264
|
+
# Tests if the toolchain for this project is for macos.
|
265
|
+
#
|
266
|
+
# @return [Boolean] Returns true if macos, false otherwise
|
267
|
+
def isMac?()
|
268
|
+
return @toolchain["os"] == "macos";
|
269
|
+
end
|
270
|
+
|
271
|
+
public
|
272
|
+
# Tests if the toolchain for this project is for linux.
|
273
|
+
#
|
274
|
+
# @return [Boolean] Returns true if linux, false otherwise
|
275
|
+
def isLinux?()
|
276
|
+
return @toolchain["os"] == "linux";
|
277
|
+
end
|
278
|
+
|
279
|
+
private
|
280
|
+
def checkDependencys()
|
281
|
+
@dependencys.each_index {|idx|
|
282
|
+
dep = @dependencys[idx];
|
283
|
+
if (dep.is_a?(Array)) then
|
284
|
+
name = dep[0];
|
285
|
+
|
286
|
+
# use installed project
|
287
|
+
proj_conf_path = File.join(getInstallPath(), "lib", "rbuildsys_conf", "#{name}.config.json");
|
288
|
+
if (!File.exists?(proj_conf_path)) then
|
289
|
+
raise RuntimeError.new("Could not find project '#{name}'!");
|
290
|
+
end
|
291
|
+
|
292
|
+
proj_conf = JSON.parse(File.read(proj_conf_path));
|
293
|
+
if (!["static", "dynamic", "both"].include?(proj_conf["libType"])) then
|
294
|
+
raise RuntimeError.new("'#{name}' can't be used as a dependency because it is not a library");
|
295
|
+
end
|
296
|
+
if (proj_conf["libType"] != dep[1].to_s || proj_conf["libType"] == "both") then
|
297
|
+
raise RuntimeError.new("'#{name}' can't be linked with linktype #{dep[1]}");
|
298
|
+
end
|
299
|
+
|
300
|
+
if (proj_conf["toolchain"] != @toolchain_name) then
|
301
|
+
raise RuntimeError.new("Dependency '#{name}' was compiled using the toolchain #{proj_conf["toolchain"]}, while this project trys to use #{@toolchain_name}!");
|
302
|
+
end
|
303
|
+
|
304
|
+
@dependencys[idx] = proj_conf;
|
305
|
+
end
|
306
|
+
}
|
307
|
+
end
|
308
|
+
|
117
309
|
public
|
118
|
-
#
|
119
|
-
# @return [true, false]
|
310
|
+
# Builds the project
|
311
|
+
# @return [true, false] State of the build, true means success, false means failure
|
120
312
|
def build()
|
121
|
-
for dep in dependencys do
|
122
|
-
if (
|
313
|
+
for dep in @dependencys do
|
314
|
+
next if (OPTIONS[:cleanBuild]);
|
315
|
+
|
316
|
+
if (dep.is_a?(Project) && !dep.build()) then
|
123
317
|
return false;
|
124
318
|
end
|
125
319
|
end
|
@@ -127,28 +321,57 @@ module RBuildSys
|
|
127
321
|
puts "Now building '#{@name}'";
|
128
322
|
puts "- using language #{@lang}";
|
129
323
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
324
|
+
checkDependencys();
|
325
|
+
#pp @dependencys.map{|dep| if (dep.is_a?(Project)) then "local #{dep.name}" else "installed #{dep["name"]}" end }
|
326
|
+
|
327
|
+
# TODO: we dont check if previous build files where compiled with the current toolchain or not!
|
328
|
+
|
329
|
+
for config_file in @config_files do
|
330
|
+
configure_file(config_file[:input], config_file[:output], config_file[:options]);
|
331
|
+
end
|
332
|
+
|
333
|
+
for dep in @dependencys do
|
334
|
+
if (dep.is_a?(Project)) then
|
335
|
+
@inc_dirs.push(*dep.public_inc_dirs).uniq!;
|
336
|
+
@lib_dirs.push("./build/#{dep.name}");
|
337
|
+
@librarys.push(dep.name);
|
338
|
+
if (dep.libType == :static) then
|
339
|
+
@lib_dirs.push(*dep.lib_dirs).uniq!
|
340
|
+
@librarys.push(*dep.librarys).uniq!
|
341
|
+
end
|
342
|
+
elsif (dep.is_a?(Hash)) then
|
343
|
+
# dependency is an globaly installed library
|
344
|
+
@inc_dirs.push(File.join(getInstallPath(), "include", dep["name"])).uniq!;
|
345
|
+
@lib_dirs.push(File.join(getInstallPath(), "lib")).uniq!;
|
346
|
+
@librarys.push(dep["name"]);
|
347
|
+
if (dep["libType"] == "static") then
|
348
|
+
@lib_dirs.push(*dep["lib_dirs"]).uniq!;
|
349
|
+
@librarys.push(*dep["librarys"]).uniq!;
|
350
|
+
end
|
137
351
|
end
|
138
352
|
end
|
139
353
|
|
140
|
-
@flags.push("
|
354
|
+
@flags.push(@toolchain["flags"]["debug"]) if (OPTIONS[:debug]);
|
355
|
+
@flags.push(@toolchain["extra_flags"]) if (!@toolchain["extra_flags"].strip.empty?);
|
141
356
|
|
142
357
|
# create the project build dir
|
143
|
-
buildDir = "./build/#{name}";
|
144
|
-
|
358
|
+
buildDir = "./build/#{@name}";
|
359
|
+
FileUtils.mkdir_p(buildDir);
|
145
360
|
|
146
361
|
# make the includes
|
147
362
|
#inc_dirs.map! { |incDir| incDir + "/*.h" }
|
148
363
|
#includes = Dir.glob(inc_dirs);
|
149
|
-
includes = @inc_dirs.map{|incDir| "
|
150
|
-
libs = @lib_dirs.map{|libDir| "
|
151
|
-
std = @c_standard ? ("
|
364
|
+
includes = @inc_dirs.map{|incDir| "#{@toolchain["flags"]["include"]} #{incDir}"}.join(" ");
|
365
|
+
libs = @lib_dirs.map{|libDir| "#{@toolchain["flags"]["libPath"]} #{libDir}"}.join(" ") + " " + @librarys.map{|lib| "#{@toolchain["flags"]["libLink"]} #{lib}"}.join(" ");
|
366
|
+
std = @c_standard ? ("#{@toolchain["flags"]["langStd"]}=#{@c_standard}") : "";
|
367
|
+
|
368
|
+
defines = @defines.map{|sym,val|
|
369
|
+
if val == nil then
|
370
|
+
"#{@toolchain["flags"]["define"]}#{sym}"
|
371
|
+
else
|
372
|
+
"#{@toolchain["flags"]["define"]}#{sym}=#{val}"
|
373
|
+
end
|
374
|
+
}.join(" ");
|
152
375
|
|
153
376
|
# for now, just ignore all global and relative paths for sources
|
154
377
|
src_dirs.select! { |srcDir|
|
@@ -156,71 +379,202 @@ module RBuildSys
|
|
156
379
|
}
|
157
380
|
|
158
381
|
source_modified = false;
|
159
|
-
build_failed
|
382
|
+
build_failed = false;
|
383
|
+
|
384
|
+
build_file = ->(srcFile, srcDir) {
|
385
|
+
#objFile = File.join(buildDir, srcFile.gsub(srcDir, ""));
|
386
|
+
objFile = File.join(buildDir, srcFile);
|
387
|
+
objFile = objFile.gsub(Regexp.new("\.(#{@src_file_endings.join("|")})$"), ".o");
|
388
|
+
srcTime = File.mtime(srcFile);
|
389
|
+
|
390
|
+
if (OPTIONS[:cleanBuild] || OPTIONS[:cleanBuildAll] || !File.exists?(objFile) || (srcTime > File.mtime(objFile))) then
|
391
|
+
source_modified = true;
|
392
|
+
FileUtils.mkdir_p(File.dirname(objFile)); # ensure we have the parent dir(s)
|
393
|
+
|
394
|
+
# build the source!
|
395
|
+
cmd = "#{@compiler}"
|
396
|
+
cmd += " #{std}" if (!std.empty?);
|
397
|
+
cmd += " #{@toolchain["flags"]["pic"]}" if (@libType == :dynamic || @libType == :both);
|
398
|
+
cmd += " #{includes}";
|
399
|
+
cmd += " #{defines}" if (defines.size > 0);
|
400
|
+
cmd += " #{@flags.join(" ")}" if (@flags.size > 0);
|
401
|
+
cmd += " -c #{@toolchain["flags"]["output"]} #{objFile} #{srcFile}";
|
402
|
+
puts "- $ #{cmd}";
|
403
|
+
f = system(cmd);
|
404
|
+
if (f) then
|
405
|
+
FileUtils.touch(objFile, :mtime => srcTime);
|
406
|
+
else
|
407
|
+
build_failed = true;
|
408
|
+
end
|
409
|
+
end
|
410
|
+
}
|
160
411
|
|
161
412
|
src_dirs.each { |srcDir|
|
162
413
|
globStr = File.join(srcDir, "**/*.{#{@src_file_endings.join(",")}}");
|
163
414
|
srcFiles = Dir.glob(globStr);
|
415
|
+
srcFiles.each { |srcFile|
|
416
|
+
build_file.call(srcFile, srcDir)
|
417
|
+
}
|
418
|
+
}
|
164
419
|
|
165
|
-
|
420
|
+
src_globs.each{ |glob|
|
421
|
+
srcFiles = Dir.glob(glob);
|
166
422
|
srcFiles.each { |srcFile|
|
167
|
-
|
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
|
423
|
+
build_file.call(srcFile, File.dirname(srcFile))
|
185
424
|
}
|
186
425
|
}
|
187
426
|
|
188
427
|
if (build_failed) then
|
428
|
+
puts "Build failed, see log for details!";
|
189
429
|
return false;
|
190
430
|
end
|
191
431
|
|
192
|
-
if (!source_modified) then
|
193
|
-
puts "- No need for building library/executable, nothing changed!";
|
194
|
-
return true;
|
195
|
-
end
|
196
|
-
|
197
432
|
objFiles = Dir.glob(buildDir + "/**/*.o");
|
198
|
-
if (libType
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
433
|
+
if (@libType != nil) then
|
434
|
+
f = true;
|
435
|
+
|
436
|
+
if (@libType == :static || @libType == :both) then
|
437
|
+
|
438
|
+
libname = @toolchain["output_filenames"]["staticLib"].clone;
|
439
|
+
libname.gsub!(/\@[Nn][Aa][Mm][Ee]\@/, @outputName);
|
440
|
+
libpath = File.join(buildDir, libname);
|
441
|
+
|
442
|
+
if (!File.exists?(libpath) || source_modified) then
|
443
|
+
puts "- Building static library #{libname}!";
|
444
|
+
cmd = "#{@archiver} rcs #{libpath} #{objFiles.join(" ")}";
|
445
|
+
puts " - $ #{cmd}";
|
446
|
+
f_static = system(cmd);
|
447
|
+
f = false if (!f_static);
|
448
|
+
else
|
449
|
+
puts "- No need for building library, nothing changed!";
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
if (@libType == :dynamic || @libType == :both) then
|
454
|
+
#libname = @toolchain["output_filenames"]["dynamicLib"].clone;
|
455
|
+
#libname.gsub!(/\@[Nn][Aa][Mm][Ee]\@/, @outputName);
|
456
|
+
#puts "- Building dynamic library #{libname}!";
|
457
|
+
#libname = File.join(buildDir, "lib#{name}.so");
|
458
|
+
#cmd = ""
|
459
|
+
puts "[WARN] dynamic librarys are not implemented yet!";
|
460
|
+
end
|
461
|
+
|
462
|
+
metadata = {
|
463
|
+
name: @outputName,
|
464
|
+
libType: @libType,
|
465
|
+
lib_dirs: @lib_dirs,
|
466
|
+
librarys: @librarys,
|
467
|
+
dependencys: @dependencys.map {|dep|
|
468
|
+
if (dep.is_a?(Project)) then
|
469
|
+
dep.outputName
|
470
|
+
else
|
471
|
+
dep["name"];
|
472
|
+
end
|
473
|
+
},
|
474
|
+
toolchain: @toolchain_name
|
475
|
+
};
|
476
|
+
# TODO: copy the toolchain definition, if the toolchain is not permanently installed on the system!
|
477
|
+
metadataFile = File.join(buildDir, "#{@outputName}.config.json");
|
478
|
+
File.write(metadataFile, JSON.pretty_generate(metadata));
|
479
|
+
|
204
480
|
return f;
|
205
|
-
elsif (libType == :dynamic) then
|
206
|
-
raise NotImplementedError.new("");
|
207
481
|
else
|
208
482
|
# build runable binary
|
209
|
-
binname =
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
483
|
+
binname = @toolchain["output_filenames"]["exec"].clone;
|
484
|
+
binname.gsub!(/\@[Nn][Aa][Mm][Ee]\@/, @outputName);
|
485
|
+
binpath = File.join(buildDir, binname);
|
486
|
+
|
487
|
+
if (!File.exists?(binpath) || source_modified) then
|
488
|
+
puts "- Building executable #{binname}!";
|
489
|
+
cmd = "#{@compiler} #{includes}"
|
490
|
+
cmd += " #{@flags.join(" ")}" if (@flags.size > 0)
|
491
|
+
cmd += " #{@toolchain["flags"]["output"]} #{binpath} #{objFiles.join(" ")} #{libs}";
|
492
|
+
puts " - $ #{cmd}";
|
493
|
+
f = system(cmd);
|
494
|
+
return f;
|
495
|
+
else
|
496
|
+
puts "- No need for building binary, nothing changed!";
|
497
|
+
end
|
215
498
|
end
|
216
499
|
|
217
500
|
return true;
|
218
501
|
end
|
502
|
+
|
503
|
+
public
|
504
|
+
# Installs the project
|
505
|
+
def install()
|
506
|
+
dir = (File.expand_path(OPTIONS[:installDir]) || "/usr/local") if (isLinux? || isMac?)
|
507
|
+
dir = (File.expand_path(OPTIONS[:installDir]) || "C:/Program Files/#{@outputName}") if (isWindows?)
|
508
|
+
|
509
|
+
puts "RBuildSys will install #{@name} to the following location: #{dir}";
|
510
|
+
puts "Do you want to proceed? [y/N]: "
|
511
|
+
if ( STDIN.readline.strip != "y" ) then
|
512
|
+
puts "Aborting installation...";
|
513
|
+
exit(1);
|
514
|
+
end
|
515
|
+
|
516
|
+
# 1. install all includes
|
517
|
+
incDir = File.join(dir, "include", @outputName);
|
518
|
+
if (!Dir.exists?(incDir)) then
|
519
|
+
puts "- create dir: #{incDir}";
|
520
|
+
FileUtils.mkdir_p(incDir);
|
521
|
+
end
|
522
|
+
|
523
|
+
@public_inc_dirs.each {|d|
|
524
|
+
files = Dir.glob(File.join("#{d}", "**/*.{h,hpp}"));
|
525
|
+
files.each {|f|
|
526
|
+
dest = File.join(incDir, f.gsub(d, ""));
|
527
|
+
puts "- install: #{dest}";
|
528
|
+
destDir = File.dirname(dest);
|
529
|
+
if (!Dir.exists?(destDir)) then
|
530
|
+
puts "- create dir: #{destDir}";
|
531
|
+
FileUtils.mkdir_p(destDir);
|
532
|
+
end
|
533
|
+
FileUtils.copy_file(f, dest)
|
534
|
+
}
|
535
|
+
}
|
536
|
+
|
537
|
+
# 2.1 install library results (if any)
|
538
|
+
buildDir = "./build/#{@name}";
|
539
|
+
if (@libType) then
|
540
|
+
libDir = File.join(dir, "lib");
|
541
|
+
if (!Dir.exists?(libDir)) then
|
542
|
+
puts "- create dir: #{libDir}";
|
543
|
+
FileUtils.mkdir_p(libDir);
|
544
|
+
end
|
545
|
+
|
546
|
+
files = Dir.glob(File.join(buildDir, "*.{a,lib,so,dll}")); # TODO: this glob should based on the toolchain definition
|
547
|
+
files.each {|f|
|
548
|
+
dest = File.join(libDir, f.gsub(buildDir, ""));
|
549
|
+
puts "- install: #{dest}";
|
550
|
+
FileUtils.copy_file(f, dest)
|
551
|
+
}
|
552
|
+
|
553
|
+
# install definitions so we can use them in other projects easier!
|
554
|
+
metadataDir = File.join(libDir, "rbuildsys_conf");
|
555
|
+
if (!Dir.exists?(metadataDir)) then
|
556
|
+
puts "- create dir: #{metadataDir}";
|
557
|
+
FileUtils.mkdir_p(metadataDir);
|
558
|
+
end
|
559
|
+
metadataFile = File.join(metadataDir, "#{@outputName}.config.json");
|
560
|
+
puts "- install: #{metadataFile}";
|
561
|
+
FileUtils.copy_file(File.join(buildDir, "#{@outputName}.config.json"), metadataFile);
|
562
|
+
end
|
563
|
+
|
564
|
+
# 2.2 install executable results
|
565
|
+
if (!@libType) then
|
566
|
+
raise NotImplementedError.new("installation of executables (*.exe, *.run etc.) is not supported yet");
|
567
|
+
end
|
568
|
+
|
569
|
+
end
|
219
570
|
end
|
220
571
|
|
221
572
|
# Stores all projects that are defined by the user
|
222
573
|
PROJECTS = {};
|
223
574
|
|
575
|
+
private
|
576
|
+
DECLARED_PROJECTS = [];
|
577
|
+
|
224
578
|
# Holds the current project that is currently being configured.
|
225
579
|
# Only non-nil inside the block for {#newProject}
|
226
580
|
@@cur_proj = nil;
|
@@ -234,177 +588,511 @@ module RBuildSys
|
|
234
588
|
# true if we only want to clean our build directory
|
235
589
|
:clean => false,
|
236
590
|
|
237
|
-
# true if we want to clean
|
591
|
+
# true if we want to make a clean rebuild for only the specified project(s)
|
238
592
|
:cleanBuild => false,
|
239
593
|
|
594
|
+
# true if we want to make a clean rebuild for the specified project(s) and its dependencys
|
595
|
+
:cleanBuildAll => false,
|
596
|
+
|
240
597
|
# true if we want to install instead of building
|
241
598
|
:install => false,
|
599
|
+
|
600
|
+
# contains the path where to install the project(s)
|
601
|
+
:installDir => ENV["RBUILDSYS_INSTALLDIR"],
|
602
|
+
|
603
|
+
# overrides the default toolchain specified in the build-script
|
604
|
+
:toolchainOverride => nil,
|
242
605
|
};
|
243
606
|
|
607
|
+
# Stores all currently loaded toolchains
|
608
|
+
TOOLCHAINS = {};
|
609
|
+
|
610
|
+
# Stores all global symbols to configure files
|
611
|
+
CONFIG_SYMBOLS = {};
|
612
|
+
|
244
613
|
# Option parser of RBuildSys, used to provide standard operations such as clean, clean build, install...
|
245
614
|
@@optparse = OptionParser.new do |opts|
|
246
|
-
|
615
|
+
|
616
|
+
class MyBanner
|
617
|
+
def to_s
|
618
|
+
gemDir = File.dirname(File.expand_path(__FILE__));
|
619
|
+
toolchains = Dir.glob("#{gemDir}/toolchains/*.json");
|
620
|
+
toolchains.map!{|f| File.basename(f, '.json') }
|
621
|
+
|
622
|
+
return "Usage: #{$PROGRAM_NAME} [options] <projects>\n" +
|
623
|
+
"Projects:\n" +
|
624
|
+
" " + (PROJECTS.keys + DECLARED_PROJECTS).join(", ") + "\n" +
|
625
|
+
"Toolchains:\n" +
|
626
|
+
" builtin: " + toolchains.join(", ") + "\n" +
|
627
|
+
" user : " + (TOOLCHAINS.keys - toolchains).join(", ") + "\n" +
|
628
|
+
"Options:";
|
629
|
+
end
|
630
|
+
end
|
631
|
+
opts.banner = MyBanner.new();
|
247
632
|
|
248
633
|
opts.on("-r","--release", "Build for release") do
|
249
634
|
# todo: if compiled for debug before, the system cannot detect the change
|
250
635
|
OPTIONS[:debug] = false;
|
251
636
|
end
|
252
|
-
opts.on("
|
637
|
+
opts.on("--rebuild", "Make a clean rebuild only for the given project(s)") do
|
253
638
|
OPTIONS[:cleanBuild] = true;
|
254
639
|
end
|
255
|
-
opts.on("-
|
640
|
+
opts.on("--rebuild-all", "Make a clean rebuild for the given project(s) and its dependencys") do
|
641
|
+
OPTIONS[:cleanBuildAll] = true;
|
642
|
+
end
|
643
|
+
opts.on("-c", "--clean", "Clean the build dirs for the given project(s)") do
|
256
644
|
OPTIONS[:clean] = true;
|
257
645
|
end
|
258
|
-
opts.on("-i", "--install", "Install all project(s) that are installable") do
|
646
|
+
opts.on("-i", "--install[=DIR]", "Install all project(s) that are installable") do |dir|
|
647
|
+
if (dir) then
|
648
|
+
OPTIONS[:installDir] = dir;
|
649
|
+
end
|
259
650
|
OPTIONS[:install] = true;
|
260
651
|
end
|
652
|
+
opts.on("--installDir=DIR", "Sets the directory to use for installation, and search for installed projects. Can also be configured with the environment variable $RBUILDSYS_INSTALLDIR.") do |dir|
|
653
|
+
OPTIONS[:installDir] = dir;
|
654
|
+
end
|
655
|
+
opts.on("-t TOOLCHAIN", "--toolchain=TOOLCHAIN", "Use the specifed toolchain to build the project and its dependencys") do |toolchain|
|
656
|
+
if (toolchain =~ /^[a-zA-Z0-9\_\-]+$/) then
|
657
|
+
OPTIONS[:toolchainOverride] = toolchain;
|
658
|
+
else
|
659
|
+
if (File.exists?(toolchain)) then
|
660
|
+
OPTIONS[:toolchainOverride] = RBuildSys.loadToolchain(toolchain)["name"];
|
661
|
+
else
|
662
|
+
raise RuntimeError.new("Commandline argument for toolchain must either be a alpha-numeric-underscore name or an filename!");
|
663
|
+
end
|
664
|
+
end
|
665
|
+
end
|
666
|
+
opts.on("-sKEY=VALUE", "Sets the symbol KEY to VALUE for config files") do |str|
|
667
|
+
# TODO: this might be not enough to override options already set in projects
|
668
|
+
data = str.split("=");
|
669
|
+
CONFIG_SYMBOLS[data[0]] = data[1];
|
670
|
+
end
|
261
671
|
end
|
262
672
|
|
263
|
-
#
|
264
|
-
#
|
265
|
-
# and has
|
673
|
+
# Creates a new project to build.
|
674
|
+
# Takes a block to execute, that defines the project. The block is immediately executed,
|
675
|
+
# and has access to the current project (that is created with this method) in {RBuildSys::@@cur_proj RBuildSys::@@cur_proj}.
|
266
676
|
#
|
267
677
|
# @yield Block to configure the project
|
268
678
|
#
|
269
|
-
# @param name [String]
|
270
|
-
# @param options [Hash]
|
679
|
+
# @param name [String] Name of the project, also used for the final output of the project
|
680
|
+
# @param options [Hash] Various options; for details see {Project#initialize}
|
271
681
|
def newProject(name, options = {})
|
272
|
-
raise ArgumentError.new("name need to be a string") if (name.class != String);
|
682
|
+
raise ArgumentError.new("Argument #1 (name) need to be a string") if (name.class != String);
|
683
|
+
raise ArgumentError.new("Argument #2 (options) need to be an hash") if (!options.is_a?(Hash));
|
684
|
+
|
273
685
|
@@cur_proj = Project.new(name, options);
|
274
686
|
yield
|
275
687
|
PROJECTS[name] = @@cur_proj;
|
276
688
|
@@cur_proj = nil;
|
277
689
|
end
|
278
690
|
|
279
|
-
#
|
691
|
+
# Begins the build process.
|
692
|
+
# Should be called after you configured all your projects with {#newProject}.
|
693
|
+
def build()
|
694
|
+
parseARGV();
|
695
|
+
ARGV.each { |projname|
|
696
|
+
proj = PROJECTS[projname];
|
697
|
+
if (proj) then
|
698
|
+
if (OPTIONS[:clean]) then
|
699
|
+
proj.clean();
|
700
|
+
elsif (OPTIONS[:install]) then
|
701
|
+
proj.install();
|
702
|
+
else
|
703
|
+
proj.build();
|
704
|
+
end
|
705
|
+
end
|
706
|
+
}
|
707
|
+
end
|
708
|
+
|
709
|
+
# Parses the commandline arguments
|
710
|
+
# @note this should be called before any call to {#newProject} and/or {#build}!
|
711
|
+
def parseARGV()
|
712
|
+
@@optparse.parse!
|
713
|
+
if (ARGV.length == 0) then
|
714
|
+
puts @@optparse;
|
715
|
+
exit(1);
|
716
|
+
end
|
717
|
+
end
|
718
|
+
|
719
|
+
# Declares the project's name so they can be displayed in the help text!
|
720
|
+
def declareProjects(*projects)
|
721
|
+
DECLARED_PROJECTS.push(*projects);
|
722
|
+
end
|
723
|
+
|
724
|
+
# Returns the version of RBuildSys
|
725
|
+
# @return ["1.0.0"]
|
726
|
+
def sysVersion()
|
727
|
+
return "1.0.0";
|
728
|
+
end
|
729
|
+
|
730
|
+
# Checks if this RBuildSys is at minimum the required version.
|
731
|
+
# This is only for scripts that dont use systems like bundler etc.
|
732
|
+
# If you use bundler or similar dependency management software, you propably should use their features instead of this.
|
733
|
+
# @return [Boolean]
|
734
|
+
def checkSysVersion(min_version)
|
735
|
+
data_min = min_version.split(".");
|
736
|
+
data_ver = sysVersion().split(".");
|
737
|
+
|
738
|
+
if (data_ver.size != data_min.size) then
|
739
|
+
return false;
|
740
|
+
end
|
741
|
+
if (data_ver.size == 4 && data_ver[3] != data_min[3]) then
|
742
|
+
# 4th part dosnt match. Should be something like 'pre' or similar.
|
743
|
+
return false;
|
744
|
+
end
|
745
|
+
|
746
|
+
data_min.pop();
|
747
|
+
data_ver.pop();
|
748
|
+
|
749
|
+
while (data_ver.size > 0) do
|
750
|
+
a = data_min.pop();
|
751
|
+
b = data_ver.pop();
|
752
|
+
return false if (a > b);
|
753
|
+
end
|
754
|
+
|
755
|
+
return true;
|
756
|
+
end
|
757
|
+
|
758
|
+
#########################################################################################
|
759
|
+
# Project configuration #
|
760
|
+
#########################################################################################
|
761
|
+
|
762
|
+
# Adds include directorys to use for the current project
|
280
763
|
#
|
281
|
-
# @note
|
282
|
-
# @param dir [String, Array<String>]
|
283
|
-
# @param more [Array<String>]
|
764
|
+
# @note Should only be called inside the block for {#newProject}
|
765
|
+
# @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}
|
766
|
+
# @param more [Array<String>] Additional include paths; each element is used in a call to {#incDir}
|
284
767
|
def incDir(dir, *more)
|
285
768
|
if (dir.class == String) then
|
286
|
-
|
287
|
-
|
769
|
+
dirPath = File.join(@@cur_proj.baseDir, dir);
|
770
|
+
raise ArgumentError.new("Argument #1 is no directory: '#{dir}' ('#{dirPath}')") if (!Dir.exists?(dirPath));
|
771
|
+
@@cur_proj.inc_dirs.push(dirPath);
|
288
772
|
elsif (dir.class == Array) then
|
289
773
|
dir.each { |d| incDir(d) }
|
290
774
|
else
|
291
|
-
raise ArgumentError.new("
|
775
|
+
raise ArgumentError.new("Argument #1 must be a String or an Array");
|
292
776
|
end
|
293
777
|
more.each { |d| incDir(d) }
|
294
778
|
end
|
295
779
|
|
296
|
-
#
|
780
|
+
# Adds include directorys to use for projects that depends on the current project
|
297
781
|
#
|
298
|
-
# @note
|
299
|
-
# @param dir [String, Array<String>]
|
300
|
-
# @param more [Array<String>]
|
782
|
+
# @note Should only be called inside the block for {#newProject}
|
783
|
+
# @param dir [String, Array<String>] Directory path; root of the include path. If its a Array, each element is used in a call to {#publish_incDir}
|
784
|
+
# @param more [Array<String>] Additional include paths; each element is used in a call to {#publish_incDir}
|
785
|
+
def publish_incDir(dir, *more)
|
786
|
+
if (@@cur_proj.libType == nil) then
|
787
|
+
raise RuntimeError.new("Can only be called when the project is configured as an library");
|
788
|
+
end
|
789
|
+
|
790
|
+
if (dir.class == String) then
|
791
|
+
dirPath = File.join(@@cur_proj.baseDir, dir);
|
792
|
+
raise ArgumentError.new("Argument #1 is no directory: '#{dir}' ('#{dirPath}')") if (!Dir.exists?(dirPath));
|
793
|
+
@@cur_proj.public_inc_dirs.push(dirPath);
|
794
|
+
elsif (dir.class == Array) then
|
795
|
+
dir.each { |d| publish_incDir(d) }
|
796
|
+
else
|
797
|
+
raise ArgumentError.new("Argument #1 must be a String or an Array");
|
798
|
+
end
|
799
|
+
more.each { |d| publish_incDir(d) }
|
800
|
+
end
|
801
|
+
|
802
|
+
# Adds source directorys to use for the current project
|
803
|
+
#
|
804
|
+
# @note Should only be called inside the block for {#newProject}
|
805
|
+
# @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}
|
806
|
+
# @param more [Array<String>] Additional source paths; each element is used in a call to {#srcDir}
|
301
807
|
def srcDir(dir, *more)
|
302
808
|
if (dir.class == String) then
|
303
|
-
|
304
|
-
|
809
|
+
dirPath = File.join(@@cur_proj.baseDir, dir);
|
810
|
+
raise ArgumentError.new("Argument #1 is no directory: '#{dir}' ('#{dirPath}')") if (!Dir.exists?(dirPath));
|
811
|
+
@@cur_proj.src_dirs.push(dirPath);
|
305
812
|
elsif (dir.class == Array) then
|
306
813
|
dir.each { |d| srcDir(d) }
|
307
814
|
else
|
308
|
-
raise ArgumentError.new("
|
815
|
+
raise ArgumentError.new("Argument #1 must be a String or an Array");
|
309
816
|
end
|
310
817
|
more.each { |d| srcDir(d) }
|
311
818
|
end
|
312
819
|
|
313
|
-
#
|
314
|
-
# this means that if you run an install of your projects, this one will not be installed
|
820
|
+
# Adds a source glob to use for the current project
|
315
821
|
#
|
316
|
-
# @note
|
822
|
+
# @note Should only be called inside the block for {#newProject}
|
823
|
+
# @param glob [String, Array<String>] Glob to be used to find source files; If its a Array, each element is used in a call to {#srcGlob}
|
824
|
+
# @param more [Array<String>] Additional source globs; each element is used in a call to {#srcGlob}
|
825
|
+
def srcGlob(glob, *more)
|
826
|
+
if (glob.class == String) then
|
827
|
+
@@cur_proj.src_globs.push(File.join(@@cur_proj.baseDir, glob));
|
828
|
+
elsif (glob.class == Array) then
|
829
|
+
glob.each { |g| srcGlob(g) }
|
830
|
+
else
|
831
|
+
raise ArgumentError.new("Argument #1 must be a String or an Array");
|
832
|
+
end
|
833
|
+
more.each { |g| srcGlob(g) }
|
834
|
+
end
|
835
|
+
|
836
|
+
# Tells the current project that it is not install-able
|
837
|
+
# This means that if you run an install of your projects, this one will not be installed
|
838
|
+
#
|
839
|
+
# @note Should only be called inside the block for {#newProject}
|
317
840
|
def noInstall()
|
318
841
|
@@cur_proj.no_install = true;
|
319
842
|
end
|
320
843
|
|
321
|
-
#
|
844
|
+
# Sets the current project as a lib
|
322
845
|
#
|
323
|
-
# @note
|
324
|
-
# @param type [:static, :dynamic, :s, :dyn]
|
846
|
+
# @note Should only be called inside the block for {#newProject}
|
847
|
+
# @param type [:static, :dynamic, :s, :dyn, :both] The type of the library: static (*.a / *.lib) or dynamic (*.so / *.dll)
|
325
848
|
def isLib(type)
|
326
849
|
@@cur_proj.libType = :static if ([:static, :s].include?(type));
|
327
850
|
@@cur_proj.libType = :dynamic if ([:dynamic, :dyn, :d].include?(type));
|
851
|
+
@@cur_proj.libType = :both if (type == :both);
|
852
|
+
raise ArgumentError.new("Argument #1 (type) must be one of the following: :static, :dynamic, :s, :dyn, :both") if (@@cur_proj.libType == nil)
|
328
853
|
end
|
329
854
|
|
330
|
-
#
|
331
|
-
#
|
855
|
+
# Returns the install path for the current project
|
856
|
+
#
|
857
|
+
# @note Should only be called inside the block for {#newProject}
|
858
|
+
def getInstallPath()
|
859
|
+
dir = OPTIONS[:installDir] || "/usr/include" if (isLinux? || isMac?);
|
860
|
+
dir = OPTIONS[:installDir] || "C:/Program Files/#{@@cur_proj.outputName}" if (isWindows?);
|
861
|
+
return File.expand_path(dir);
|
862
|
+
end
|
863
|
+
|
864
|
+
# Links a RBuildSys project to the current project.
|
865
|
+
# This has the effect that the given project is build before the current project, and
|
332
866
|
# if the given project is a lib, it is also linked to the current project
|
333
867
|
#
|
334
|
-
# @note
|
335
|
-
# @param name [String]
|
336
|
-
|
868
|
+
# @note Should only be called inside the block for {#newProject}
|
869
|
+
# @param name [String] Can be a local project (inside current file) or any installed project
|
870
|
+
# @param linktype [:static, :dynamic] Specify wich linkage type should be used to link the project with the current one.
|
871
|
+
# If the project to link dosnt support the linktype, this raises an error.
|
872
|
+
def use(name, linktype)
|
337
873
|
if (PROJECTS[name] == nil) then
|
338
|
-
|
339
|
-
raise NotImplementedError.new("");
|
874
|
+
@@cur_proj.dependencys.push([name, linktype]);
|
340
875
|
else
|
341
876
|
# use local
|
342
877
|
proj = PROJECTS[name];
|
878
|
+
|
879
|
+
if (!proj.libType) then
|
880
|
+
raise ArgumentError.new("Argument #1 can't be used cause it is not a library");
|
881
|
+
end
|
882
|
+
if (proj.libType != linktype && proj.libType != :both) then
|
883
|
+
raise ArgumentError.new("Argument #1 can't be linked with linktype #{linktype}!");
|
884
|
+
end
|
885
|
+
|
886
|
+
@@cur_proj.dependencys.push(proj);
|
887
|
+
end
|
888
|
+
end
|
889
|
+
|
890
|
+
# Adds a external library through pkg-config.
|
891
|
+
# If no pkg-config is installed on the system, this functions raises an error
|
892
|
+
#
|
893
|
+
# @note Should only be called inside the block for {#newProject}
|
894
|
+
# @note For retriving the flags from pkg-config, +--libs+ and +--cflags+ is used, and the output is given to {#flag}
|
895
|
+
# @param name [String] Name of the pkg-config package
|
896
|
+
def usePkgconf(name)
|
897
|
+
if (`which pkg-config` == "") then
|
898
|
+
raise ArgumentError.new("Option pkgconf specified, but no pkg-config installed on system!");
|
343
899
|
end
|
344
900
|
|
345
|
-
|
346
|
-
|
901
|
+
modver = `pkg-config --short-errors --modversion #{name}`;
|
902
|
+
if (modver.start_with?("No package") or $?.exitstatus != 0) then
|
903
|
+
raise RuntimeError.new("Could not find package '#{name}' with pkg-config!");
|
347
904
|
end
|
348
905
|
|
349
|
-
|
906
|
+
linker_flags = `pkg-config --short-errors --libs #{name}`;
|
907
|
+
if ($?.exitstatus != 0) then
|
908
|
+
raise RuntimeError.new("Could not get linkler flags for '#{name}', plg-config error: #{linker_flags}");
|
909
|
+
end
|
910
|
+
flag(linker_flags);
|
911
|
+
|
912
|
+
cflags = `pkg-config --short-errors --cflags #{name}`;
|
913
|
+
if ($?.exitstatus != 0) then
|
914
|
+
raise RuntimeError.new("Could not get compiler flags for '#{name}', plg-config error: #{linker_flags}");
|
915
|
+
end
|
916
|
+
flag(cflags);
|
350
917
|
end
|
351
918
|
|
352
|
-
#
|
919
|
+
# Adds a external library to the current project
|
353
920
|
#
|
354
|
-
# @note
|
355
|
-
# @note
|
356
|
-
# @param name [String]
|
357
|
-
# @param path [String, nil]
|
358
|
-
|
921
|
+
# @note Should only be called inside the block for {#newProject}
|
922
|
+
# @note There is currently no check in place to verify that the given library exists
|
923
|
+
# @param name [String] Name of the library that should be used. in a c/c++ context, it should be without the leading "lib"
|
924
|
+
# @param path [String, nil] Optional search path for this library
|
925
|
+
# @param options [Hash] Optional options
|
926
|
+
# @option options :pkgconf [Boolean] If true, search pkgconf(ig) for additional informations about the lib
|
927
|
+
def useLib(name, path = nil, options = {})
|
928
|
+
if (options[:pkgconf]) then
|
929
|
+
usePkgconf(name);
|
930
|
+
return;
|
931
|
+
end
|
932
|
+
|
359
933
|
@@cur_proj.librarys.push(name);
|
360
934
|
@@cur_proj.lib_dirs.push(path) if (path != nil && Dir.exists?(path));
|
361
935
|
end
|
362
936
|
|
363
|
-
#
|
937
|
+
# Adds a define to the compiler call
|
938
|
+
#
|
939
|
+
# @note Should only be called inside the block for {#newProject}
|
940
|
+
# @param symbol [String] Symbol that should be defined
|
941
|
+
# @param value [String, nil] Optional value the symbol should hold
|
942
|
+
def define(symbol, value = nil)
|
943
|
+
@@cur_proj.defines[symbol] = value;
|
944
|
+
end
|
945
|
+
|
946
|
+
# Adds a flag to the flags of the current project.
|
947
|
+
#
|
948
|
+
# @note Should only be called inside the block for {#newProject}
|
949
|
+
# @note This should only be used when nothing else works, because this method dosnt ensure that the flags used are working with the current toolchain!
|
950
|
+
# @param flag [String] the flag to be added
|
951
|
+
def flag(flag)
|
952
|
+
@@cur_proj.flags.push(flag);
|
953
|
+
end
|
954
|
+
|
955
|
+
#########################################################################################
|
956
|
+
# Option parsing #
|
957
|
+
#########################################################################################
|
958
|
+
|
959
|
+
# Adds an option to the option parser of RBuildSys
|
364
960
|
# @see OptionParser#on
|
365
961
|
def onOption(*opts, &block)
|
366
962
|
@@optparse.on(*opts, block);
|
367
963
|
end
|
368
964
|
|
369
|
-
#
|
965
|
+
# Adds an option to the option parser of RBuildSys
|
370
966
|
# @see OptionParser#on_tail
|
371
967
|
def onTailOption(*opts, &block)
|
372
968
|
@@optparse.on_tail(*opts, block);
|
373
969
|
end
|
374
970
|
|
375
|
-
#
|
971
|
+
# Adds an option to the option parser of RBuildSys
|
376
972
|
# @see OptionParser#on_head
|
377
973
|
def onHeadOption(*opts, &block)
|
378
974
|
@@optparse.on_head(*opts, block);
|
379
975
|
end
|
380
976
|
|
381
|
-
|
977
|
+
#########################################################################################
|
978
|
+
# Toolchains #
|
979
|
+
#########################################################################################
|
980
|
+
|
981
|
+
# Loads a toolchain definition from a file. Format needs to be JSON.
|
382
982
|
#
|
383
|
-
# @
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
983
|
+
# @param file [String] path to the toolchain definition file
|
984
|
+
def self.loadToolchain(file)
|
985
|
+
if (!File.exists?(file)) then
|
986
|
+
raise ArgumentError.new("Argument #1 (file) needs to be the path to an existing file");
|
987
|
+
end
|
388
988
|
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
@@optparse.parse!
|
393
|
-
if (ARGV.length == 0) then
|
394
|
-
puts @@optparse;
|
395
|
-
exit(1);
|
989
|
+
data = JSON.parse(File.read(file));
|
990
|
+
if (TOOLCHAINS.keys.include?(data["name"])) then
|
991
|
+
raise RuntimeError.new("Cannot load toolchain '#{data["name"]}' from '#{file}': already loaded");
|
396
992
|
end
|
993
|
+
TOOLCHAINS[data["name"]] = data;
|
397
994
|
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
995
|
+
return data;
|
996
|
+
end
|
997
|
+
|
998
|
+
private
|
999
|
+
def self.load_default_toolchains
|
1000
|
+
# load builtin toolchains
|
1001
|
+
gemDir = File.dirname(File.expand_path(__FILE__));
|
1002
|
+
toolchains = Dir.glob("#{gemDir}/toolchains/*.json");
|
1003
|
+
toolchains.each {|f|
|
1004
|
+
self.loadToolchain(f);
|
407
1005
|
}
|
1006
|
+
|
1007
|
+
# use toolchain installed for the user
|
1008
|
+
toolchains = Dir.glob("#{Dir.home}/.rbuildsys/toolchains/*.json");
|
1009
|
+
toolchains.each {|f|
|
1010
|
+
self.loadToolchain(f);
|
1011
|
+
}
|
1012
|
+
end
|
1013
|
+
load_default_toolchains()
|
1014
|
+
|
1015
|
+
#########################################################################################
|
1016
|
+
# Configure files #
|
1017
|
+
#########################################################################################
|
1018
|
+
|
1019
|
+
public
|
1020
|
+
# Sets a symbol with an optional value to configure files. See {#configureFile}
|
1021
|
+
#
|
1022
|
+
# @note When used inside the block for {#newProject}, the symbol is only visible for the project. If used outside of the block, the symbol is visible for everyone!
|
1023
|
+
# @param symbol [String] The symbol that should be set
|
1024
|
+
# @param value [String, nil] Optional value for the symbol
|
1025
|
+
def set(symbol, value = nil)
|
1026
|
+
if (@@cur_proj == nil) then
|
1027
|
+
CONFIG_SYMBOLS[symbol] = value;
|
1028
|
+
else
|
1029
|
+
@@cur_proj.config_symbols[symbol] = value;
|
1030
|
+
end
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
# Tells the project that, in order to build it, the file specified at +input+ must be transformed to the file at +output+.
|
1034
|
+
#
|
1035
|
+
# @note Should only be called inside the block for {#newProject}
|
1036
|
+
# @param input [String] The filename of the file that should be configured
|
1037
|
+
# @param output [String] The filename that should be used to save the result, cannot be the same as the input!
|
1038
|
+
# @param options [Hash] Some optional options
|
1039
|
+
def configureFile(input, output, options = {})
|
1040
|
+
if (!input.is_a?(String) || input.strip.empty?) then
|
1041
|
+
raise ArgumentError.new("Argument #1 (input) needs to be a string");
|
1042
|
+
end
|
1043
|
+
|
1044
|
+
if (!output.is_a?(String) || output.strip.empty?) then
|
1045
|
+
raise ArgumentError.new("Argument #2 (output) needs to be a string");
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
if (input == output) then
|
1049
|
+
raise ArgumentError.new("Argument #1 (input) and #2 (output) cannot be the same");
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
@@cur_proj.config_files.push({
|
1053
|
+
input: File.join(@@cur_proj.baseDir, input),
|
1054
|
+
output: File.join(@@cur_proj.baseDir, output),
|
1055
|
+
options: options
|
1056
|
+
});
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
#########################################################################################
|
1060
|
+
# Utils #
|
1061
|
+
#########################################################################################
|
1062
|
+
|
1063
|
+
# Shorthand for {Project#isWindows?}
|
1064
|
+
def isWindows?()
|
1065
|
+
return @@cur_proj.isWindows?;
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
# Shorthand for {Project#isMac?}
|
1069
|
+
def isMac?()
|
1070
|
+
return @@cur_proj.isMac?;
|
1071
|
+
end
|
1072
|
+
|
1073
|
+
# Shorthand for {Project#isMac?}
|
1074
|
+
def isLinux?()
|
1075
|
+
return @@cur_proj.isLinux?;
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
# Tests if the toolchain is the given type of OS.
|
1079
|
+
#
|
1080
|
+
# @param type [:win, :win32, :windows, :mac, :macos, :apple, :linux] the OS to check
|
1081
|
+
# @return [Boolean] Returns true if OS is correct, false otherwise
|
1082
|
+
def isToolchainOS?(type)
|
1083
|
+
if ([:win, :win32, :windows].include?(type)) then
|
1084
|
+
return @@cur_proj.isWindows?;
|
1085
|
+
end
|
1086
|
+
|
1087
|
+
if ([:mac, :macos, :apple].include?(type)) then
|
1088
|
+
return @@cur_proj.isMac?();
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
if ([:linux].include?(type)) then
|
1092
|
+
return @@cur_proj.isLinux?();
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
raise ArgumentError.new("Unexpected argument: os-type '#{type}' unknown");
|
408
1096
|
end
|
409
1097
|
|
410
1098
|
end
|