erebrus 0.1.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +7 -0
- data/Rakefile +8 -0
- data/bin/erebrus +401 -0
- data/examples/Buildfile +96 -0
- data/lib/erebrus/build_engine.rb +449 -0
- data/lib/erebrus/dsl.rb +165 -0
- data/lib/erebrus/target.rb +130 -0
- data/lib/erebrus/version.rb +3 -0
- data/lib/erebrus.rb +10 -0
- data/sig/erebrus.rbs +4 -0
- metadata +66 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 76b069200add69a2d85d26e12f70dd726611b1873e77f9ce0f39b66b5c836efb
|
|
4
|
+
data.tar.gz: 407eb8f255a7bdc2cc495a66d5dc712e93f467ca8a01f20ff95e5762a89f1add
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e41e9a1e3878050ba948b036576a7630f34d94cbaa9c5e897ce07eadbac10e18fe79ce5aa0d882ff5336178fd7a504e2ec1728552a2dd3a85d7f4f3304831360
|
|
7
|
+
data.tar.gz: 4a35080e1c2bd7025cc66510457ceba4d9372254cf42916f4908cba345f8ccbdc97cc92a8d833819d577010a2ffdd3c58b515896531dce39b3c49b5c2443152d
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Daedalus OS
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
data/bin/erebrus
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require "thor"
|
|
3
|
+
require "erebrus"
|
|
4
|
+
|
|
5
|
+
class ErebrusCommand < Thor
|
|
6
|
+
class_option :verbose, aliases: "-v", type: :boolean, desc: "Verbose output"
|
|
7
|
+
class_option :file, aliases: "-f", desc: "Buildfile to use", default: "Buildfile"
|
|
8
|
+
|
|
9
|
+
desc "build [TARGET]", "Builds the project with optional target"
|
|
10
|
+
option :namespace, aliases: "-n", desc: "Target namespace"
|
|
11
|
+
option :var, aliases: "-D", type: :hash, desc: "Set variables (e.g., -D CC=gcc -D CFLAGS=-O2)"
|
|
12
|
+
option :parallel, aliases: "-j", type: :numeric, desc: "Number of parallel jobs"
|
|
13
|
+
def build(target = nil)
|
|
14
|
+
load_buildfile_with_error_handling
|
|
15
|
+
|
|
16
|
+
target_name = resolve_target_name(target)
|
|
17
|
+
|
|
18
|
+
context = prepare_context
|
|
19
|
+
|
|
20
|
+
begin
|
|
21
|
+
Erebrus.build(target_name, context)
|
|
22
|
+
puts "Build completed successfully!" unless options[:quiet]
|
|
23
|
+
rescue Erebrus::Error => e
|
|
24
|
+
puts "Build failed: #{e.message}"
|
|
25
|
+
exit 1
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
puts "Unexpected error: #{e.message}"
|
|
28
|
+
puts e.backtrace if options[:verbose]
|
|
29
|
+
exit 1
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
desc "list [NAMESPACE]", "Lists all available targets, optionally filtered by namespace"
|
|
34
|
+
def list(namespace = nil)
|
|
35
|
+
load_buildfile_with_error_handling
|
|
36
|
+
|
|
37
|
+
begin
|
|
38
|
+
if namespace
|
|
39
|
+
Erebrus.list_targets(namespace: namespace)
|
|
40
|
+
else
|
|
41
|
+
Erebrus.list_targets
|
|
42
|
+
end
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
puts "Error listing targets: #{e.message}"
|
|
45
|
+
exit 1
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
desc "namespaces", "Lists all available namespaces"
|
|
50
|
+
def namespaces
|
|
51
|
+
load_buildfile_with_error_handling
|
|
52
|
+
|
|
53
|
+
begin
|
|
54
|
+
Erebrus.list_namespaces
|
|
55
|
+
rescue StandardError => e
|
|
56
|
+
puts "Error listing namespaces: #{e.message}"
|
|
57
|
+
exit 1
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
desc "init [TYPE]", "Initializes the project with a sample Buildfile"
|
|
62
|
+
def init(type = "basic")
|
|
63
|
+
buildfile = options[:file]
|
|
64
|
+
|
|
65
|
+
if File.exist?(buildfile)
|
|
66
|
+
puts "Buildfile '#{buildfile}' already exists!"
|
|
67
|
+
return
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
sample_content = case type.downcase
|
|
71
|
+
when "cpp", "c++"
|
|
72
|
+
generate_cpp_buildfile
|
|
73
|
+
when "c"
|
|
74
|
+
generate_c_buildfile
|
|
75
|
+
when "advanced"
|
|
76
|
+
generate_advanced_buildfile
|
|
77
|
+
else
|
|
78
|
+
generate_basic_buildfile
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
File.write(buildfile, sample_content)
|
|
82
|
+
puts "Created #{buildfile} (#{type} template)"
|
|
83
|
+
puts "Edit the file to define your build targets and run 'erebrus build' to build your project"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
desc "validate", "Validates the Buildfile syntax"
|
|
87
|
+
def validate
|
|
88
|
+
buildfile = options[:file]
|
|
89
|
+
|
|
90
|
+
unless File.exist?(buildfile)
|
|
91
|
+
puts "Error: Buildfile '#{buildfile}' not found"
|
|
92
|
+
exit 1
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
begin
|
|
96
|
+
Erebrus.reset!
|
|
97
|
+
Erebrus.load_buildfile(buildfile)
|
|
98
|
+
puts "Buildfile is valid ✓"
|
|
99
|
+
rescue StandardError => e
|
|
100
|
+
puts "Buildfile validation failed: #{e.message}"
|
|
101
|
+
exit 1
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
desc "graph [TARGET]", "Shows dependency graph for target"
|
|
106
|
+
def graph(target = nil)
|
|
107
|
+
load_buildfile_with_error_handling
|
|
108
|
+
|
|
109
|
+
target_name = resolve_target_name(target)
|
|
110
|
+
|
|
111
|
+
begin
|
|
112
|
+
puts "Dependency graph for '#{target_name}':"
|
|
113
|
+
puts "(Graph visualization not yet implemented)"
|
|
114
|
+
rescue StandardError => e
|
|
115
|
+
puts "Error generating graph: #{e.message}"
|
|
116
|
+
exit 1
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
desc "clean", "Runs the clean target if available"
|
|
121
|
+
def clean
|
|
122
|
+
load_buildfile_with_error_handling
|
|
123
|
+
|
|
124
|
+
begin
|
|
125
|
+
Erebrus.build("clean")
|
|
126
|
+
rescue Erebrus::Error => e
|
|
127
|
+
if e.message.include?("not found")
|
|
128
|
+
puts "No clean target defined"
|
|
129
|
+
else
|
|
130
|
+
puts "Clean failed: #{e.message}"
|
|
131
|
+
exit 1
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
desc "version", "Show version"
|
|
137
|
+
def version
|
|
138
|
+
puts "Erebrus #{Erebrus::VERSION}"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def load_buildfile_with_error_handling
|
|
144
|
+
buildfile = options[:file]
|
|
145
|
+
|
|
146
|
+
unless File.exist?(buildfile)
|
|
147
|
+
puts "Error: Buildfile '#{buildfile}' not found"
|
|
148
|
+
puts "Run 'erebrus init' to create a sample Buildfile"
|
|
149
|
+
exit 1
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
begin
|
|
153
|
+
Erebrus.reset!
|
|
154
|
+
Erebrus.load_buildfile(buildfile)
|
|
155
|
+
rescue StandardError => e
|
|
156
|
+
puts "Error loading buildfile: #{e.message}"
|
|
157
|
+
puts e.backtrace if options[:verbose]
|
|
158
|
+
exit 1
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def resolve_target_name(target)
|
|
163
|
+
return target unless target && options[:namespace]
|
|
164
|
+
|
|
165
|
+
"#{options[:namespace]}:#{target}"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def prepare_context
|
|
169
|
+
context = {}
|
|
170
|
+
|
|
171
|
+
context.merge!(options[:var]) if options[:var]
|
|
172
|
+
|
|
173
|
+
context["PLATFORM"] = RUBY_PLATFORM
|
|
174
|
+
context["RUBY_VERSION"] = RUBY_VERSION
|
|
175
|
+
context["PWD"] = Dir.pwd
|
|
176
|
+
|
|
177
|
+
context
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def generate_basic_buildfile
|
|
181
|
+
<<~BUILDFILE
|
|
182
|
+
target :clean, description: "Clean build artifacts" do
|
|
183
|
+
remove "build"
|
|
184
|
+
remove "dist"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
target :setup, description: "Setup build environment" do
|
|
188
|
+
mkdir "build"
|
|
189
|
+
mkdir "dist"
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
target :build, description: "Build the project" do
|
|
193
|
+
depends_on :setup
|
|
194
|
+
#{" "}
|
|
195
|
+
action do
|
|
196
|
+
puts "Building project..."
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
target :test, description: "Run tests" do
|
|
201
|
+
depends_on :build
|
|
202
|
+
#{" "}
|
|
203
|
+
action do
|
|
204
|
+
puts "Running tests..."
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
default :build
|
|
209
|
+
BUILDFILE
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def generate_cpp_buildfile
|
|
213
|
+
<<~BUILDFILE
|
|
214
|
+
set_variable 'CXX', get_variable('CXX', 'g++')
|
|
215
|
+
set_variable 'CXXFLAGS', get_variable('CXXFLAGS', '-std=c++17 -Wall -Wextra -O2')
|
|
216
|
+
set_variable 'SRCDIR', 'src'
|
|
217
|
+
set_variable 'BUILDDIR', 'build'
|
|
218
|
+
set_variable 'BINDIR', 'bin'
|
|
219
|
+
|
|
220
|
+
target :clean, description: "Clean build artifacts" do
|
|
221
|
+
remove "\${BUILDDIR}"
|
|
222
|
+
remove "\${BINDIR}"
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
target :setup, description: "Setup build directories" do
|
|
226
|
+
mkdir "\${BUILDDIR}"
|
|
227
|
+
mkdir "\${BINDIR}"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
target :compile, description: "Compile source files" do
|
|
231
|
+
depends_on :setup
|
|
232
|
+
depends_on_files "\${SRCDIR}/*.cpp", "\${SRCDIR}/*.hpp"
|
|
233
|
+
produces "\${BUILDDIR}/*.o"
|
|
234
|
+
#{" "}
|
|
235
|
+
run "\${CXX} \${CXXFLAGS} -c \${SRCDIR}/*.cpp"
|
|
236
|
+
run "mv *.o \${BUILDDIR}/ 2>/dev/null || true"
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
target :link, description: "Link executable" do
|
|
240
|
+
depends_on :compile
|
|
241
|
+
produces "\${BINDIR}/myapp"
|
|
242
|
+
#{" "}
|
|
243
|
+
run "\${CXX} \${BUILDDIR}/*.o -o \${BINDIR}/myapp"
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
target :build, description: "Build the complete project" do
|
|
247
|
+
depends_on :link
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
target :test, description: "Run tests" do
|
|
251
|
+
depends_on :build
|
|
252
|
+
#{" "}
|
|
253
|
+
run "\${BINDIR}/myapp --test"
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
target :install, description: "Install the application" do
|
|
257
|
+
depends_on :build
|
|
258
|
+
#{" "}
|
|
259
|
+
platform :linux do
|
|
260
|
+
copy "\${BINDIR}/myapp", "/usr/local/bin/myapp"
|
|
261
|
+
end
|
|
262
|
+
#{" "}
|
|
263
|
+
platform :windows do
|
|
264
|
+
copy "\${BINDIR}/myapp.exe", "C:/Program Files/MyApp/myapp.exe"
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
default :build
|
|
269
|
+
BUILDFILE
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def generate_c_buildfile
|
|
273
|
+
<<~BUILDFILE
|
|
274
|
+
set_variable 'CC', get_variable('CC', 'gcc')
|
|
275
|
+
set_variable 'CFLAGS', get_variable('CFLAGS', '-std=c99 -Wall -Wextra -O2')
|
|
276
|
+
|
|
277
|
+
target :clean, description: "Clean build artifacts" do
|
|
278
|
+
remove "build"
|
|
279
|
+
remove "bin"
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
target :setup, description: "Setup build directories" do
|
|
283
|
+
mkdir "build"
|
|
284
|
+
mkdir "bin"
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
target :compile, description: "Compile source files" do
|
|
288
|
+
depends_on :setup
|
|
289
|
+
depends_on_files "src/*.c", "src/*.h"
|
|
290
|
+
#{" "}
|
|
291
|
+
run "\${CC} \${CFLAGS} -c src/*.c"
|
|
292
|
+
run "mv *.o build/ 2>/dev/null || true"
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
target :link, description: "Link executable" do
|
|
296
|
+
depends_on :compile
|
|
297
|
+
#{" "}
|
|
298
|
+
run "\${CC} build/*.o -o bin/myapp"
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
target :build, description: "Build the project" do
|
|
302
|
+
depends_on :link
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
default :build
|
|
306
|
+
BUILDFILE
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def generate_advanced_buildfile
|
|
310
|
+
<<~BUILDFILE
|
|
311
|
+
set_variable 'PROJECT_NAME', 'MyProject'
|
|
312
|
+
set_variable 'VERSION', '1.0.0'
|
|
313
|
+
set_variable 'BUILD_TYPE', get_variable('BUILD_TYPE', 'Release')
|
|
314
|
+
|
|
315
|
+
conditional platform?(:windows) do
|
|
316
|
+
set_variable 'EXE_EXT', '.exe'
|
|
317
|
+
set_variable 'LIB_EXT', '.dll'
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
conditional platform?(:linux) do
|
|
321
|
+
set_variable 'EXE_EXT', ''
|
|
322
|
+
set_variable 'LIB_EXT', '.so'
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
namespace :clean do
|
|
326
|
+
target :all, description: "Clean everything" do
|
|
327
|
+
remove "build"
|
|
328
|
+
remove "dist"
|
|
329
|
+
remove "temp"
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
target :build, description: "Clean build artifacts only" do
|
|
333
|
+
remove "build"
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
namespace :build do
|
|
338
|
+
target :debug, description: "Build debug version" do
|
|
339
|
+
set_variable 'BUILD_TYPE', 'Debug'
|
|
340
|
+
depends_on 'compile:debug'
|
|
341
|
+
depends_on 'link:debug'
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
target :release, description: "Build release version" do
|
|
345
|
+
set_variable 'BUILD_TYPE', 'Release'
|
|
346
|
+
depends_on 'compile:release'
|
|
347
|
+
depends_on 'link:release'
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
namespace :compile do
|
|
352
|
+
target :debug, description: "Compile debug version" do
|
|
353
|
+
tag :compile, :debug
|
|
354
|
+
only_if ->(ctx) { ctx['BUILD_TYPE'] == 'Debug' }
|
|
355
|
+
#{" "}
|
|
356
|
+
run "g++ -g -DDEBUG -c src/*.cpp"
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
target :release, description: "Compile release version" do
|
|
360
|
+
tag :compile, :release
|
|
361
|
+
only_if ->(ctx) { ctx['BUILD_TYPE'] == 'Release' }
|
|
362
|
+
#{" "}
|
|
363
|
+
run "g++ -O2 -DNDEBUG -c src/*.cpp"
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
namespace :link do
|
|
368
|
+
target :debug, description: "Link debug executable" do
|
|
369
|
+
depends_on 'compile:debug'
|
|
370
|
+
run "g++ *.o -o bin/\${PROJECT_NAME}_debug\${EXE_EXT}"
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
target :release, description: "Link release executable" do
|
|
374
|
+
depends_on 'compile:release'
|
|
375
|
+
run "g++ *.o -o bin/\${PROJECT_NAME}\${EXE_EXT}"
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
namespace :test do
|
|
380
|
+
target :unit, description: "Run unit tests" do
|
|
381
|
+
depends_on 'build:debug'
|
|
382
|
+
run "bin/\${PROJECT_NAME}_debug\${EXE_EXT} --unit-tests"
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
target :integration, description: "Run integration tests" do
|
|
386
|
+
depends_on 'build:release'
|
|
387
|
+
run "bin/\${PROJECT_NAME}\${EXE_EXT} --integration-tests"
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
target :all, description: "Run all tests" do
|
|
391
|
+
depends_on :unit
|
|
392
|
+
depends_on :integration
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
default 'build:release'
|
|
397
|
+
BUILDFILE
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
ErebrusCommand.start
|
data/examples/Buildfile
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Erebrus Buildfile Example
|
|
2
|
+
# This demonstrates the Ruby build DSL for C/C++ projects
|
|
3
|
+
|
|
4
|
+
target :clean, description: "Clean all build artifacts" do
|
|
5
|
+
remove "build"
|
|
6
|
+
remove "dist"
|
|
7
|
+
remove "obj"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
target :setup, description: "Setup build directories" do
|
|
11
|
+
mkdir "build"
|
|
12
|
+
mkdir "dist"
|
|
13
|
+
mkdir "obj"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
target :configure, description: "Configure build environment" do
|
|
17
|
+
depends_on :setup
|
|
18
|
+
|
|
19
|
+
action do |context|
|
|
20
|
+
puts "Configuring build for #{context[:platform] || 'default'} platform"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
target :compile_sources, description: "Compile source files" do
|
|
25
|
+
depends_on :configure
|
|
26
|
+
|
|
27
|
+
# Compile all C++ files in src/
|
|
28
|
+
run "g++ -std=c++17 -Wall -Wextra -O2 -c src/*.cpp -Iinclude"
|
|
29
|
+
|
|
30
|
+
# Move object files to obj directory
|
|
31
|
+
run "mv *.o obj/ 2>/dev/null || true"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
target :compile_tests, description: "Compile test files" do
|
|
35
|
+
depends_on :compile_sources
|
|
36
|
+
|
|
37
|
+
run "g++ -std=c++17 -Wall -Wextra -O2 -c tests/*.cpp -Iinclude -Isrc"
|
|
38
|
+
run "mv *.o obj/ 2>/dev/null || true"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
target :link_main, description: "Link main executable" do
|
|
42
|
+
depends_on :compile_sources
|
|
43
|
+
|
|
44
|
+
run "g++ obj/*.o -o dist/myapp"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
target :link_tests, description: "Link test executable" do
|
|
48
|
+
depends_on :compile_tests
|
|
49
|
+
|
|
50
|
+
run "g++ obj/*.o -o dist/test_runner"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
target :build, description: "Build the main application" do
|
|
54
|
+
depends_on :link_main
|
|
55
|
+
|
|
56
|
+
action do
|
|
57
|
+
puts "Build completed successfully!"
|
|
58
|
+
puts "Executable: dist/myapp"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
target :test, description: "Run all tests" do
|
|
63
|
+
depends_on :link_tests
|
|
64
|
+
|
|
65
|
+
run "dist/test_runner"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
target :install, description: "Install the application" do
|
|
69
|
+
depends_on :build
|
|
70
|
+
|
|
71
|
+
copy "dist/myapp", "/usr/local/bin/myapp"
|
|
72
|
+
|
|
73
|
+
action do
|
|
74
|
+
puts "Application installed to /usr/local/bin/myapp"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
target :package, description: "Create distribution package" do
|
|
79
|
+
depends_on :build
|
|
80
|
+
|
|
81
|
+
mkdir "package"
|
|
82
|
+
copy "dist/myapp", "package/"
|
|
83
|
+
copy "README.md", "package/"
|
|
84
|
+
copy "LICENSE", "package/"
|
|
85
|
+
|
|
86
|
+
run "tar -czf dist/myapp-1.0.tar.gz -C package ."
|
|
87
|
+
remove "package"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
target :all, description: "Build, test, and package" do
|
|
91
|
+
depends_on :test
|
|
92
|
+
depends_on :package
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Set the default target
|
|
96
|
+
default :build
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
require "pathname"
|
|
2
|
+
|
|
3
|
+
module Erebrus
|
|
4
|
+
class BuildEngine
|
|
5
|
+
attr_reader :targets, :namespaces, :variables, :included_files
|
|
6
|
+
attr_accessor :current_namespace
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@targets = {}
|
|
10
|
+
@namespaces = {}
|
|
11
|
+
@variables = {}
|
|
12
|
+
@default_target = nil
|
|
13
|
+
@current_namespace = nil
|
|
14
|
+
@included_files = Set.new
|
|
15
|
+
@file_watchers = {}
|
|
16
|
+
@conditional_stack = []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def target(name, description: nil, namespace: nil, &block)
|
|
20
|
+
full_name = build_target_name(name, namespace || @current_namespace)
|
|
21
|
+
target_obj = Target.new(full_name, description: description)
|
|
22
|
+
target_obj.namespace = namespace || @current_namespace
|
|
23
|
+
@targets[full_name] = target_obj
|
|
24
|
+
|
|
25
|
+
ns = namespace || @current_namespace
|
|
26
|
+
if ns
|
|
27
|
+
@namespaces[ns] ||= []
|
|
28
|
+
@namespaces[ns] << full_name
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if block_given?
|
|
32
|
+
target_context = TargetContext.new(target_obj, self)
|
|
33
|
+
target_context.instance_eval(&block)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
target_obj
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def namespace(name, &block)
|
|
40
|
+
old_namespace = @current_namespace
|
|
41
|
+
@current_namespace = name.to_s
|
|
42
|
+
|
|
43
|
+
begin
|
|
44
|
+
yield if block_given?
|
|
45
|
+
ensure
|
|
46
|
+
@current_namespace = old_namespace
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def include_buildfile(file_path, namespace: nil)
|
|
51
|
+
resolved_path = resolve_file_path(file_path)
|
|
52
|
+
|
|
53
|
+
if @included_files.include?(resolved_path)
|
|
54
|
+
puts "Warning: Buildfile '#{resolved_path}' already included, skipping"
|
|
55
|
+
return
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
raise Error, "Buildfile not found: #{resolved_path}" unless File.exist?(resolved_path)
|
|
59
|
+
|
|
60
|
+
@included_files.add(resolved_path)
|
|
61
|
+
|
|
62
|
+
old_namespace = @current_namespace
|
|
63
|
+
@current_namespace = namespace if namespace
|
|
64
|
+
|
|
65
|
+
begin
|
|
66
|
+
content = File.read(resolved_path)
|
|
67
|
+
instance_eval(content, resolved_path)
|
|
68
|
+
ensure
|
|
69
|
+
@current_namespace = old_namespace
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def set_variable(name, value)
|
|
74
|
+
@variables[name.to_s] = value
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def get_variable(name, default = nil)
|
|
78
|
+
@variables[name.to_s] || ENV[name.to_s] || default
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def conditional(condition, &block)
|
|
82
|
+
@conditional_stack.push(condition)
|
|
83
|
+
|
|
84
|
+
begin
|
|
85
|
+
yield if condition && block_given?
|
|
86
|
+
ensure
|
|
87
|
+
@conditional_stack.pop
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def platform?(name)
|
|
92
|
+
case name.to_s.downcase
|
|
93
|
+
when "windows", "win32"
|
|
94
|
+
Gem.win_platform?
|
|
95
|
+
when "linux"
|
|
96
|
+
RUBY_PLATFORM.include?("linux")
|
|
97
|
+
when "macos", "darwin"
|
|
98
|
+
RUBY_PLATFORM.include?("darwin")
|
|
99
|
+
else
|
|
100
|
+
RUBY_PLATFORM.include?(name.to_s)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def file_exists?(path)
|
|
105
|
+
File.exist?(resolve_file_path(path))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def directory_exists?(path)
|
|
109
|
+
Dir.exist?(resolve_file_path(path))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def default(target_name)
|
|
113
|
+
@default_target = resolve_target_name(target_name)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def build(target_name = nil, context = {})
|
|
117
|
+
target_name = resolve_target_name(target_name || @default_target)
|
|
118
|
+
|
|
119
|
+
raise Error, "No target specified and no default target set" unless target_name
|
|
120
|
+
|
|
121
|
+
target_obj = @targets[target_name]
|
|
122
|
+
unless target_obj
|
|
123
|
+
available_targets = @targets.keys.sort
|
|
124
|
+
raise Error, "Target '#{target_name}' not found. Available targets: #{available_targets.join(", ")}"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
merged_context = @variables.merge(context)
|
|
128
|
+
|
|
129
|
+
reset_all_targets!
|
|
130
|
+
execute_target(target_obj, merged_context)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def list_targets(namespace: nil)
|
|
134
|
+
targets_to_show = if namespace
|
|
135
|
+
@namespaces[namespace.to_s] || []
|
|
136
|
+
else
|
|
137
|
+
@targets.keys
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
if namespace
|
|
141
|
+
puts "Targets in namespace '#{namespace}':"
|
|
142
|
+
else
|
|
143
|
+
puts "Available targets:"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
targets_to_show.sort.each do |name|
|
|
147
|
+
target = @targets[name]
|
|
148
|
+
next unless target
|
|
149
|
+
|
|
150
|
+
puts " #{name}:"
|
|
151
|
+
puts " Description: #{target.description}" if target.description
|
|
152
|
+
puts " Namespace: #{target.namespace}" if target.namespace && !namespace
|
|
153
|
+
puts " Dependencies: #{target.dependencies.join(", ")}" unless target.dependencies.empty?
|
|
154
|
+
puts " Conditions: #{target.conditions.join(", ")}" unless target.conditions.empty?
|
|
155
|
+
puts
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def list_namespaces
|
|
160
|
+
puts "Available namespaces:"
|
|
161
|
+
@namespaces.each do |ns, targets|
|
|
162
|
+
puts " #{ns}: #{targets.size} targets"
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def watch_file(pattern, &block)
|
|
167
|
+
@file_watchers[pattern] = block
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def check_file_changes
|
|
171
|
+
@file_watchers.each do |pattern, callback|
|
|
172
|
+
Dir.glob(pattern).each do |file|
|
|
173
|
+
callback.call(file) if File.mtime(file) > (@last_check || Time.at(0))
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
@last_check = Time.now
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
private
|
|
180
|
+
|
|
181
|
+
def build_target_name(name, namespace = nil)
|
|
182
|
+
if namespace
|
|
183
|
+
"#{namespace}:#{name}"
|
|
184
|
+
else
|
|
185
|
+
name.to_s
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def resolve_target_name(name)
|
|
190
|
+
return nil unless name
|
|
191
|
+
|
|
192
|
+
name_str = name.to_s
|
|
193
|
+
|
|
194
|
+
return name_str if name_str.include?(":")
|
|
195
|
+
|
|
196
|
+
if @current_namespace
|
|
197
|
+
namespaced = "#{@current_namespace}:#{name_str}"
|
|
198
|
+
return namespaced if @targets.key?(namespaced)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
name_str
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def resolve_file_path(path)
|
|
205
|
+
pathname = Pathname.new(path)
|
|
206
|
+
return pathname.to_s if pathname.absolute?
|
|
207
|
+
|
|
208
|
+
Pathname.pwd.join(path).to_s
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def execute_target(target, context, visited = Set.new)
|
|
212
|
+
target_name = target.name
|
|
213
|
+
|
|
214
|
+
if visited.include?(target_name)
|
|
215
|
+
raise Error, "Circular dependency detected: #{visited.to_a.join(" -> ")} -> #{target_name}"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
return if target.executed
|
|
219
|
+
|
|
220
|
+
unless target.conditions.all? { |condition| condition.call(context) }
|
|
221
|
+
puts "Skipping target '#{target_name}' (conditions not met)"
|
|
222
|
+
target.executed = true
|
|
223
|
+
return
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
visited.add(target_name)
|
|
227
|
+
|
|
228
|
+
target.dependencies.each do |dep_name|
|
|
229
|
+
resolved_dep = resolve_target_name(dep_name)
|
|
230
|
+
dep_target = @targets[resolved_dep]
|
|
231
|
+
raise Error, "Dependency '#{dep_name}' not found for target '#{target_name}'" unless dep_target
|
|
232
|
+
|
|
233
|
+
execute_target(dep_target, context, visited.dup)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
target.execute(context)
|
|
237
|
+
|
|
238
|
+
visited.delete(target_name)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def reset_all_targets!
|
|
242
|
+
@targets.each_value(&:reset!)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
class TargetContext
|
|
247
|
+
def initialize(target, engine = nil)
|
|
248
|
+
@target = target
|
|
249
|
+
@engine = engine
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def depends_on(*deps)
|
|
253
|
+
@target.depends_on(*deps)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def depends_on_files(*files)
|
|
257
|
+
@target.depends_on_files(*files)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def produces(*files)
|
|
261
|
+
@target.produces(*files)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def condition(&block)
|
|
265
|
+
@target.condition(&block)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def only_if(condition)
|
|
269
|
+
@target.only_if(condition)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def tag(*tags)
|
|
273
|
+
@target.tag(*tags)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def action(&block)
|
|
277
|
+
@target.action(&block)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def run(command, options = {})
|
|
281
|
+
@target.action do |context|
|
|
282
|
+
expanded_command = expand_variables(command, context)
|
|
283
|
+
puts "Running: #{expanded_command}"
|
|
284
|
+
|
|
285
|
+
if options[:capture]
|
|
286
|
+
result = `#{expanded_command}`
|
|
287
|
+
raise Error, "Command failed: #{expanded_command}" unless $?.success?
|
|
288
|
+
|
|
289
|
+
result.chomp
|
|
290
|
+
else
|
|
291
|
+
system(expanded_command) || raise(Error, "Command failed: #{expanded_command}")
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def copy(source, destination, options = {})
|
|
297
|
+
@target.action do |context|
|
|
298
|
+
expanded_source = expand_variables(source, context)
|
|
299
|
+
expanded_dest = expand_variables(destination, context)
|
|
300
|
+
|
|
301
|
+
puts "Copying #{expanded_source} to #{expanded_dest}"
|
|
302
|
+
require "fileutils"
|
|
303
|
+
|
|
304
|
+
if options[:preserve]
|
|
305
|
+
FileUtils.cp_r(expanded_source, expanded_dest, preserve: true)
|
|
306
|
+
else
|
|
307
|
+
FileUtils.cp_r(expanded_source, expanded_dest)
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def mkdir(path)
|
|
313
|
+
@target.action do |context|
|
|
314
|
+
expanded_path = expand_variables(path, context)
|
|
315
|
+
puts "Creating directory: #{expanded_path}"
|
|
316
|
+
require "fileutils"
|
|
317
|
+
FileUtils.mkdir_p(expanded_path)
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def remove(path)
|
|
322
|
+
@target.action do |context|
|
|
323
|
+
expanded_path = expand_variables(path, context)
|
|
324
|
+
puts "Removing: #{expanded_path}"
|
|
325
|
+
require "fileutils"
|
|
326
|
+
FileUtils.rm_rf(expanded_path)
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def touch(path)
|
|
331
|
+
@target.action do |context|
|
|
332
|
+
expanded_path = expand_variables(path, context)
|
|
333
|
+
puts "Touching: #{expanded_path}"
|
|
334
|
+
require "fileutils"
|
|
335
|
+
FileUtils.touch(expanded_path)
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def chmod(mode, path)
|
|
340
|
+
@target.action do |context|
|
|
341
|
+
expanded_path = expand_variables(path, context)
|
|
342
|
+
puts "Changing permissions of #{expanded_path} to #{mode}"
|
|
343
|
+
require "fileutils"
|
|
344
|
+
FileUtils.chmod(mode, expanded_path)
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def download(url, destination)
|
|
349
|
+
@target.action do |context|
|
|
350
|
+
expanded_url = expand_variables(url, context)
|
|
351
|
+
expanded_dest = expand_variables(destination, context)
|
|
352
|
+
|
|
353
|
+
puts "Downloading #{expanded_url} to #{expanded_dest}"
|
|
354
|
+
require "net/http"
|
|
355
|
+
require "uri"
|
|
356
|
+
|
|
357
|
+
uri = URI(expanded_url)
|
|
358
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
359
|
+
request = Net::HTTP::Get.new(uri)
|
|
360
|
+
response = http.request(request)
|
|
361
|
+
|
|
362
|
+
raise Error, "Download failed: #{response.code} #{response.message}" unless response.code == "200"
|
|
363
|
+
|
|
364
|
+
File.write(expanded_dest, response.body)
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def template(template_file, output_file, variables = {})
|
|
370
|
+
@target.action do |context|
|
|
371
|
+
expanded_template = expand_variables(template_file, context)
|
|
372
|
+
expanded_output = expand_variables(output_file, context)
|
|
373
|
+
|
|
374
|
+
puts "Processing template #{expanded_template} -> #{expanded_output}"
|
|
375
|
+
|
|
376
|
+
template_content = File.read(expanded_template)
|
|
377
|
+
merged_vars = context.merge(variables)
|
|
378
|
+
|
|
379
|
+
result = template_content.gsub(/\{\{(\w+)\}\}/) do |match|
|
|
380
|
+
var_name = ::Regexp.last_match(1)
|
|
381
|
+
merged_vars[var_name] || merged_vars[var_name.to_sym] || match
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
File.write(expanded_output, result)
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def parallel(*targets)
|
|
389
|
+
@target.action do
|
|
390
|
+
threads = targets.map do |target_name|
|
|
391
|
+
Thread.new do
|
|
392
|
+
@engine&.build(target_name) if @engine
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
threads.each(&:join)
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def conditional(condition, &block)
|
|
400
|
+
@target.action do |context|
|
|
401
|
+
result = case condition
|
|
402
|
+
when Proc
|
|
403
|
+
condition.call(context)
|
|
404
|
+
when Symbol
|
|
405
|
+
context[condition]
|
|
406
|
+
when String
|
|
407
|
+
context[condition] || ENV[condition]
|
|
408
|
+
else
|
|
409
|
+
!!condition
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
block.call(context) if result && block_given?
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def platform(name, &block)
|
|
417
|
+
conditional(->(ctx) { @engine&.platform?(name) }, &block)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def file_exists(path, &block)
|
|
421
|
+
conditional(->(ctx) { File.exist?(expand_variables(path, ctx)) }, &block)
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def directory_exists(path, &block)
|
|
425
|
+
conditional(->(ctx) { Dir.exist?(expand_variables(path, ctx)) }, &block)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def set_variable(name, value)
|
|
429
|
+
@target.action do |context|
|
|
430
|
+
@engine&.set_variable(name, expand_variables(value, context))
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def get_variable(name, default = nil)
|
|
435
|
+
@engine&.get_variable(name, default)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
private
|
|
439
|
+
|
|
440
|
+
def expand_variables(text, context)
|
|
441
|
+
return text unless text.is_a?(String)
|
|
442
|
+
|
|
443
|
+
text.gsub(/\$\{(\w+)\}|\$(\w+)/) do |match|
|
|
444
|
+
var_name = ::Regexp.last_match(1) || ::Regexp.last_match(2)
|
|
445
|
+
context[var_name] || context[var_name.to_sym] || ENV[var_name] || match
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
end
|
data/lib/erebrus/dsl.rb
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
module Erebrus
|
|
2
|
+
module DSL
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.extend(ClassMethods)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module ClassMethods
|
|
8
|
+
def build_engine
|
|
9
|
+
@build_engine ||= BuildEngine.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def target(name, description: nil, namespace: nil, &block)
|
|
13
|
+
build_engine.target(name, description: description, namespace: namespace, &block)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def namespace(name, &block)
|
|
17
|
+
build_engine.namespace(name, &block)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def include_buildfile(file_path, namespace: nil)
|
|
21
|
+
build_engine.include_buildfile(file_path, namespace: namespace)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def set_variable(name, value)
|
|
25
|
+
build_engine.set_variable(name, value)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def get_variable(name, default = nil)
|
|
29
|
+
build_engine.get_variable(name, default)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def conditional(condition, &block)
|
|
33
|
+
build_engine.conditional(condition, &block)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def platform?(name)
|
|
37
|
+
build_engine.platform?(name)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def default(target_name)
|
|
41
|
+
build_engine.default(target_name)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def build(target_name = nil, context = {})
|
|
45
|
+
build_engine.build(target_name, context)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def list_targets(namespace: nil)
|
|
49
|
+
build_engine.list_targets(namespace: namespace)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def list_namespaces
|
|
53
|
+
build_engine.list_namespaces
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def target(name, description: nil, namespace: nil, &block)
|
|
58
|
+
self.class.build_engine.target(name, description: description, namespace: namespace, &block)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def namespace(name, &block)
|
|
62
|
+
self.class.build_engine.namespace(name, &block)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def include_buildfile(file_path, namespace: nil)
|
|
66
|
+
self.class.build_engine.include_buildfile(file_path, namespace: namespace)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def set_variable(name, value)
|
|
70
|
+
self.class.build_engine.set_variable(name, value)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def get_variable(name, default = nil)
|
|
74
|
+
self.class.build_engine.get_variable(name, default)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def conditional(condition, &block)
|
|
78
|
+
self.class.build_engine.conditional(condition, &block)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def platform?(name)
|
|
82
|
+
self.class.build_engine.platform?(name)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def default(target_name)
|
|
86
|
+
self.class.build_engine.default(target_name)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def build(target_name = nil, context = {})
|
|
90
|
+
self.class.build_engine.build(target_name, context)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def list_targets(namespace: nil)
|
|
94
|
+
self.class.build_engine.list_targets(namespace: namespace)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def list_namespaces
|
|
98
|
+
self.class.build_engine.list_namespaces
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def self.target(name, description: nil, namespace: nil, &block)
|
|
103
|
+
@global_engine ||= BuildEngine.new
|
|
104
|
+
@global_engine.target(name, description: description, namespace: namespace, &block)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def self.namespace(name, &block)
|
|
108
|
+
@global_engine ||= BuildEngine.new
|
|
109
|
+
@global_engine.namespace(name, &block)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def self.include_buildfile(file_path, namespace: nil)
|
|
113
|
+
@global_engine ||= BuildEngine.new
|
|
114
|
+
@global_engine.include_buildfile(file_path, namespace: namespace)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def self.set_variable(name, value)
|
|
118
|
+
@global_engine ||= BuildEngine.new
|
|
119
|
+
@global_engine.set_variable(name, value)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def self.get_variable(name, default = nil)
|
|
123
|
+
@global_engine ||= BuildEngine.new
|
|
124
|
+
@global_engine.get_variable(name, default)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def self.conditional(condition, &block)
|
|
128
|
+
@global_engine ||= BuildEngine.new
|
|
129
|
+
@global_engine.conditional(condition, &block)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def self.platform?(name)
|
|
133
|
+
@global_engine ||= BuildEngine.new
|
|
134
|
+
@global_engine.platform?(name)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def self.default(target_name)
|
|
138
|
+
@global_engine ||= BuildEngine.new
|
|
139
|
+
@global_engine.default(target_name)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def self.build(target_name = nil, context = {})
|
|
143
|
+
@global_engine ||= BuildEngine.new
|
|
144
|
+
@global_engine.build(target_name, context)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def self.list_targets(namespace: nil)
|
|
148
|
+
@global_engine ||= BuildEngine.new
|
|
149
|
+
@global_engine.list_targets(namespace: namespace)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def self.list_namespaces
|
|
153
|
+
@global_engine ||= BuildEngine.new
|
|
154
|
+
@global_engine.list_namespaces
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def self.load_buildfile(file_path)
|
|
158
|
+
@global_engine ||= BuildEngine.new
|
|
159
|
+
@global_engine.include_buildfile(file_path)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def self.reset!
|
|
163
|
+
@global_engine = nil
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
module Erebrus
|
|
2
|
+
class Target
|
|
3
|
+
attr_reader :name, :dependencies, :description, :conditions, :file_dependencies, :outputs
|
|
4
|
+
attr_accessor :executed, :namespace, :tags, :priority
|
|
5
|
+
|
|
6
|
+
def initialize(name, description: nil)
|
|
7
|
+
@name = name.to_s
|
|
8
|
+
@description = description
|
|
9
|
+
@dependencies = []
|
|
10
|
+
@actions = []
|
|
11
|
+
@conditions = []
|
|
12
|
+
@file_dependencies = []
|
|
13
|
+
@outputs = []
|
|
14
|
+
@executed = false
|
|
15
|
+
@namespace = nil
|
|
16
|
+
@tags = []
|
|
17
|
+
@priority = 0
|
|
18
|
+
@last_run = nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def depends_on(*deps)
|
|
22
|
+
@dependencies.concat(deps.map(&:to_s))
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def depends_on_files(*files)
|
|
27
|
+
@file_dependencies.concat(files.map(&:to_s))
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def produces(*files)
|
|
32
|
+
@outputs.concat(files.map(&:to_s))
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def condition(&block)
|
|
37
|
+
@conditions << block if block_given?
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def only_if(condition)
|
|
42
|
+
@conditions << case condition
|
|
43
|
+
when Proc
|
|
44
|
+
condition
|
|
45
|
+
when Symbol
|
|
46
|
+
->(ctx) { ctx[condition] }
|
|
47
|
+
when String
|
|
48
|
+
->(ctx) { ctx[condition] || ENV[condition] }
|
|
49
|
+
else
|
|
50
|
+
->(_) { !!condition }
|
|
51
|
+
end
|
|
52
|
+
self
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def tag(*tags)
|
|
56
|
+
@tags.concat(tags.map(&:to_s))
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def action(&block)
|
|
61
|
+
@actions << block if block_given?
|
|
62
|
+
self
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def needs_execution?(context = {})
|
|
66
|
+
return true if @executed == false
|
|
67
|
+
|
|
68
|
+
return true if file_dependencies_changed?
|
|
69
|
+
|
|
70
|
+
@conditions.any? { |condition| !condition.call(context) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def execute(context = {})
|
|
74
|
+
return if @executed
|
|
75
|
+
|
|
76
|
+
start_time = Time.now
|
|
77
|
+
|
|
78
|
+
puts "Executing target: #{@name}"
|
|
79
|
+
puts " #{@description}" if @description
|
|
80
|
+
puts " Namespace: #{@namespace}" if @namespace
|
|
81
|
+
puts " Tags: #{@tags.join(", ")}" unless @tags.empty?
|
|
82
|
+
|
|
83
|
+
begin
|
|
84
|
+
@actions.each_with_index do |action, index|
|
|
85
|
+
puts " Action #{index + 1}/#{@actions.size}" if @actions.size > 1
|
|
86
|
+
|
|
87
|
+
if action.arity == 0
|
|
88
|
+
action.call
|
|
89
|
+
else
|
|
90
|
+
action.call(context)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
@executed = true
|
|
95
|
+
@last_run = Time.now
|
|
96
|
+
|
|
97
|
+
execution_time = Time.now - start_time
|
|
98
|
+
puts " Completed in #{execution_time.round(2)}s"
|
|
99
|
+
rescue StandardError => e
|
|
100
|
+
puts " Failed: #{e.message}"
|
|
101
|
+
raise Error, "Target '#{@name}' failed: #{e.message}"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def reset!
|
|
106
|
+
@executed = false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def file_dependencies_changed?
|
|
110
|
+
return false if @file_dependencies.empty? || @outputs.empty?
|
|
111
|
+
|
|
112
|
+
output_times = @outputs.map do |file|
|
|
113
|
+
File.exist?(file) ? File.mtime(file) : Time.at(0)
|
|
114
|
+
end
|
|
115
|
+
oldest_output = output_times.min
|
|
116
|
+
|
|
117
|
+
@file_dependencies.any? do |file|
|
|
118
|
+
File.exist?(file) && File.mtime(file) > oldest_output
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def to_s
|
|
123
|
+
@name
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def inspect
|
|
127
|
+
"#<Erebrus::Target:#{@name} ns=#{@namespace} deps=#{@dependencies} executed=#{@executed}>"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
data/lib/erebrus.rb
ADDED
data/sig/erebrus.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: erebrus
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- jel9
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: thor
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.4'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.4'
|
|
26
|
+
email:
|
|
27
|
+
- chloedev@proton.me
|
|
28
|
+
executables:
|
|
29
|
+
- erebrus
|
|
30
|
+
extensions: []
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
files:
|
|
33
|
+
- LICENSE
|
|
34
|
+
- README.md
|
|
35
|
+
- Rakefile
|
|
36
|
+
- bin/erebrus
|
|
37
|
+
- examples/Buildfile
|
|
38
|
+
- lib/erebrus.rb
|
|
39
|
+
- lib/erebrus/build_engine.rb
|
|
40
|
+
- lib/erebrus/dsl.rb
|
|
41
|
+
- lib/erebrus/target.rb
|
|
42
|
+
- lib/erebrus/version.rb
|
|
43
|
+
- sig/erebrus.rbs
|
|
44
|
+
homepage: https://github.com/daedalus-os/erebrus
|
|
45
|
+
licenses: []
|
|
46
|
+
metadata:
|
|
47
|
+
homepage_uri: https://github.com/daedalus-os/erebrus
|
|
48
|
+
source_code_uri: https://github.com/daedalus-os/erebrus
|
|
49
|
+
rdoc_options: []
|
|
50
|
+
require_paths:
|
|
51
|
+
- lib
|
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: 3.2.0
|
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
requirements: []
|
|
63
|
+
rubygems_version: 3.6.9
|
|
64
|
+
specification_version: 4
|
|
65
|
+
summary: Modern C/C++ build system like msbuild, with ruby instead of xml
|
|
66
|
+
test_files: []
|