erebrus 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76b069200add69a2d85d26e12f70dd726611b1873e77f9ce0f39b66b5c836efb
4
- data.tar.gz: 407eb8f255a7bdc2cc495a66d5dc712e93f467ca8a01f20ff95e5762a89f1add
3
+ metadata.gz: 519cdef5b67694f284b00f6114a6f9ac439fe608f31452ac157c48b8859c184a
4
+ data.tar.gz: fdbe5002c3136d3835f87f203033ce1d5e06c3ccec0c451ff12fb2ab0864e954
5
5
  SHA512:
6
- metadata.gz: e41e9a1e3878050ba948b036576a7630f34d94cbaa9c5e897ce07eadbac10e18fe79ce5aa0d882ff5336178fd7a504e2ec1728552a2dd3a85d7f4f3304831360
7
- data.tar.gz: 4a35080e1c2bd7025cc66510457ceba4d9372254cf42916f4908cba345f8ccbdc97cc92a8d833819d577010a2ffdd3c58b515896531dce39b3c49b5c2443152d
6
+ metadata.gz: caaeec63caf2ae10b477a5b83b59399b9bdc8f45a4614a3901703bef84f58ba8d00cafae1f7e90e6e1152e91a4563c3d4182272d9459dd7b719075f5b779e0dd
7
+ data.tar.gz: 76d06dfbaf4bda854cda916944e8ba0a448276dee4b9a837e3402a508c6619686c1ae639f3223e926d77ad6eb035df6a380c68b967805ad13f908208b7af69a5
data/bin/erebrus CHANGED
@@ -5,147 +5,186 @@ require "erebrus"
5
5
  class ErebrusCommand < Thor
6
6
  class_option :verbose, aliases: "-v", type: :boolean, desc: "Verbose output"
7
7
  class_option :file, aliases: "-f", desc: "Buildfile to use", default: "Buildfile"
8
+ class_option :color, type: :boolean, default: true, desc: "Enable colored output (--no-color to disable)"
9
+ class_option :dir, aliases: "-d", type: :string, desc: "Directory to run the build in"
8
10
 
9
11
  desc "build [TARGET]", "Builds the project with optional target"
10
12
  option :namespace, aliases: "-n", desc: "Target namespace"
11
13
  option :var, aliases: "-D", type: :hash, desc: "Set variables (e.g., -D CC=gcc -D CFLAGS=-O2)"
12
14
  option :parallel, aliases: "-j", type: :numeric, desc: "Number of parallel jobs"
13
15
  def build(target = nil)
14
- load_buildfile_with_error_handling
16
+ in_working_dir do
17
+ load_buildfile_with_error_handling
15
18
 
16
- target_name = resolve_target_name(target)
19
+ target_name = resolve_target_name(target)
17
20
 
18
- context = prepare_context
21
+ context = prepare_context
19
22
 
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
23
+ begin
24
+ Erebrus.build(target_name, context)
25
+ rescue Erebrus::Error => e
26
+ say_error "Build failed: #{e.message}"
27
+ exit 1
28
+ rescue StandardError => e
29
+ say_error "Unexpected error: #{e.message}"
30
+ puts e.backtrace if options[:verbose]
31
+ exit 1
32
+ end
30
33
  end
31
34
  end
32
35
 
33
36
  desc "list [NAMESPACE]", "Lists all available targets, optionally filtered by namespace"
34
37
  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
38
+ in_working_dir do
39
+ load_buildfile_with_error_handling
40
+
41
+ begin
42
+ puts "" # spacing for readability
43
+ if namespace
44
+ say_title("Targets in namespace '#{namespace}'")
45
+ else
46
+ say_title("Available targets")
47
+ end
48
+ if namespace
49
+ Erebrus.list_targets(namespace: namespace)
50
+ else
51
+ Erebrus.list_targets
52
+ end
53
+ rescue StandardError => e
54
+ say_error "Error listing targets: #{e.message}"
55
+ exit 1
42
56
  end
43
- rescue StandardError => e
44
- puts "Error listing targets: #{e.message}"
45
- exit 1
46
57
  end
47
58
  end
48
59
 
49
60
  desc "namespaces", "Lists all available namespaces"
50
61
  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
62
+ in_working_dir do
63
+ load_buildfile_with_error_handling
64
+
65
+ begin
66
+ say_title "Available namespaces"
67
+ Erebrus.list_namespaces
68
+ rescue StandardError => e
69
+ say_error "Error listing namespaces: #{e.message}"
70
+ exit 1
71
+ end
58
72
  end
59
73
  end
60
74
 
61
75
  desc "init [TYPE]", "Initializes the project with a sample Buildfile"
62
76
  def init(type = "basic")
63
- buildfile = options[:file]
77
+ in_working_dir do
78
+ buildfile = options[:file]
64
79
 
