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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0a0cf035d47b272eefe1385dd63919f5b6c58c75765248dc165f3ba942057b6
4
- data.tar.gz: 637e125435680c16aefe62e318bc78990b2b0e1910d3c669071757996442279c
3
+ metadata.gz: 1bf5fcdb4df2979ab91e097f590ba95f5bf9340e491003badda831ebd8898276
4
+ data.tar.gz: 5ef55f5ca03dbc62c4404515e853c7552d0e7eef9126861e721089c8d710913e
5
5
  SHA512:
6
- metadata.gz: 935badb603ccfa49c812c702fd884f143a130fa051fda3f66fab25080a704549c11df48fc4fb3d731dd3789a1225488d2bd3229a700a5ef0dcab42cb6136bd9f
7
- data.tar.gz: '008219953c4f5ca5f9e245240bc824c7db35cba780830dee4e2fa58680fe3a8259a87350e4146f42bb91ab838381a51c0b8b899a960338e6eb4bdcd69496861d'
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 "" # spacing for readability
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
@@ -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 = 30
299
- filled = (percent * bar_length / 100.0).round
300
- bar = "[" + "#" * filled + " " * (bar_length - filled) + "]"
301
- $stdout.print "\rProgress #{bar} #{percent}% (#{current}/#{total})"
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
- context[var_name] || context[var_name.to_sym] || ENV[var_name] || match
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
@@ -1,3 +1,3 @@
1
1
  module Erebrus
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.4"
3
3
  end
@@ -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
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.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - jel9