erebrus 0.1.2 → 0.1.4
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
- data/bin/erebrus +1 -3
- data/examples/Buildfile +37 -3
- data/lib/erebrus/build_engine.rb +179 -9
- data/lib/erebrus/version.rb +1 -1
- data/sig/erebrus/build_engine.rbs +8 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1bf5fcdb4df2979ab91e097f590ba95f5bf9340e491003badda831ebd8898276
|
|
4
|
+
data.tar.gz: 5ef55f5ca03dbc62c4404515e853c7552d0e7eef9126861e721089c8d710913e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e8b82d1a8064ecace9fc87632277a345ed1b7840cdd3c8c110b9506bd5f9e22edd0b13363aa7cf9b2333743e0b1f739e4af22e04daa8dd2ce29e0d5db47686ea
|
|
7
|
+
data.tar.gz: 88803910630b9a10898d172540e852c733bc312b1303c9bc9c6d7ba61ae8f9a50632eb0a14792b77140c4eab4afcc5ec19f960e9c17097c84deac355743dac27
|
data/bin/erebrus
CHANGED
|
@@ -39,7 +39,7 @@ class ErebrusCommand < Thor
|
|
|
39
39
|
load_buildfile_with_error_handling
|
|
40
40
|
|
|
41
41
|
begin
|
|
42
|
-
puts ""
|
|
42
|
+
puts ""
|
|
43
43
|
if namespace
|
|
44
44
|
say_title("Targets in namespace '#{namespace}'")
|
|
45
45
|
else
|
|
@@ -209,7 +209,6 @@ class ErebrusCommand < Thor
|
|
|
209
209
|
|
|
210
210
|
context.merge!(options[:var]) if options[:var]
|
|
211
211
|
|
|
212
|
-
# Legacy variables for backward compatibility
|
|
213
212
|
context["PLATFORM"] = RUBY_PLATFORM
|
|
214
213
|
context["RUBY_VERSION"] = RUBY_VERSION
|
|
215
214
|
context["PWD"] = Dir.pwd
|
|
@@ -217,7 +216,6 @@ class ErebrusCommand < Thor
|
|
|
217
216
|
context
|
|
218
217
|
end
|
|
219
218
|
|
|
220
|
-
# --- Styling helpers -----------------------------------------------------
|
|
221
219
|
def color_enabled?
|
|
222
220
|
options[:color] && $stdout.respond_to?(:isatty) && $stdout.isatty && ENV["NO_COLOR"].nil?
|
|
223
221
|
rescue StandardError
|
data/examples/Buildfile
CHANGED
|
@@ -192,21 +192,53 @@ target :parallel_compile, description: "Compile sources and tests in parallel" d
|
|
|
192
192
|
parallel :compile_sources, :compile_tests
|
|
193
193
|
end
|
|
194
194
|
|
|
195
|
+
# Demonstrate MSVC environment detection and setup (Windows only)
|
|
196
|
+
target :msvc_demo, description: "Detect MSVC and show version (Windows)" do
|
|
197
|
+
platform :windows do
|
|
198
|
+
conditional(msvc_available?) do
|
|
199
|
+
msvc_setup arch: "x64", version: "latest"
|
|
200
|
+
echo "MSVC Channel: ${MSVC_CHANNEL}"
|
|
201
|
+
echo "MSVC Version: ${MSVC_VERSION}"
|
|
202
|
+
run "where cl"
|
|
203
|
+
# Use cmd to ignore non-zero exit from cl /Bv
|
|
204
|
+
run "cmd /c \"cl /Bv & exit /b 0\""
|
|
205
|
+
end
|
|
206
|
+
conditional(!msvc_available?) do
|
|
207
|
+
echo "Visual Studio Build Tools not found. Install VS 2022/2019 Build Tools to enable MSVC."
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
target :msvc_preview_demo, description: "Detect VS Preview/Insiders and show version" do
|
|
213
|
+
platform :windows do
|
|
214
|
+
conditional(msvc_available?(version: "insiders")) do
|
|
215
|
+
msvc_setup arch: "x64", version: "insiders"
|
|
216
|
+
echo "MSVC Channel: ${MSVC_CHANNEL}"
|
|
217
|
+
echo "MSVC Version: ${MSVC_VERSION}"
|
|
218
|
+
run "where cl"
|
|
219
|
+
run "cmd /c \"cl /Bv & exit /b 0\""
|
|
220
|
+
end
|
|
221
|
+
conditional(!msvc_available?(version: "insiders")) do
|
|
222
|
+
echo "VS Preview/Insiders not found. Install Visual Studio Preview (Build Tools/Community)."
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
195
227
|
# Demonstrate conditional logic with system variables
|
|
196
228
|
target :system_specific, description: "Show system-specific behavior" do
|
|
197
229
|
echo "Detected system: ${HOST_OS} on ${HOST_ARCH}"
|
|
198
|
-
|
|
230
|
+
|
|
199
231
|
conditional(get_variable("HOST_OS") == "windows") do
|
|
200
232
|
echo "Running Windows-specific commands"
|
|
201
233
|
echo "User profile: ${HOME}"
|
|
202
234
|
echo "Temp directory: ${TEMP_DIR}"
|
|
203
235
|
end
|
|
204
|
-
|
|
236
|
+
|
|
205
237
|
conditional(get_variable("HOST_ARCH") == "x64") do
|
|
206
238
|
echo "64-bit architecture detected"
|
|
207
239
|
echo "Using optimized 64-bit settings"
|
|
208
240
|
end
|
|
209
|
-
|
|
241
|
+
|
|
210
242
|
# Show CPU-based logic
|
|
211
243
|
echo "System has ${CPU_COUNT} CPU cores available"
|
|
212
244
|
end
|
|
@@ -217,6 +249,8 @@ target :demo, description: "Run all demonstration features" do
|
|
|
217
249
|
depends_on :gen_config
|
|
218
250
|
depends_on :write_demo
|
|
219
251
|
depends_on :env_demo
|
|
252
|
+
depends_on :msvc_demo
|
|
253
|
+
depends_on :msvc_preview_demo
|
|
220
254
|
depends_on :system_specific
|
|
221
255
|
depends_on :build
|
|
222
256
|
depends_on :symlink_demo
|
data/lib/erebrus/build_engine.rb
CHANGED
|
@@ -19,7 +19,6 @@ module Erebrus
|
|
|
19
19
|
@progress_count = 0
|
|
20
20
|
@progress_enabled = false
|
|
21
21
|
@progress_line_open = false
|
|
22
|
-
# Predefine useful system and build variables
|
|
23
22
|
@variables["HOST_OS"] = detect_host_os
|
|
24
23
|
@variables["HOST_ARCH"] = detect_host_arch
|
|
25
24
|
@variables["CPU_COUNT"] = detect_cpu_count
|
|
@@ -140,7 +139,6 @@ module Erebrus
|
|
|
140
139
|
|
|
141
140
|
merged_context = @variables.merge(context)
|
|
142
141
|
|
|
143
|
-
# Setup build-level progress bar across targets to execute
|
|
144
142
|
target_obj = @targets[target_name]
|
|
145
143
|
to_run = collect_targets_to_run(target_obj)
|
|
146
144
|
@progress_total = to_run.size
|
|
@@ -255,7 +253,6 @@ module Erebrus
|
|
|
255
253
|
execute_target(dep_target, context, visited.dup)
|
|
256
254
|
end
|
|
257
255
|
|
|
258
|
-
# Ensure progress bar line is terminated before any action output
|
|
259
256
|
finish_progress_bar if @progress_enabled
|
|
260
257
|
target.execute(context)
|
|
261
258
|
increment_progress!
|
|
@@ -267,7 +264,6 @@ module Erebrus
|
|
|
267
264
|
@targets.each_value(&:reset!)
|
|
268
265
|
end
|
|
269
266
|
|
|
270
|
-
# Progress helpers -------------------------------------------------------
|
|
271
267
|
def collect_targets_to_run(root)
|
|
272
268
|
seen = Set.new
|
|
273
269
|
stack = [root]
|
|
@@ -295,14 +291,66 @@ module Erebrus
|
|
|
295
291
|
def print_progress_bar(current, total)
|
|
296
292
|
current = [current, total].min
|
|
297
293
|
percent = total.zero? ? 100 : ((current.to_f / total) * 100).round
|
|
298
|
-
bar_length =
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
294
|
+
bar_length = 25
|
|
295
|
+
|
|
296
|
+
filled_exact = (percent * bar_length / 100.0)
|
|
297
|
+
filled_full = filled_exact.floor
|
|
298
|
+
partial = filled_exact - filled_full
|
|
299
|
+
|
|
300
|
+
full_block = "█"
|
|
301
|
+
partial_blocks = ["", "▏", "▎", "▍", "▌", "▋", "▊", "▉"]
|
|
302
|
+
empty_block = "░"
|
|
303
|
+
|
|
304
|
+
bar = ""
|
|
305
|
+
bar += full_block * filled_full
|
|
306
|
+
|
|
307
|
+
if filled_full < bar_length && partial > 0
|
|
308
|
+
partial_index = (partial * (partial_blocks.length - 1)).round
|
|
309
|
+
bar += partial_blocks[partial_index]
|
|
310
|
+
filled_full += 1
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
bar += empty_block * (bar_length - filled_full)
|
|
314
|
+
|
|
315
|
+
if color_supported?
|
|
316
|
+
colored_bar = colorize_progress_bar(bar, filled_exact, bar_length)
|
|
317
|
+
$stdout.print "\r#{colored_bar} #{colorize(percent.to_s + "%",
|
|
318
|
+
:cyan)} #{colorize("(#{current}/#{total})", :dim)}"
|
|
319
|
+
else
|
|
320
|
+
$stdout.print "\r#{bar} #{percent}% (#{current}/#{total})"
|
|
321
|
+
end
|
|
322
|
+
|
|
302
323
|
$stdout.flush
|
|
303
324
|
@progress_line_open = true
|
|
304
325
|
end
|
|
305
326
|
|
|
327
|
+
def color_supported?
|
|
328
|
+
$stdout.respond_to?(:isatty) && $stdout.isatty && ENV["NO_COLOR"].nil?
|
|
329
|
+
rescue StandardError
|
|
330
|
+
false
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def colorize(text, color)
|
|
334
|
+
return text unless color_supported?
|
|
335
|
+
|
|
336
|
+
case color
|
|
337
|
+
when :green then "\e[32m#{text}\e[0m"
|
|
338
|
+
when :cyan then "\e[36m#{text}\e[0m"
|
|
339
|
+
when :dim then "\e[2m#{text}\e[0m"
|
|
340
|
+
else text
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def colorize_progress_bar(bar, filled_exact, bar_length)
|
|
345
|
+
return bar unless color_supported?
|
|
346
|
+
|
|
347
|
+
filled_chars = filled_exact.ceil
|
|
348
|
+
filled_portion = bar[0, filled_chars]
|
|
349
|
+
empty_portion = bar[filled_chars..-1] || ""
|
|
350
|
+
|
|
351
|
+
"\e[32m#{filled_portion}\e[0m\e[2m#{empty_portion}\e[0m"
|
|
352
|
+
end
|
|
353
|
+
|
|
306
354
|
def finish_progress_bar
|
|
307
355
|
return unless @progress_enabled
|
|
308
356
|
return unless @progress_line_open
|
|
@@ -363,6 +411,77 @@ module Erebrus
|
|
|
363
411
|
def detect_temp_dir
|
|
364
412
|
ENV["TMPDIR"] || ENV["TMP"] || ENV["TEMP"] || "/tmp"
|
|
365
413
|
end
|
|
414
|
+
|
|
415
|
+
public
|
|
416
|
+
# --- MSVC / Visual Studio environment helpers -------------------------
|
|
417
|
+
# Returns a Hash of environment variables configured by VsDevCmd for a given arch
|
|
418
|
+
def msvc_env(arch: "x64", version: "latest")
|
|
419
|
+
raise Error, "MSVC environment is only available on Windows" unless detect_host_os == "windows"
|
|
420
|
+
|
|
421
|
+
vsdevcmd = find_vsdevcmd(version: version)
|
|
422
|
+
raise Error, "Visual Studio Dev Cmd not found" unless vsdevcmd && File.exist?(vsdevcmd)
|
|
423
|
+
|
|
424
|
+
# Capture environment after activating VS developer environment
|
|
425
|
+
cmd = %Q{cmd.exe /c "\"#{vsdevcmd}\" -arch=#{arch} && set"}
|
|
426
|
+
output = `#{cmd}`
|
|
427
|
+
raise Error, "Failed to load MSVC environment" unless $?.success?
|
|
428
|
+
|
|
429
|
+
env = {}
|
|
430
|
+
output.each_line do |line|
|
|
431
|
+
line = line.strip
|
|
432
|
+
next if line.empty?
|
|
433
|
+
next unless line.include?("=")
|
|
434
|
+
k, v = line.split("=", 2)
|
|
435
|
+
env[k] = v
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
env
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Try to locate VsDevCmd.bat using vswhere, then fall back to well-known paths
|
|
442
|
+
def find_vsdevcmd(version: "latest")
|
|
443
|
+
# Normalize version request
|
|
444
|
+
v = (version || "latest").to_s.downcase
|
|
445
|
+
include_prerelease = (v == "insiders" || v == "preview" || v == "prerelease")
|
|
446
|
+
|
|
447
|
+
# First try vswhere
|
|
448
|
+
vswhere = File.join(ENV["ProgramFiles(x86)"] || "C:/Program Files (x86)", "Microsoft Visual Studio", "Installer", "vswhere.exe")
|
|
449
|
+
if File.exist?(vswhere)
|
|
450
|
+
# Prefer installations that have VC tools
|
|
451
|
+
flags = []
|
|
452
|
+
flags << "-products *"
|
|
453
|
+
flags << "-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64"
|
|
454
|
+
flags << "-latest" if v == "latest" || include_prerelease
|
|
455
|
+
flags << "-prerelease" if include_prerelease
|
|
456
|
+
property_cmd = %Q{"#{vswhere}" #{flags.join(" ")} -property installationPath}
|
|
457
|
+
install_path = `#{property_cmd}`.to_s.strip
|
|
458
|
+
if !$?.nil? && $?.success? && install_path && !install_path.empty?
|
|
459
|
+
vsdevcmd = File.join(install_path, "Common7", "Tools", "VsDevCmd.bat")
|
|
460
|
+
return vsdevcmd if File.exist?(vsdevcmd)
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# Fallback to common installation paths
|
|
465
|
+
pf = ENV["ProgramFiles"] || "C:/Program Files"
|
|
466
|
+
pf86 = ENV["ProgramFiles(x86)"] || "C:/Program Files (x86)"
|
|
467
|
+
years = case v
|
|
468
|
+
when "2019" then ["2019", "2022", "2026"]
|
|
469
|
+
when "2022" then ["2022", "2019", "2026"]
|
|
470
|
+
when "2026" then ["2026", "2022", "2019"]
|
|
471
|
+
else ["2026", "2022", "2019"]
|
|
472
|
+
end
|
|
473
|
+
editions = ["Preview", "BuildTools", "Community", "Professional", "Enterprise"]
|
|
474
|
+
[pf, pf86].each do |base|
|
|
475
|
+
years.each do |year|
|
|
476
|
+
editions.each do |edition|
|
|
477
|
+
candidate = File.join(base, "Microsoft Visual Studio", year, edition, "Common7", "Tools", "VsDevCmd.bat")
|
|
478
|
+
return candidate if File.exist?(candidate)
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
nil
|
|
484
|
+
end
|
|
366
485
|
end
|
|
367
486
|
|
|
368
487
|
class TargetContext
|
|
@@ -412,6 +531,52 @@ module Erebrus
|
|
|
412
531
|
end
|
|
413
532
|
end
|
|
414
533
|
|
|
534
|
+
# Check if MSVC environment (VsDevCmd) is available on this machine
|
|
535
|
+
def msvc_available?(version: "latest")
|
|
536
|
+
!!(@engine&.find_vsdevcmd(version: version))
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
# Activate MSVC environment variables for subsequent actions in this target
|
|
540
|
+
def msvc_setup(options = {})
|
|
541
|
+
@target.action do |_context|
|
|
542
|
+
arch = options[:arch] || "x64"
|
|
543
|
+
version = options[:version] || "latest"
|
|
544
|
+
env = @engine&.msvc_env(arch: arch, version: version)
|
|
545
|
+
raise Error, "MSVC environment not found" unless env && env.any?
|
|
546
|
+
|
|
547
|
+
# Set env for this process so later run/system calls inherit it
|
|
548
|
+
env.each { |k, v| ENV[k] = v }
|
|
549
|
+
|
|
550
|
+
# Expose some useful MSVC-related variables to the build context
|
|
551
|
+
@engine&.set_variable("MSVC_VERSION", env["VCToolsVersion"] || env["VisualStudioVersion"] || "unknown")
|
|
552
|
+
@engine&.set_variable("MSVC_CHANNEL", (version.to_s.downcase =~ /(preview|insiders|prerelease)/ ? "preview" : "stable"))
|
|
553
|
+
@engine&.set_variable("VSINSTALLDIR", env["VSINSTALLDIR"]) if env["VSINSTALLDIR"]
|
|
554
|
+
@engine&.set_variable("VCToolsInstallDir", env["VCToolsInstallDir"]) if env["VCToolsInstallDir"]
|
|
555
|
+
@engine&.set_variable("WindowsSdkDir", env["WindowsSdkDir"]) if env["WindowsSdkDir"]
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
# Temporarily activate MSVC environment within the provided block
|
|
560
|
+
def with_msvc_env(options = {})
|
|
561
|
+
@target.action do |context|
|
|
562
|
+
arch = options[:arch] || "x64"
|
|
563
|
+
version = options[:version] || "latest"
|
|
564
|
+
env = @engine&.msvc_env(arch: arch, version: version)
|
|
565
|
+
raise Error, "MSVC environment not found" unless env && env.any?
|
|
566
|
+
|
|
567
|
+
backup = {}
|
|
568
|
+
env.each do |k, v|
|
|
569
|
+
backup[k] = ENV[k]
|
|
570
|
+
ENV[k] = v
|
|
571
|
+
end
|
|
572
|
+
begin
|
|
573
|
+
yield(context) if block_given?
|
|
574
|
+
ensure
|
|
575
|
+
env.each_key { |k| ENV[k] = backup[k] }
|
|
576
|
+
end
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
|
|
415
580
|
def move(source, destination)
|
|
416
581
|
@target.action do |context|
|
|
417
582
|
expanded_source = expand_variables(source, context)
|
|
@@ -627,7 +792,12 @@ module Erebrus
|
|
|
627
792
|
|
|
628
793
|
text.gsub(/\$\{(\w+)\}|\$(\w+)/) do |match|
|
|
629
794
|
var_name = ::Regexp.last_match(1) || ::Regexp.last_match(2)
|
|
630
|
-
|
|
795
|
+
# Prefer context, then engine variables, then ENV
|
|
796
|
+
context[var_name] ||
|
|
797
|
+
context[var_name.to_sym] ||
|
|
798
|
+
(@engine && @engine.get_variable(var_name)) ||
|
|
799
|
+
ENV[var_name] ||
|
|
800
|
+
match
|
|
631
801
|
end
|
|
632
802
|
end
|
|
633
803
|
end
|
data/lib/erebrus/version.rb
CHANGED
|
@@ -30,6 +30,9 @@ module Erebrus
|
|
|
30
30
|
|
|
31
31
|
def watch_file: (String pattern) { (String) -> untyped } -> void
|
|
32
32
|
def check_file_changes: () -> void
|
|
33
|
+
|
|
34
|
+
# MSVC helpers
|
|
35
|
+
def msvc_env: (arch?: String, version?: String) -> Hash[String, String]
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
class TargetContext
|
|
@@ -74,5 +77,10 @@ module Erebrus
|
|
|
74
77
|
def append: (String file, String content) -> Erebrus::Target
|
|
75
78
|
def with_env: (Hash[Symbol | String, untyped] vars) { (Hash[Symbol | String, untyped]) -> untyped } -> Erebrus::Target
|
|
76
79
|
def run_set: ((String | Symbol) name, String command) -> Erebrus::Target
|
|
80
|
+
|
|
81
|
+
# MSVC environment actions
|
|
82
|
+
def msvc_available?: (version?: String) -> bool
|
|
83
|
+
def msvc_setup: (Hash[Symbol | String, untyped] options) -> Erebrus::Target
|
|
84
|
+
def with_msvc_env: (Hash[Symbol | String, untyped] options) { (Hash[Symbol | String, untyped]) -> untyped } -> Erebrus::Target
|
|
77
85
|
end
|
|
78
86
|
end
|