65
- if File.exist?(buildfile)
66
- puts "Buildfile '#{buildfile}' already exists!"
67
- return
68
- end
80
+ if File.exist?(buildfile)
81
+ puts "Buildfile '#{buildfile}' already exists!"
82
+ return
83
+ end
69
84
 
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"
85
+ sample_content = case type.downcase
86
+ when "cpp", "c++"
87
+ generate_cpp_buildfile
88
+ when "c"
89
+ generate_c_buildfile
90
+ when "advanced"
91
+ generate_advanced_buildfile
92
+ else
93
+ generate_basic_buildfile
94
+ end
95
+
96
+ File.write(buildfile, sample_content)
97
+ say_success "Created #{buildfile} (#{type} template)"
98
+ say_info "Edit the file to define your build targets and run 'erebrus build' to build your project"
99
+ end
84
100
  end
85
101
 
86
102
  desc "validate", "Validates the Buildfile syntax"
87
103
  def validate
88
- buildfile = options[:file]
104
+ in_working_dir do
105
+ buildfile = options[:file]
89
106
 
90
- unless File.exist?(buildfile)
91
- puts "Error: Buildfile '#{buildfile}' not found"
92
- exit 1
93
- end
107
+ unless File.exist?(buildfile)
108
+ puts "Error: Buildfile '#{buildfile}' not found"
109
+ exit 1
110
+ end
94
111
 
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
112
+ begin
113
+ Erebrus.reset!
114
+ Erebrus.load_buildfile(buildfile)
115
+ say_success "Buildfile is valid \u2713"
116
+ rescue StandardError => e
117
+ say_error "Buildfile validation failed: #{e.message}"
118
+ exit 1
119
+ end
102
120
  end
103
121
  end
104
122
 
105
123
  desc "graph [TARGET]", "Shows dependency graph for target"
106
124
  def graph(target = nil)
107
- load_buildfile_with_error_handling
125
+ in_working_dir do
126
+ load_buildfile_with_error_handling
108
127
 
109
- target_name = resolve_target_name(target)
128
+ target_name = resolve_target_name(target)
110
129
 
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
130
+ begin
131
+ say_title "Dependency graph for '#{target_name}'"
132
+ say_info "(Graph visualization not yet implemented)"
133
+ rescue StandardError => e
134
+ say_error "Error generating graph: #{e.message}"
135
+ exit 1
136
+ end
117
137
  end
118
138
  end
119
139
 
120
140
  desc "clean", "Runs the clean target if available"
121
141
  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
142
+ in_working_dir do
143
+ load_buildfile_with_error_handling
144
+
145
+ begin
146
+ Erebrus.build("clean")
147
+ rescue Erebrus::Error => e
148
+ if e.message.include?("not found")
149
+ say_warn "No clean target defined"
150
+ else
151
+ say_error "Clean failed: #{e.message}"
152
+ exit 1
153
+ end
132
154
  end
133
155
  end
134
156
  end
135
157
 
136
158
  desc "version", "Show version"
137
159
  def version
138
- puts "Erebrus #{Erebrus::VERSION}"
160
+ say_info "Erebrus #{Erebrus::VERSION}"
139
161
  end
140
162
 
141
163
  private
142
164
 
165
+ def in_working_dir
166
+ dir = options[:dir]
167
+ return yield unless dir
168
+
169
+ unless Dir.exist?(dir)
170
+ say_error "Error: directory '#{dir}' not found"
171
+ exit 1
172
+ end
173
+ old = Dir.pwd
174
+ Dir.chdir(dir)
175
+ begin
176
+ yield
177
+ ensure
178
+ Dir.chdir(old)
179
+ end
180
+ end
181
+
143
182
  def load_buildfile_with_error_handling
144
183
  buildfile = options[:file]
145
184
 
146
185
  unless File.exist?(buildfile)
147
- puts "Error: Buildfile '#{buildfile}' not found"
148
- puts "Run 'erebrus init' to create a sample Buildfile"
186
+ say_error "Error: Buildfile '#{buildfile}' not found"
187
+ say_info "Run 'erebrus init' to create a sample Buildfile"
149
188
  exit 1
150
189
  end
151
190
 
@@ -153,7 +192,7 @@ class ErebrusCommand < Thor
153
192
  Erebrus.reset!
154
193
  Erebrus.load_buildfile(buildfile)
155
194
  rescue StandardError => e
156
- puts "Error loading buildfile: #{e.message}"
195
+ say_error "Error loading buildfile: #{e.message}"
157
196
  puts e.backtrace if options[:verbose]
158
197
  exit 1
159
198
  end
@@ -177,6 +216,57 @@ class ErebrusCommand < Thor
177
216
  context
178
217
  end
179
218
 
219
+ # --- Styling helpers -----------------------------------------------------
220
+ def color_enabled?
221
+ options[:color] && $stdout.respond_to?(:isatty) && $stdout.isatty && ENV["NO_COLOR"].nil?
222
+ rescue StandardError
223
+ options[:color]
224
+ end
225
+
226
+ def color_code(color)
227
+ case color
228
+ when :black then 30
229
+ when :red then 31
230
+ when :green then 32
231
+ when :yellow then 33
232
+ when :blue then 34
233
+ when :magenta then 35
234
+ when :cyan then 36
235
+ when :white then 37
236
+ else nil
237
+ end
238
+ end
239
+
240
+ def c(text, color = nil, bold: false)
241
+ return text unless color_enabled? && (bold || color)
242
+
243
+ codes = []
244
+ codes << 1 if bold
245
+ cc = color_code(color)
246
+ codes << cc if cc
247
+ "\e[#{codes.join(";")}m#{text}\e[0m"
248
+ end
249
+
250
+ def say_title(msg)
251
+ puts c("==> #{msg}", :blue, bold: true)
252
+ end
253
+
254
+ def say_success(msg)
255
+ puts c(msg, :green, bold: true)
256
+ end
257
+
258
+ def say_error(msg)
259
+ puts c(msg, :red, bold: true)
260
+ end
261
+
262
+ def say_warn(msg)
263
+ puts c(msg, :yellow)
264
+ end
265
+
266
+ def say_info(msg)
267
+ puts c(msg, :cyan)
268
+ end
269
+
180
270
  def generate_basic_buildfile
