wslc-wip 1.1.0 → 1.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 +4 -4
- data/README.md +12 -9
- data/lib/wip/build_context.rb +5 -0
- data/lib/wip/cli.rb +19 -8
- data/lib/wip/command_runner.rb +80 -8
- data/lib/wip/compose_file.rb +6 -4
- data/lib/wip/version.rb +1 -1
- 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: 49996dea1b8ae083cceb0caaafc0b8ddcc6de0a3ab8a7f5f4f856156948840d3
|
|
4
|
+
data.tar.gz: cca5758770d9f4364427c1ae7519ee8026c598b1002206fc9af8f22f5ffc0a64
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: df63da254bbbb1b6dedf5e56b5a78d8fbb09fd7d6dfa41ae0b3b58db600f07e8703ea3d729a4bb125e8d1a29e498ce8c3e4504e93d74ad51ce37b7d4a3bad0db
|
|
7
|
+
data.tar.gz: 8b8607bdda225610b7c7389210c16a04764aa95ba45748f9bcd3c384f9b6eb3ccb371342cea89a12749d7c4226f9fe6b94729c191513690b40168b339d21dd7b
|
data/README.md
CHANGED
|
@@ -244,8 +244,10 @@ top level (`networks:`, `volumes:`, `configs:`, `secrets:`, ...) is the one exce
|
|
|
244
244
|
real Compose tools, not by `wip`, so `wip` silently ignores it rather than rejecting an otherwise
|
|
245
245
|
valid compose.yml over sections it doesn't need to look at:
|
|
246
246
|
|
|
247
|
-
- Per service: `image`, `build` (string or `{context:, dockerfile:}`;
|
|
248
|
-
`compose.yml`, not wherever `wip` is invoked from
|
|
247
|
+
- Per service: `image`, `build` (string or `{context:, dockerfile:, args:, shadow_context:}`;
|
|
248
|
+
`context` is resolved relative to `compose.yml`, not wherever `wip` is invoked from; `dockerfile`
|
|
249
|
+
stays relative to `context` itself — see [.dockerignore](#dockerignore) for what `shadow_context`
|
|
250
|
+
does), `command` (shell or exec form), `environment`
|
|
249
251
|
(mapping or `KEY=VALUE` array — a mapping value must not be null; host environment pass-through
|
|
250
252
|
isn't supported), `ports`/`volumes` (short syntax only — `"host:container"` strings, not
|
|
251
253
|
long-syntax mappings), `working_dir`, `user`, `depends_on` (ordering only — a `condition:` other
|
|
@@ -272,13 +274,14 @@ to load a different file instead.
|
|
|
272
274
|
|
|
273
275
|
`wip build` reads `.dockerignore` from the build context and excludes anything it matches before
|
|
274
276
|
handing the context to `wslc build`, since `wslc` sends the context as-is otherwise. Set
|
|
275
|
-
`commands.build.shadow_context` in `wip.yml`
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
277
|
+
`commands.build.shadow_context` in `wip.yml` (or `build.shadow_context` on a compose-native
|
|
278
|
+
`build:` service in `compose.yml`) to a directory on the Windows filesystem to enable a persistent
|
|
279
|
+
shadow context for projects outside `/mnt/<drive>`. The first build copies every included file;
|
|
280
|
+
later builds only copy added or changed files and remove deleted or newly ignored files. Without
|
|
281
|
+
this setting the optimization is disabled, and it only applies under WSL2 — on WSL1 (or anywhere
|
|
282
|
+
else) the context is handed to `wslc build` directly. Projects already on `/mnt/c` (or another
|
|
283
|
+
mounted Windows drive) also continue to build directly even when the setting is present. The path
|
|
284
|
+
must live outside the build context itself, or `wip build`/`wip up` refuses it.
|
|
282
285
|
|
|
283
286
|
### Source sync
|
|
284
287
|
|
data/lib/wip/build_context.rb
CHANGED
|
@@ -30,6 +30,11 @@ module Wip
|
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
# Whether the upcoming stage will use the Windows-side shadow directory
|
|
34
|
+
# (configured shadow_root, on WSL2, with a context outside /mnt) rather
|
|
35
|
+
# than staging in place or under a WSL-side tmpdir.
|
|
36
|
+
def shadow? = shadow_required?
|
|
37
|
+
|
|
33
38
|
private
|
|
34
39
|
|
|
35
40
|
# A shadow root under the context would itself be walked by included_files
|
data/lib/wip/cli.rb
CHANGED
|
@@ -76,8 +76,7 @@ module Wip
|
|
|
76
76
|
build_context = BuildContext.new(context, shadow_root: settings['shadow_context'])
|
|
77
77
|
build_context.stage(on_progress: progress.method(:tick)) do |staged_context|
|
|
78
78
|
progress.finish
|
|
79
|
-
|
|
80
|
-
execute(built, interactive: tty?(true))
|
|
79
|
+
run_staged_build(build_context, staged_context, settings, extra)
|
|
81
80
|
end
|
|
82
81
|
ensure
|
|
83
82
|
progress&.finish
|
|
@@ -248,10 +247,12 @@ module Wip
|
|
|
248
247
|
execute(exec_target(command, interactive: interactive), interactive: tty?(interactive))
|
|
249
248
|
end
|
|
250
249
|
|
|
251
|
-
def execute(command, interactive: false, exit_on_failure: true)
|
|
250
|
+
def execute(command, interactive: false, exit_on_failure: true, chdir: nil)
|
|
252
251
|
runner = CommandRunner.new(debug: debug?)
|
|
252
|
+
run_options = { interactive: interactive }
|
|
253
|
+
run_options[:chdir] = chdir if chdir
|
|
253
254
|
code = reporter.step("running: #{CommandDisplay.for_debug(command)}", live: !interactive) do
|
|
254
|
-
runner.run(command,
|
|
255
|
+
runner.run(command, **run_options)
|
|
255
256
|
end
|
|
256
257
|
exit(code) if exit_on_failure && !code.zero?
|
|
257
258
|
code
|
|
@@ -322,15 +323,23 @@ module Wip
|
|
|
322
323
|
extra = compose_build_extra(spec)
|
|
323
324
|
warn "wip: building service '#{name}' (tag: #{spec['tag']}) from #{spec['context']}"
|
|
324
325
|
progress = StagingProgress.new
|
|
325
|
-
BuildContext.new(spec['context']
|
|
326
|
+
build_context = BuildContext.new(spec['context'], shadow_root: spec['shadow_context'])
|
|
327
|
+
build_context.stage(on_progress: progress.method(:tick)) do |staged_context|
|
|
326
328
|
progress.finish
|
|
327
|
-
|
|
328
|
-
execute(builder.build(settings: settings, extra: extra), interactive: tty?(true))
|
|
329
|
+
run_staged_build(build_context, staged_context, { 'tag' => spec['tag'] }, extra)
|
|
329
330
|
end
|
|
330
331
|
ensure
|
|
331
332
|
progress&.finish
|
|
332
333
|
end
|
|
333
334
|
|
|
335
|
+
# wslc build crashes (ERROR_UNHANDLED_EXCEPTION) when handed an absolute
|
|
336
|
+
# context path; running from inside the context and passing "." avoids it.
|
|
337
|
+
def run_staged_build(build_context, staged_context, settings, extra)
|
|
338
|
+
warn "wip: using shadow build context at #{staged_context}" if build_context.shadow?
|
|
339
|
+
built = builder.build(settings: settings.merge('context' => '.'), extra: extra)
|
|
340
|
+
execute(built, interactive: tty?(true), chdir: staged_context)
|
|
341
|
+
end
|
|
342
|
+
|
|
334
343
|
def build_extra_options(extra)
|
|
335
344
|
extra = extra.dup
|
|
336
345
|
extra.shift if extra.first == '--'
|
|
@@ -356,7 +365,9 @@ module Wip
|
|
|
356
365
|
|
|
357
366
|
Dir.mktmpdir('wip-sync-build-') do |dir|
|
|
358
367
|
File.write(File.join(dir, 'Dockerfile'), settings.build['dockerfile'])
|
|
359
|
-
|
|
368
|
+
# wslc build crashes (ERROR_UNHANDLED_EXCEPTION) when handed an absolute
|
|
369
|
+
# context path; running from inside the context and passing "." avoids it.
|
|
370
|
+
execute(builder.sync_build('.'), chdir: dir)
|
|
360
371
|
end
|
|
361
372
|
end
|
|
362
373
|
|
data/lib/wip/command_runner.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'open3'
|
|
4
|
+
require 'pty'
|
|
5
|
+
require 'io/console'
|
|
4
6
|
|
|
5
7
|
module Wip
|
|
6
8
|
# Executes a built command, pumping its I/O and returning the exit status.
|
|
@@ -14,13 +16,14 @@ module Wip
|
|
|
14
16
|
@debug = debug
|
|
15
17
|
end
|
|
16
18
|
|
|
17
|
-
def run(command, env: {}, interactive: false)
|
|
19
|
+
def run(command, env: {}, interactive: false, chdir: nil)
|
|
18
20
|
@stderr.puts "+ #{CommandDisplay.for_debug(command)}" if @debug
|
|
19
|
-
return run_attached(command, env) if interactive
|
|
21
|
+
return run_attached(command, env, chdir) if interactive
|
|
20
22
|
|
|
23
|
+
opts = chdir ? { chdir: chdir } : {}
|
|
21
24
|
captured = +''
|
|
22
25
|
status = nil
|
|
23
|
-
Open3.popen3(env, *command) do |input, output, error, wait|
|
|
26
|
+
Open3.popen3(env, *command, opts) do |input, output, error, wait|
|
|
24
27
|
input.close
|
|
25
28
|
threads = [pump(output, @stdout, captured), pump(error, @stderr, captured)]
|
|
26
29
|
threads.each(&:join)
|
|
@@ -52,15 +55,84 @@ module Wip
|
|
|
52
55
|
|
|
53
56
|
# Piping stdin/stdout/stderr (as `run` does above) closes the child's
|
|
54
57
|
# stdin immediately, which breaks anything that reads from the terminal
|
|
55
|
-
# (a shell, `rails console`, ...).
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
# (a shell, `rails console`, ...). Run it behind a pseudo-terminal instead
|
|
59
|
+
# of inheriting wip's real fds directly: a pty still gives the child a
|
|
60
|
+
# genuine controlling terminal (job control, Ctrl-C -> SIGINT, isatty-gated
|
|
61
|
+
# rendering all work the same as direct inheritance), but routes output
|
|
62
|
+
# through wip first, so report_hint can still see it — inherited fds go
|
|
63
|
+
# straight to the terminal and wip never would. wip's own terminal is
|
|
64
|
+
# switched to raw mode for the duration so only the pty's line discipline
|
|
65
|
+
# echoes input; without that, every keystroke would echo twice.
|
|
66
|
+
def run_attached(command, env, chdir)
|
|
67
|
+
opts = chdir ? { chdir: chdir } : {}
|
|
68
|
+
captured = +''
|
|
69
|
+
status = nil
|
|
70
|
+
PTY.spawn(env, *command, opts) do |output, input, pid|
|
|
71
|
+
sync_winsize(output)
|
|
72
|
+
with_winsize_sync(output) { with_raw_stdin { pump_attached(output, input, captured) } }
|
|
73
|
+
_, status = Process.wait2(pid)
|
|
74
|
+
end
|
|
75
|
+
report_hint(captured) unless status.success?
|
|
60
76
|
exitstatus(status)
|
|
61
77
|
rescue Errno::ENOENT => e
|
|
62
78
|
@stderr.puts e.message
|
|
63
79
|
127
|
|
80
|
+
rescue Interrupt
|
|
81
|
+
@stderr.puts "\nwip: interrupted"
|
|
82
|
+
130
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def pump_attached(output, input, captured)
|
|
86
|
+
stdin_thread = forward_stdin(input)
|
|
87
|
+
loop do
|
|
88
|
+
chunk = output.readpartial(4096)
|
|
89
|
+
@stdout.write(chunk)
|
|
90
|
+
captured << chunk
|
|
91
|
+
end
|
|
92
|
+
rescue Errno::EIO, IOError
|
|
93
|
+
# the pty's slave side closed when the child exited
|
|
94
|
+
ensure
|
|
95
|
+
stdin_thread.kill
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Keeps the child's pty sized to wip's real terminal so full-screen
|
|
99
|
+
# programs (an editor, `less`, ...) render correctly. A non-tty @stdout
|
|
100
|
+
# (piped output, tests) has no size to read, so the pty keeps its default.
|
|
101
|
+
def sync_winsize(output)
|
|
102
|
+
output.winsize = @stdout.winsize if @stdout.respond_to?(:winsize) && @stdout.tty?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# The child's pty only gets the terminal size wip had at spawn time
|
|
106
|
+
# (sync_winsize, above) — it's a separate pty from wip's own real one, so
|
|
107
|
+
# later resizes of wip's terminal don't reach it on their own. Trap
|
|
108
|
+
# SIGWINCH for the duration to re-sync it live, restoring whatever handler
|
|
109
|
+
# was already installed (if any) once the command finishes.
|
|
110
|
+
def with_winsize_sync(output)
|
|
111
|
+
return yield unless @stdout.respond_to?(:winsize) && @stdout.tty?
|
|
112
|
+
|
|
113
|
+
previous = Signal.trap('WINCH') { sync_winsize(output) }
|
|
114
|
+
yield
|
|
115
|
+
ensure
|
|
116
|
+
Signal.trap('WINCH', previous) if previous
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# A non-tty @stdin (piped input, tests) has no raw mode to switch to, so
|
|
120
|
+
# it's forwarded to the child as-is.
|
|
121
|
+
def with_raw_stdin(&)
|
|
122
|
+
return yield unless @stdin.respond_to?(:raw) && @stdin.tty?
|
|
123
|
+
|
|
124
|
+
@stdin.raw(&)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def forward_stdin(input)
|
|
128
|
+
Thread.new do
|
|
129
|
+
loop do
|
|
130
|
+
chunk = @stdin.readpartial(4096)
|
|
131
|
+
input.write(chunk)
|
|
132
|
+
end
|
|
133
|
+
rescue IOError, Errno::EIO, Errno::EBADF
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
64
136
|
end
|
|
65
137
|
|
|
66
138
|
# An Interrupt during the main thread's `join` closes these pipes out from
|
data/lib/wip/compose_file.rb
CHANGED
|
@@ -24,7 +24,7 @@ module Wip
|
|
|
24
24
|
# service already shares one project network (config.rb); and `wslc run`/`exec` has no
|
|
25
25
|
# restart-policy or capability flag to forward `restart:`/`cap_add:` to.
|
|
26
26
|
IGNORED_SERVICE_KEYS = %w[tty stdin_open networks restart cap_add].freeze
|
|
27
|
-
BUILD_KEYS = %w[context dockerfile args].freeze
|
|
27
|
+
BUILD_KEYS = %w[context dockerfile args shadow_context].freeze
|
|
28
28
|
SUPPORTED_CONDITIONS = %w[service_started].freeze
|
|
29
29
|
LIST_HINT = 'only supports short syntax ("host:container"), not long-syntax mappings'
|
|
30
30
|
|
|
@@ -140,9 +140,11 @@ module Wip
|
|
|
140
140
|
unless unknown.empty?
|
|
141
141
|
|
|
142
142
|
context = resolve_context(presence(value['context']) || '.')
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
# Kept relative to context (not resolved against it) so `-f` still finds it once
|
|
144
|
+
# `wip up`/`wip build` chdir into a staged or shadowed copy of that context.
|
|
145
|
+
{ 'context' => context, 'dockerfile' => presence(value['dockerfile']),
|
|
146
|
+
'args' => normalize_kv(name, value['args'], 'build.args'),
|
|
147
|
+
'shadow_context' => presence(value['shadow_context']) }.compact
|
|
146
148
|
end
|
|
147
149
|
|
|
148
150
|
# build.context is relative to compose.yml's own directory (Compose's own rule),
|
data/lib/wip/version.rb
CHANGED