wslc-wip 0.17.4 → 0.17.6

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: 0c7d06fb49766e980be92c2d70d3a0c86dc68b16c0c2b5d84e05d5baa1dd40f1
4
- data.tar.gz: 61ad143b173b17849427d03efdaa451849b0e58d16a3eb97c55855d641ced273
3
+ metadata.gz: adf27a8bafbb2482bfe25b393e3623a39fd0c8d77f92a7c6e6e60286506c8f17
4
+ data.tar.gz: 77d180849706ea0915e82c03a7bfcae6d5ee0c581a87db808e3eceacdb0b8210
5
5
  SHA512:
6
- metadata.gz: c1accaef0df664f9d35659d08cb8782c1e54ea9a56cae60e1bd53ddd347c2736501e1fc785be9667541c225f88340e448fd8b2fab32a4f41171f613863533ea4
7
- data.tar.gz: 235f4c9ad2997138d262eaac4098edfe0582db8bf56dc8743250a17d10fac14df96fdea3ab575faef91f096e4f091ad2a1ffb06931da59aa5e683eaf40327c03
6
+ metadata.gz: f894b61d72a792604221a0bc41c5bef018cb2257ee7469d958f17f96fd5de57e52f2a1665b74e51bfbbd3e6cea6ba9f684f42a4ab64747462098c4742b7cd350
7
+ data.tar.gz: 68201cce4009583ffee442f06858d930a9d618a399553fbf04523281c8012630e0c8bedc1517012b5f9afca9896612d42829206303943b5548118d0a2d960d17
data/README.md CHANGED
@@ -370,8 +370,8 @@ valid compose.yml over sections it doesn't need to look at:
370
370
  | `wip version` | wip's version, plus WSLC's if it can be detected |
371
371
  | `wip doctor` | Diagnose WSL2, interop, WSLC, config, architecture, and Git |
372
372
  | `wip config` | Print the effective configuration (secrets masked) |
373
- | `wip build -- --no-cache` | Build the image from the `build` definition |
374
- | `wip up [-d] [--no-sync]` | Start the primary `dependencies:` entry (`container:` names which one) and its sidecars (creating any that are missing, on `network:` if set). `-d` runs the main container in the background; with `sync:` configured, the source is mirrored into the volume first unless `--no-sync` |
373
+ | `wip build [--no-cache] [-- OPTIONS]` | Build the image from the `build` definition. By default, wip passes the target tag as `--cache-from` so previous layers can be reused; `--no-cache` disables that cache path. |
374
+ | `wip up [-d] [--no-sync] [--no-cache]` | Start the primary `dependencies:` entry (`container:` names which one) and its sidecars (creating any that are missing, on `network:` if set). `-d` runs the main container in the background; with `sync:` configured, the source is mirrored into the volume first unless `--no-sync` |
375
375
  | `wip down` | Stop and remove the primary container and its sidecar `dependencies:` |
376
376
  | `wip exec [--no-interactive] COMMAND...` | Run a command in the existing container |
377
377
  | `wip run [--no-interactive] COMMAND...` | Run a command in a new `--rm` container (mode: compose `exec`s into `compose.service` instead — see "Compose mode" above) |
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'find'
3
4
  require 'fileutils'
4
5
  require 'pathname'
5
6
  require 'tmpdir'
@@ -41,13 +42,31 @@ module Wip
41
42
  end
42
43
  end
43
44
 
45
+ # Walks the tree by hand (rather than Dir.glob-ing it all up front) so an
46
+ # ignored directory — node_modules, vendor/bundle, a multi-gigabyte
47
+ # storage/ — is never descended into just to be thrown away afterward.
44
48
  def included_files
45
- Dir.glob('**/*', File::FNM_DOTMATCH, base: @root.to_s).select do |entry|
46
- next false if %w[. ..].include?(entry)
49
+ result = []
50
+ Find.find(@root.to_s) do |path|
51
+ next if path == @root.to_s
47
52
 
48
- path = @root.join(entry)
49
- (path.file? || path.symlink?) && !@ignore.ignored?(entry)
53
+ entry = Pathname(path).relative_path_from(@root).to_s
54
+ result << entry if visit?(path, entry)
50
55
  end