181
271
  <<~BUILDFILE
182
272
  target :clean, description: "Clean build artifacts" do
@@ -0,0 +1,5 @@
1
+ dist/
2
+ obj/
3
+ package/
4
+ build/
5
+ /package
data/examples/Buildfile CHANGED
@@ -1,6 +1,10 @@
1
1
  # Erebrus Buildfile Example
2
2
  # This demonstrates the Ruby build DSL for C/C++ projects
3
3
 
4
+ # Global variables used in the example and templates
5
+ set_variable "APP_NAME", "myapp"
6
+ set_variable "VERSION", "1.0.0"
7
+
4
8
  target :clean, description: "Clean all build artifacts" do
5
9
  remove "build"
6
10
  remove "dist"
@@ -15,44 +19,53 @@ end
15
19
 
16
20
  target :configure, description: "Configure build environment" do
17
21
  depends_on :setup
18
-
22
+
19
23
  action do |context|
20
- puts "Configuring build for #{context[:platform] || 'default'} platform"
24
+ puts "Configuring build for #{context[:platform] || "default"} platform"
21
25
  end
22
26
  end
23
27
 
24
28
  target :compile_sources, description: "Compile source files" do
25
29
  depends_on :configure
26
-
27
- # Compile all C++ files in src/
30
+
28
31
  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
+
33
+ action do
34
+ require "fileutils"
35
+ Dir.glob("*.o").each do |obj|
36
+ FileUtils.mv(obj, File.join("obj", File.basename(obj)))
37
+ end
38
+ end
32
39
  end
33
40
 
34
41
  target :compile_tests, description: "Compile test files" do
35
42
  depends_on :compile_sources
36
-
43
+
37
44
  run "g++ -std=c++17 -Wall -Wextra -O2 -c tests/*.cpp -Iinclude -Isrc"
38
- run "mv *.o obj/ 2>/dev/null || true"
45
+
46
+ action do
47
+ require "fileutils"
48
+ Dir.glob("*.o").each do |obj|
49
+ FileUtils.mv(obj, File.join("obj", File.basename(obj)))
50
+ end
51
+ end
39
52
  end
40
53
 
41
54
  target :link_main, description: "Link main executable" do
42
55
  depends_on :compile_sources
43
-
44
- run "g++ obj/*.o -o dist/myapp"
56
+
57
+ run "g++ obj/main.o obj/hello.o -o dist/myapp"
45
58
  end
46
59
 
47
60
  target :link_tests, description: "Link test executable" do
48
61
  depends_on :compile_tests
49
-
50
- run "g++ obj/*.o -o dist/test_runner"
62
+
63
+ run "g++ obj/test_main.o obj/hello.o -o dist/test_runner"
51
64
  end
52
65
 
53
66
  target :build, description: "Build the main application" do
54
67
  depends_on :link_main
55
-
68
+
56
69
  action do
57
70
  puts "Build completed successfully!"
58
71
  puts "Executable: dist/myapp"
@@ -61,29 +74,65 @@ end
61
74
 
62
75
  target :test, description: "Run all tests" do
63
76
  depends_on :link_tests
64
-
77
+
65
78
  run "dist/test_runner"
66
79
  end
67
80
 
68
81
  target :install, description: "Install the application" do
69
82
  depends_on :build
70
-
71
- copy "dist/myapp", "/usr/local/bin/myapp"
72
-
83
+
84
+ platform :linux do
85
+ directory_exists "/usr/local/bin" do
86
+ copy "dist/myapp", "/usr/local/bin/myapp"
87
+ end
88
+ end
89
+ platform :windows do
90
+ directory_exists "C:/Users/Public" do
91
+ copy "dist/myapp", "C:/Users/Public/myapp.exe"
92
+ end
93
+ end
94
+ platform :darwin do
95
+ directory_exists "/usr/local/bin" do
96
+ copy "dist/myapp", "/usr/local/bin/myapp"
97
+ end
98
+ end
99
+
73
100
  action do
74
- puts "Application installed to /usr/local/bin/myapp"
101
+ puts "Application installed for ${HOST_OS}"
75
102
  end
76
103
  end
77
104
 
78
105
  target :package, description: "Create distribution package" do
79
106
  depends_on :build
80
-
107
+
108
+ # Ensure stale file or directory doesn't block packaging
109
+ remove "package"
81
110
  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 ."
111
+ # Copy executable (handle Windows .exe)
112
+ file_exists "dist/myapp" do
113
+ copy "dist/myapp", "package"
114
+ end
115
+ file_exists "dist/myapp.exe" do
116
+ copy "dist/myapp.exe", "package"
117
+ end
118
+ # Include docs
119
+ file_exists "README.md" do
120
+ copy "README.md", "package"
121
+ end
122
+ file_exists "LICENSE" do
123
+ copy "LICENSE", "package"
124
+ end
125
+
126
+ platform :linux do
127
+ run "tar -czf dist/myapp-1.0.tar.gz -C package ."
128
+ end
129
+ platform :darwin do
130
+ run "tar -czf dist/myapp-1.0.tar.gz -C package ."
131
+ end
132
+ platform :windows do
133
+ echo "Creating zip archive on Windows"
134
+ run "powershell -NoProfile -Command \"Compress-Archive -Path package/* -DestinationPath dist/myapp-1.0.zip -Force\""
135
+ end
87
136
  remove "package"
88
137
  end
89
138
 
@@ -92,5 +141,56 @@ target :all, description: "Build, test, and package" do
92
141
  depends_on :package
93
142
  end
94
143
 
144
+ target :show_info, description: "Show host and environment info" do
145
+ echo "Host OS: ${HOST_OS}"
146
+ echo "Ruby: ${RUBY_VERSION}"
147
+ echo "PWD: ${PWD}"
148
+ end
149
+
150
+ target :gen_config, description: "Generate config from template" do
151
+ depends_on :setup
152
+ template "templates/app_config.tpl", "dist/config.txt"
153
+ end
154
+
155
+ # Demonstrate write/append utilities
156
+ target :write_demo, description: "Write and append notes" do
157
+ depends_on :setup
158
+ write "dist/notes.txt", "Notes for ${HOST_OS}\n"
159
+ append "dist/notes.txt", "Built at: #{Time.now}\n"
160
+ end
161
+
162
+ # Demonstrate environment handling
163
+ target :env_demo, description: "Run with temporary environment" do
164
+ with_env("MY_FLAG" => "42") do |ctx|
165
+ run "ruby -e \"puts ENV['MY_FLAG']\""
166
+ end
167
+ end
168
+
169
+ # Demonstrate optional symlink on Unix-like systems
170
+ target :symlink_demo, description: "Create a symlink to myapp (Unix only)" do
171
+ depends_on :build
172
+ platform :linux do
173
+ symlink "dist/myapp", "dist/myapp_link"
174
+ end
175
+ platform :darwin do
176
+ symlink "dist/myapp", "dist/myapp_link"
177
+ end
178
+ end
179
+
180
+ # Parallel demonstration: compile sources and tests concurrently
181
+ target :parallel_compile, description: "Compile sources and tests in parallel" do
182
+ parallel :compile_sources, :compile_tests
183
+ end
184
+
185
+ # Aggregate demo target
186
+ target :demo, description: "Run all demonstration features" do
187
+ depends_on :show_info
188
+ depends_on :gen_config
189
+ depends_on :write_demo
190
+ depends_on :env_demo
191
+ depends_on :build
192
+ depends_on :symlink_demo
193
+ end
194
+
95
195
  # Set the default target
96
- default :build
196
+ default :build
@@ -0,0 +1,4 @@
1
+ #pragma once
2
+ #include <string>
3
+
4
+ std::string hello();
@@ -0,0 +1,5 @@
1
+ #include "../include/hello.hpp"
2
+
3
+ std::string hello() {
4
+ return "Hello from Erebrus example";
5
+ }
@@ -0,0 +1,7 @@
1
+ #include <iostream>
2
+ #include "../include/hello.hpp"
3
+
4
+ int main() {
5
+ std::cout << hello() << std::endl;
6
+ return 0;
7
+ }
@@ -0,0 +1,8 @@
1
+ # Application Configuration
2
+ AppName={{APP_NAME}}
3
+ Version={{VERSION}}
4
+ HostOS={{HOST_OS}}
5
+ Binary=dist/myapp
6
+
7
+ # This file is generated by the Erebrus examples Buildfile.
8
+ # You can reference variables defined via set_variable or passed context.
@@ -0,0 +1,10 @@
1
+ #include <iostream>
2
+ #include <cassert>
3
+ #include "../include/hello.hpp"
4
+
5
+ int main() {
6
+ std::string msg = hello();
7
+ assert(msg == "Hello from Erebrus example");
8
+ std::cout << "Test passed: hello() returns expected message" << std::endl;
9
+ return 0;
10
+ }
@@ -1,4 +1,5 @@
1
1
  require "pathname"
2
+ require "rbconfig"
2
3
 
3
4
  module Erebrus
4
5
  class BuildEngine
@@ -14,6 +15,12 @@ module Erebrus
14
15
  @included_files = Set.new
15
16
  @file_watchers = {}
16
17
  @conditional_stack = []
18
+ @progress_total = 0
19
+ @progress_count = 0
20
+ @progress_enabled = false
21
+ @progress_line_open = false
22
+ # Predefine HOST_OS for use in Buildfiles and contexts
23
+ @variables["HOST_OS"] = detect_host_os
17
24
  end