56
+ result
57
+ end
58
+
59
+ # Returns whether `path` belongs in the staged context, pruning the walk
60
+ # when it's an ignored directory so nothing under it is even visited —
61
+ # unless a later negated rule could still re-include something beneath it.
62
+ def visit?(path, entry)
63
+ ignored = @ignore.ignored?(entry)
64
+ if !File.symlink?(path) && File.directory?(path)
65
+ Find.prune if ignored && @ignore.prunable?(entry)
66
+ return false
67
+ end
68
+
69
+ !ignored && (File.file?(path) || File.symlink?(path))
51
70
  end
52
71
  end
53
72
  end
data/lib/wip/cli.rb CHANGED
@@ -66,15 +66,17 @@ module Wip
66
66
  end
67
67
 
68
68
  desc 'build [OPTIONS]', 'Build the configured image'
69
+ option :no_cache, type: :boolean, default: false, desc: 'Build without using cached layers'
69
70
  def build(*extra)
70
- extra.shift if extra.first == '--'
71
+ extra = build_extra_options(extra)
71
72
  settings = load_config.command('build') || {}
72
- context = settings['context'] || '.'
73
+ context = Pathname(load_config.path).dirname.join(settings['context'] || '.').to_s
73
74
  warn "wip: staging build context (#{context})"
74
75
  progress = StagingProgress.new
75
76
  BuildContext.new(context).stage(on_progress: progress.method(:tick)) do |staged_context|
76
77
  progress.finish
77
- execute(builder.build(settings: settings.merge('context' => staged_context), extra: extra))
78
+ built = builder.build(settings: settings.merge('context' => staged_context), extra: extra)
79
+ execute(built, interactive: tty?(true))
78
80
  end
79
81
  ensure
80
82
  progress&.finish
@@ -83,6 +85,7 @@ module Wip
83
85
  desc 'up', 'Start the configured container and its dependencies, creating them if necessary'
84
86
  option :detach, type: :boolean, default: false, aliases: '-d'
85
87
  option :sync, type: :boolean, default: true, desc: 'Mirror the source into the sync volume first (--no-sync skips)'
88
+ option :no_cache, type: :boolean, default: false, desc: 'Build compose-native images without cached layers'
86
89
  def up
87
90
  if load_config.compose?
88
91
  sync_before_boot if options[:sync]
@@ -301,11 +304,39 @@ module Wip
301
304
  # `wip up` invocation, mirroring ensure_sync_image's "build once, not every tick"
302
305
  # approach. A no-op for mode: container/compose and for build:-less services.
303
306
  def ensure_compose_images
304
- load_config.compose_build_specs.each_value do |spec|
305
- extra = spec['dockerfile'] ? ['-f', spec['dockerfile']] : []
306
- spec['args']&.each { |key, value| extra.push('--build-arg', "#{key}=#{value}") }
307
- execute(builder.build(settings: { 'context' => spec['context'], 'tag' => spec['tag'] }, extra: extra))
307
+ load_config.compose_build_specs.each_value { |spec| build_compose_image(spec) }
308
+ end
309
+
310
+ # Stages spec['context'] the same way `wip build` does, so a .dockerignore next to
311
+ # a compose-native service's build: is honored and progress is reported here too.
312
+ def build_compose_image(spec)
313
+ extra = compose_build_extra(spec)
314
+ warn "wip: staging build context (#{spec['context']})"
315
+ progress = StagingProgress.new
316
+ BuildContext.new(spec['context']).stage(on_progress: progress.method(:tick)) do |staged_context|
317
+ progress.finish
318
+ settings = { 'context' => staged_context, 'tag' => spec['tag'] }
319
+ execute(builder.build(settings: settings, extra: extra), interactive: tty?(true))
308
320
  end