18
25
 
19
26
  def target(name, description: nil, namespace: nil, &block)
@@ -126,8 +133,16 @@ module Erebrus
126
133
 
127
134
  merged_context = @variables.merge(context)
128
135
 
136
+ # Setup build-level progress bar across targets to execute
137
+ target_obj = @targets[target_name]
138
+ to_run = collect_targets_to_run(target_obj)
139
+ @progress_total = to_run.size
140
+ @progress_count = 0
141
+ @progress_enabled = $stdout.respond_to?(:isatty) && $stdout.isatty
142
+
129
143
  reset_all_targets!
130
144
  execute_target(target_obj, merged_context)
145
+ finish_progress_bar if @progress_enabled
131
146
  end
132
147
 
133
148
  def list_targets(namespace: nil)
@@ -218,8 +233,8 @@ module Erebrus
218
233
  return if target.executed
219
234
 
220
235
  unless target.conditions.all? { |condition| condition.call(context) }
221
- puts "Skipping target '#{target_name}' (conditions not met)"
222
236
  target.executed = true
237
+ increment_progress!
223
238
  return
224
239
  end
225
240
 
@@ -233,7 +248,10 @@ module Erebrus
233
248
  execute_target(dep_target, context, visited.dup)
234
249
  end
235
250
 
251
+ # Ensure progress bar line is terminated before any action output
252
+ finish_progress_bar if @progress_enabled
236
253
  target.execute(context)
254
+ increment_progress!
237
255
 
238
256
  visited.delete(target_name)
239
257
  end
@@ -241,6 +259,65 @@ module Erebrus
241
259
  def reset_all_targets!
242
260
  @targets.each_value(&:reset!)
243
261
  end
262
+
263
+ # Progress helpers -------------------------------------------------------
264
+ def collect_targets_to_run(root)
265
+ seen = Set.new
266
+ stack = [root]
267
+ until stack.empty?
268
+ t = stack.pop
269
+ next if seen.include?(t.name)
270
+ seen.add(t.name)
271
+ t.dependencies.each do |dep|
272
+ dep_name = resolve_target_name(dep)
273
+ dt = @targets[dep_name]
274
+ stack << dt if dt
275
+ end
276
+ end
277
+ seen
278
+ end
279
+
280
+ def increment_progress!
281
+ return unless @progress_enabled
282
+ @progress_count += 1
283
+ print_progress_bar(@progress_count, @progress_total)
284
+ end
285
+
286
+ def print_progress_bar(current, total)
287
+ current = [current, total].min
288
+ percent = total.zero? ? 100 : ((current.to_f / total) * 100).round
289
+ bar_length = 30
290
+ filled = (percent * bar_length / 100.0).round
291
+ bar = "[" + "#" * filled + " " * (bar_length - filled) + "]"
292
+ $stdout.print "\rProgress #{bar} #{percent}% (#{current}/#{total})"
293
+ $stdout.flush
294
+ @progress_line_open = true
295
+ end
296
+
297
+ def finish_progress_bar
298
+ return unless @progress_enabled
299
+ return unless @progress_line_open
300
+ $stdout.puts
301
+ @progress_line_open = false
302
+ end
303
+
304
+ private
305
+
306
+ def detect_host_os
307
+ host = RbConfig::CONFIG["host_os"].downcase
308
+ case host
309
+ when /mswin|mingw|cygwin/
310
+ "windows"
311
+ when /darwin|mac os/
312
+ "macos"
313
+ when /linux/
314
+ "linux"
315
+ when /freebsd|openbsd|netbsd/
316
+ "bsd"
317
+ else
318
+ "unknown"
319
+ end
320
+ end
244
321
  end
245
322
 
246
323
  class TargetContext
@@ -277,10 +354,82 @@ module Erebrus
277
354
  @target.action(&block)
278
355
  end
279
356
 
357
+ def echo(message)
358
+ @target.action do |context|
359
+ puts expand_variables(message, context)
360
+ end
361
+ end
362
+
363
+ def sleep(seconds)
364
+ @target.action do |context|
365
+ s = expand_variables(seconds, context)
366
+ Kernel.sleep(s.to_f)
367
+ end
368
+ end
369
+
370
+ def move(source, destination)
371
+ @target.action do |context|
372
+ expanded_source = expand_variables(source, context)
373
+ expanded_dest = expand_variables(destination, context)
374
+ require "fileutils"
375
+ FileUtils.mv(expanded_source, expanded_dest)
376
+ end
377
+ end
378
+
379
+ def symlink(target, link_name)
380
+ @target.action do |context|
381
+ expanded_target = expand_variables(target, context)
382
+ expanded_link = expand_variables(link_name, context)
383
+ require "fileutils"
384
+ FileUtils.ln_s(expanded_target, expanded_link, force: true)
385
+ end
386
+ end
387
+
388
+ def write(file, content)
389
+ @target.action do |context|
390
+ expanded_file = expand_variables(file, context)
391
+ expanded_content = expand_variables(content, context)
392
+ File.write(expanded_file, expanded_content)
393
+ end
394
+ end
395
+
396
+ def append(file, content)
397
+ @target.action do |context|
398
+ expanded_file = expand_variables(file, context)
399
+ expanded_content = expand_variables(content, context)
400
+ File.open(expanded_file, "a") { |f| f.write(expanded_content) }
401
+ end
402
+ end
403
+
404
+ def with_env(vars = {})
405
+ @target.action do |context|
406
+ merged = vars.transform_keys(&:to_s).transform_values { |v| expand_variables(v, context).to_s }
407
+ backup = {}
408
+ merged.each do |k, v|
409
+ backup[k] = ENV[k]
410
+ ENV[k] = v
411
+ end
412
+ begin
413
+ yield(context) if block_given?
414
+ ensure
415
+ merged.each_key { |k| ENV[k] = backup[k] }
416
+ end
417
+ end
418
+ end
419
+
420
+ def run_set(name, command)
421
+ @target.action do |context|
422
+ expanded_command = expand_variables(command, context)
423
+ puts "Running (capture): #{expanded_command}"
424
+ result = `#{expanded_command}`
425
+ raise Error, "Command failed: #{expanded_command}" unless $?.success?
426
+ @engine&.set_variable(name, result.chomp)
427
+ end
428
+ end
429
+
280
430
  def run(command, options = {})
281
431
  @target.action do |context|
282
432
  expanded_command = expand_variables(command, context)
283
- puts "Running: #{expanded_command}"
284
433
 
285
434
  if options[:capture]
286
435
  result = `#{expanded_command}`
@@ -297,8 +446,6 @@ module Erebrus
297
446
  @target.action do |context|
298
447
  expanded_source = expand_variables(source, context)
299
448
  expanded_dest = expand_variables(destination, context)
300
-
301
- puts "Copying #{expanded_source} to #{expanded_dest}"
302
449
  require "fileutils"
303
450
 
304
451
  if options[:preserve]
@@ -312,7 +459,6 @@ module Erebrus
312
459
  def mkdir(path)
313
460
  @target.action do |context|
314
461
  expanded_path = expand_variables(path, context)
315
- puts "Creating directory: #{expanded_path}"
316
462
  require "fileutils"
317
463
  FileUtils.mkdir_p(expanded_path)
318
464
  end
@@ -321,7 +467,6 @@ module Erebrus
321
467
  def remove(path)
322
468
  @target.action do |context|
323
469
  expanded_path = expand_variables(path, context)
324
- puts "Removing: #{expanded_path}"
325
470
  require "fileutils"
326
471
  FileUtils.rm_rf(expanded_path)
327
472
  end
@@ -330,7 +475,6 @@ module Erebrus
330
475
  def touch(path)
331
476
  @target.action do |context|
332
477
  expanded_path = expand_variables(path, context)
333
- puts "Touching: #{expanded_path}"
334
478
  require "fileutils"
335
479
  FileUtils.touch(expanded_path)
336
480
  end
@@ -339,7 +483,6 @@ module Erebrus
339
483
  def chmod(mode, path)
340
484
  @target.action do |context|
341
485
  expanded_path = expand_variables(path, context)
342
- puts "Changing permissions of #{expanded_path} to #{mode}"
343
486
  require "fileutils"
344
487
  FileUtils.chmod(mode, expanded_path)
345
488
  end
@@ -349,8 +492,6 @@ module Erebrus
349
492
  @target.action do |context|
350
493
  expanded_url = expand_variables(url, context)
351
494
  expanded_dest = expand_variables(destination, context)
352
-
353
- puts "Downloading #{expanded_url} to #{expanded_dest}"
354
495
  require "net/http"
355
496
  require "uri"
356
497
 
@@ -371,8 +512,6 @@ module Erebrus
371
512
  expanded_template = expand_variables(template_file, context)
372
513
  expanded_output = expand_variables(output_file, context)
373
514
 
374
- puts "Processing template #{expanded_template} -> #{expanded_output}"
375
-
376
515
  template_content = File.read(expanded_template)
377
516
  merged_vars = context.merge(variables)
378
517
 
@@ -75,15 +75,8 @@ module Erebrus
75
75
 
76
76
  start_time = Time.now
77
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
78
  begin
84
79
  @actions.each_with_index do |action, index|
85
- puts " Action #{index + 1}/#{@actions.size}" if @actions.size > 1
86
-
87
80
  if action.arity == 0
88
81
  action.call
89
82
  else
@@ -93,11 +86,7 @@ module Erebrus
93
86
 
94
87
  @executed = true
95
88
  @last_run = Time.now
96
-
97
- execution_time = Time.now - start_time
98
- puts " Completed in #{execution_time.round(2)}s"
99
89
  rescue StandardError => e
100
- puts " Failed: #{e.message}"
101
90
  raise Error, "Target '#{@name}' failed: #{e.message}"
102
91
  end
103
92
  end
@@ -1,3 +1,3 @@
1
1
  module Erebrus