321
+ ensure
322
+ progress&.finish
323
+ end
324
+
325
+ def build_extra_options(extra)
326
+ extra = extra.dup
327
+ extra.shift if extra.first == '--'
328
+ with_no_cache_option(extra)
329
+ end
330
+
331
+ def compose_build_extra(spec)
332
+ extra = spec['dockerfile'] ? ['-f', spec['dockerfile']] : []
333
+ spec['args']&.each { |key, value| extra.push('--build-arg', "#{key}=#{value}") }
334
+ with_no_cache_option(extra)
335
+ end
336
+
337
+ def with_no_cache_option(extra)
338
+ extra.unshift('--no-cache') if options[:no_cache] && !extra.include?('--no-cache')
339
+ extra
309
340
  end
310
341
 
311
342
  # Builds sync.build's image once per invocation (not per --watch tick, which
@@ -142,7 +142,8 @@ module Wip
142
142
  tag = values['tag'] || values['image']
143
143
  raise ConfigError, 'Build image/tag must not be empty' if tag.to_s.empty?
144
144
 
145
- [@wslc, 'build', '-t', tag, *extra, context]
145
+ cache_options = extra.include?('--no-cache') ? [] : ['--cache-from', tag]
146
+ [@wslc, 'build', '-t', tag, *cache_options, *extra, context]
146
147
  end
147
148
 
148
149
  def custom(name, arguments)
@@ -27,7 +27,7 @@ module Wip
27
27
  status = wait.value
28
28
  end
29
29
  report_hint(captured) unless status.success?
30
- status.exitstatus
30
+ exitstatus(status)
31
31
  rescue Errno::ENOENT => e
32
32
  @stderr.puts e.message
33
33
  127
@@ -38,6 +38,13 @@ module Wip
38
38
 
39
39
  private
40
40
 
41
+ # exitstatus is nil when the process was killed by a signal instead of
42
+ # exiting normally; fall back to the conventional 128+signal shell code
43
+ # so callers always get a comparable integer.
44
+ def exitstatus(status)
45
+ status.exitstatus || (128 + status.termsig)
46
+ end
47
+
41
48
  def report_hint(captured)
42
49
  hint = @interpreter.interpret(captured.force_encoding(Encoding::UTF_8).scrub)
43
50
  @stderr.puts("\n#{hint}") if hint
@@ -50,7 +57,7 @@ module Wip
50
57
  def run_attached(command, env)
51
58
  pid = Process.spawn(env, *command)
52
59
  _, status = Process.wait2(pid)
53
- status.exitstatus
60
+ exitstatus(status)
54
61
  rescue Errno::ENOENT => e
55
62
  @stderr.puts e.message
56
63
  127
@@ -30,8 +30,26 @@ module Wip
30
30
  end
31
31
  end
32
32
 
33
+ # Whether a walker can stop descending into an ignored directory without
34
+ # risking a miss: false if some later negated rule (e.g. `!node_modules/pkg`
35
+ # after `node_modules`) could still match something beneath it.
36
+ def prunable?(relative_path)
37
+ dir_components = relative_path.split('/')
38
+ @rules.none? { |rule| rule.negate && targets_descendant?(rule.pattern, dir_components) }
39
+ end
40
+
33
41
  private
34
42
 
43
+ def targets_descendant?(pattern, dir_components)
44
+ pattern_components = pattern.split('/')
45
+ return true if pattern_components.first == '**'
46
+ return false if pattern_components.size <= dir_components.size
47
+
48
+ pattern_components.first(dir_components.size).each_with_index.all? do |component, i|
49
+ File.fnmatch(component, dir_components[i], FNMATCH_FLAGS)
50
+ end
51
+ end
52
+
35
53
  def parse(line)
36
54
  line = line.strip
37
55
  return nil if line.empty? || line.start_with?('#')
data/lib/wip/doctor.rb CHANGED
@@ -151,6 +151,8 @@ module Wip
151
151
  condition == :ok ? ok_message : fail_message)
152
152
  end
153
153
 
154
- def command_available?(name) = ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).any? { |dir| File.executable?(File.join(dir, name)) }
154
+ def command_available?(name)
155
+ ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).any? { |dir| File.executable?(File.join(dir, name)) }
156
+ end
155
157
  end
156
158
  end
data/lib/wip/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wip
4
- VERSION = '0.17.4'
4
+ VERSION = '0.17.6'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wslc-wip
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.4
4
+ version: 0.17.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wip contributors