2
- VERSION = "0.1.0"
3
- end
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,78 @@
1
+ module Erebrus
2
+ class BuildEngine
3
+ attr_reader targets: Hash[String, Erebrus::Target]
4
+ attr_reader namespaces: Hash[String, Array[String]]
5
+ attr_reader variables: Hash[String, untyped]
6
+ attr_reader included_files: ::Set[String]
7
+
8
+ attr_accessor current_namespace: String?
9
+
10
+ def initialize: () -> void
11
+
12
+ def target: ((String | Symbol) name, description?: String?, namespace?: String?, ?{ () -> void }) -> Erebrus::Target
13
+ def namespace: ((String | Symbol) name) { () -> void } -> void
14
+ def include_buildfile: (String file_path, namespace?: String?) -> void
15
+
16
+ def set_variable: ((String | Symbol) name, untyped value) -> void
17
+ def get_variable: ((String | Symbol) name, untyped? default) -> untyped
18
+
19
+ def conditional: (untyped condition) { () -> void } -> void
20
+
21
+ def platform?: ((String | Symbol) name) -> bool
22
+ def file_exists?: (String path) -> bool
23
+ def directory_exists?: (String path) -> bool
24
+
25
+ def default: ((String | Symbol) target_name) -> void
26
+ def build: (? (String | Symbol) target_name, ?Hash[Symbol | String, untyped] context) -> void
27
+
28
+ def list_targets: (namespace?: String) -> void
29
+ def list_namespaces: () -> void
30
+
31
+ def watch_file: (String pattern) { (String) -> untyped } -> void
32
+ def check_file_changes: () -> void
33
+ end
34
+
35
+ class TargetContext
36
+ def initialize: (Erebrus::Target target, ?Erebrus::BuildEngine engine) -> void
37
+
38
+ def depends_on: (* (String | Symbol) deps) -> Erebrus::Target
39
+ def depends_on_files: (* String files) -> Erebrus::Target
40
+ def produces: (* String files) -> Erebrus::Target
41
+
42
+ def condition: () ?{ (Hash[Symbol | String, untyped]) -> bool } -> Erebrus::Target
43
+ def only_if: ((::Proc | Symbol | String | bool) condition) -> Erebrus::Target
44
+
45
+ def tag: (* (String | Symbol) tags) -> Erebrus::Target
46
+
47
+ def action: () { () -> untyped } -> Erebrus::Target
48
+ | () { (Hash[Symbol | String, untyped]) -> untyped } -> Erebrus::Target
49
+ | () -> Erebrus::Target
50
+
51
+ def run: (String command, Hash[Symbol, untyped]? options) -> Erebrus::Target
52
+ def copy: (String source, String destination, Hash[Symbol, untyped]? options) -> Erebrus::Target
53
+ def mkdir: (String path) -> Erebrus::Target
54
+ def remove: (String path) -> Erebrus::Target
55
+ def touch: (String path) -> Erebrus::Target
56
+ def chmod: (Integer mode, String path) -> Erebrus::Target
57
+ def download: (String url, String destination) -> Erebrus::Target
58
+ def template: (String template_file, String output_file, Hash[Symbol | String, untyped]? variables) -> Erebrus::Target
59
+ def parallel: (* (String | Symbol) targets) -> Erebrus::Target
60
+
61
+ def conditional: (untyped condition) { (Hash[Symbol | String, untyped]) -> untyped } -> Erebrus::Target
62
+ def platform: ((String | Symbol) name) { (Hash[Symbol | String, untyped]) -> untyped } -> Erebrus::Target
63
+ def file_exists: (String path) { (Hash[Symbol | String, untyped]) -> untyped } -> Erebrus::Target
64
+ def directory_exists: (String path) { (Hash[Symbol | String, untyped]) -> untyped } -> Erebrus::Target
65
+
66
+ def set_variable: ((String | Symbol) name, untyped value) -> Erebrus::Target
67
+ def get_variable: ((String | Symbol) name, untyped? default) -> untyped
68
+
69
+ def echo: (String message) -> Erebrus::Target
70
+ def sleep: ((Integer | Float | String) seconds) -> Erebrus::Target
71
+ def move: (String source, String destination) -> Erebrus::Target
72
+ def symlink: (String target, String link_name) -> Erebrus::Target
73
+ def write: (String file, String content) -> Erebrus::Target
74
+ def append: (String file, String content) -> Erebrus::Target
75
+ def with_env: (Hash[Symbol | String, untyped] vars) { (Hash[Symbol | String, untyped]) -> untyped } -> Erebrus::Target
76
+ def run_set: ((String | Symbol) name, String command) -> Erebrus::Target
77
+ end
78
+ end
@@ -0,0 +1,37 @@
1
+ module Erebrus
2
+ module DSL
3
+ def self.included: (untyped base) -> void
4
+
5
+ module ClassMethods
6
+ def build_engine: () -> Erebrus::BuildEngine
7
+
8
+ def target: ((String | Symbol) name, description?: String?, namespace?: String?, ?{ () -> void }) -> Erebrus::Target
9
+ def namespace: ((String | Symbol) name) { () -> void } -> void
10
+ def include_buildfile: (String file_path, namespace?: String?) -> void
11
+
12
+ def set_variable: ((String | Symbol) name, untyped value) -> void
13
+ def get_variable: ((String | Symbol) name, untyped? default) -> untyped
14
+
15
+ def conditional: (untyped condition) { () -> void } -> void
16
+ def platform?: ((String | Symbol) name) -> bool
17
+ def default: ((String | Symbol) target_name) -> void
18
+ def build: (? (String | Symbol) target_name, ?Hash[Symbol | String, untyped] context) -> void
19
+ def list_targets: (namespace?: String) -> void
20
+ def list_namespaces: () -> void
21
+ end
22
+
23
+ def target: ((String | Symbol) name, description?: String?, namespace?: String?, ?{ () -> void }) -> Erebrus::Target
24
+ def namespace: ((String | Symbol) name) { () -> void } -> void
25
+ def include_buildfile: (String file_path, namespace?: String?) -> void
26
+
27
+ def set_variable: ((String | Symbol) name, untyped value) -> void
28
+ def get_variable: ((String | Symbol) name, untyped? default) -> untyped
29
+
30
+ def conditional: (untyped condition) { () -> void } -> void
31
+ def platform?: ((String | Symbol) name) -> bool
32
+ def default: ((String | Symbol) target_name) -> void
33
+ def build: (? (String | Symbol) target_name, ?Hash[Symbol | String, untyped] context) -> void
34
+ def list_targets: (namespace?: String) -> void
35
+ def list_namespaces: () -> void
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ module Erebrus
2
+ class Target
3
+ attr_reader name: String
4
+ attr_reader dependencies: Array[String]
5
+ attr_reader description: String?
6
+ attr_reader conditions: Array[::Proc]
7
+ attr_reader file_dependencies: Array[String]
8
+ attr_reader outputs: Array[String]
9
+
10
+ attr_accessor executed: bool
11
+ attr_accessor namespace: String?
12
+ attr_accessor tags: Array[String]
13
+ attr_accessor priority: Integer
14
+
15
+ def initialize: ((String | Symbol) name, ?description: String?) -> void
16
+
17
+ def depends_on: (* (String | Symbol) deps) -> Erebrus::Target
18
+ def depends_on_files: (* String files) -> Erebrus::Target
19
+ def produces: (* String files) -> Erebrus::Target
20
+
21
+ def condition: () ?{ (Hash[Symbol | String, untyped]) -> bool } -> Erebrus::Target
22
+ def only_if: ((::Proc | Symbol | String | bool) condition) -> Erebrus::Target
23
+
24
+ def tag: (* (String | Symbol) tags) -> Erebrus::Target
25
+
26
+ def action: () { () -> untyped } -> Erebrus::Target
27
+ | () { (Hash[Symbol | String, untyped]) -> untyped } -> Erebrus::Target
28
+ | () -> Erebrus::Target
29
+
30
+ def needs_execution?: (?Hash[Symbol | String, untyped]) -> bool
31
+ def execute: (?Hash[Symbol | String, untyped]) -> void
32
+ def reset!: () -> void
33
+ def file_dependencies_changed?: () -> bool
34
+ def to_s: () -> String
35
+ def inspect: () -> String
36
+ end
37
+ end
data/sig/erebrus.rbs CHANGED
@@ -1,4 +1,23 @@
1
1
  module Erebrus
2
2
  VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
3
+
4
+ class Error < ::StandardError
5
+ end
6
+
7
+ def self.target: ((String | Symbol) name, description?: String?, namespace?: String?, ?{ () -> void }) -> Erebrus::Target
8
+ def self.namespace: ((String | Symbol) name) { () -> void } -> void
9
+ def self.include_buildfile: (String file_path, namespace?: String?) -> void
10
+
11
+ def self.set_variable: ((String | Symbol) name, untyped value) -> void
12
+ def self.get_variable: ((String | Symbol) name, untyped? default) -> untyped
13
+
14
+ def self.conditional: (untyped condition) { () -> void } -> void
15
+ def self.platform?: ((String | Symbol) name) -> bool
16
+ def self.default: ((String | Symbol) target_name) -> void
17
+ def self.build: (? (String | Symbol) target_name, ?Hash[Symbol | String, untyped] context) -> void
18
+ def self.list_targets: (namespace?: String) -> void
19
+ def self.list_namespaces: () -> void
20
+
21
+ def self.load_buildfile: (String file_path) -> void
22
+ def self.reset!: () -> void
4
23
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: erebrus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - jel9
@@ -34,13 +34,22 @@ files:
34
34
  - README.md
35
35
  - Rakefile
36
36
  - bin/erebrus
37
+ - examples/.gitignore
37
38
  - examples/Buildfile
39
+ - examples/include/hello.hpp
40
+ - examples/src/hello.cpp
41
+ - examples/src/main.cpp
42
+ - examples/templates/app_config.tpl
43
+ - examples/tests/test_main.cpp
38
44
  - lib/erebrus.rb
39
45
  - lib/erebrus/build_engine.rb
40
46
  - lib/erebrus/dsl.rb
41
47
  - lib/erebrus/target.rb
42
48
  - lib/erebrus/version.rb
43
49
  - sig/erebrus.rbs
50
+ - sig/erebrus/build_engine.rbs
51
+ - sig/erebrus/dsl.rbs
52
+ - sig/erebrus/target.rbs
44
53
  homepage: https://github.com/daedalus-os/erebrus
45
54
  licenses: []
46
55
  metadata: