zui 0.0.7 → 0.0.8

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: 37391c87c1e030ef5af552a78b1f80fa66a7a85dafefc55be4151ba74d95c2f2
4
- data.tar.gz: 86dda365fcb01ee72a10cd6ddb19937d09356f082bb8e19b7d18fe994089e8d6
3
+ metadata.gz: ea2305d12a3ec2d53ad0edb93e6b87e3933b3ba84df4a694e08218598e9fc207
4
+ data.tar.gz: 4dd9ab11ce4db9e34f29f22e1c3831f28e1efbba1f8d33015e093a504f05be08
5
5
  SHA512:
6
- metadata.gz: 8d990b363f659195f29ff88fb7353521715d0ec98c56f86f0bf63269793f13c07836ad27119b56583c0b50e78fc9ed09bd5a75bee6f4d12f44cd8c30237ca811
7
- data.tar.gz: 5d2caf9dcdf55debaf65dba86f2fbe841e84ddc28bee47cab13779a63568cbf197c23d86a5031cb0a854ec1424681abbf286fbaa758eda48dfbeddc021978fd3
6
+ metadata.gz: ed270c4a23c21f39f84659bdbdfcdc3ec20bf498b2199a19926d2a9105a95d696aec76802183a1fd5037878ee732043a81827a6e865991cf0861c8df9f7776f7
7
+ data.tar.gz: 900bd4d8baca403284a9fabd3bc29c2bd263542cdd60184f2df9f1b117706d378bc0db5698628545abdbcbedbfd769ba4fe4b5947c27cda4c242a21c21b7adbe
data/README.md CHANGED
@@ -56,8 +56,8 @@ a window.
56
56
 
57
57
  ## Quick start
58
58
 
59
- Install Ruby 3.1 or newer and the gem. Zui downloads its version-matched native client only when
60
- you explicitly configure it:
59
+ Install Ruby 3.1 or newer and the gem. Zui downloads its version-matched native client and small
60
+ mruby bundle runtime only when you explicitly configure it:
61
61
 
62
62
  ```bash
63
63
  gem install zui
@@ -70,9 +70,9 @@ zui run main.rb
70
70
  That is the complete development setup. You do not need a Qt SDK, CMake, a C++ compiler, or a
71
71
  system-wide Qt installation.
72
72
 
73
- `zui doctor --fix` downloads the native client for the installed Zui version, verifies its
74
- SHA-256 checksum and manifest, and activates it atomically in the user cache. It does not modify
75
- shell startup files or global Qt environment variables.
73
+ `zui doctor --fix` downloads the native client and lite mruby runtime for the installed Zui
74
+ version, verifies their SHA-256 checksums and manifests, and activates them atomically in the user
75
+ cache. It does not modify shell startup files or global Qt environment variables.
76
76
 
77
77
  ## Your first application
78
78
 
@@ -178,10 +178,9 @@ matching native client.
178
178
  | macOS | Intel x86-64 | `zui-client-macos-x86_64` | Standard `.app` bundle | Supported and CI verified |
179
179
  | Windows | x86-64 | `zui-client-windows-x86_64` | Portable application directory | Supported and CI verified |
180
180
 
181
- The application bundle expects Ruby 3.1 or newer on the destination. Its launcher prefers
182
- `ZUI_RUBY`, then the Ruby executable that created the bundle when that path is still available,
183
- and finally a `ruby` executable on `PATH`. Installers that carry a private Ruby can point
184
- `ZUI_RUBY` at that executable.
181
+ Application bundles do not require Ruby on the destination. The default `--lite` mode embeds Zui's
182
+ versioned mruby runtime. `--full` embeds a private CRuby plus only the non-Zui gems resolved by the
183
+ project's `Gemfile.lock`.
185
184
 
186
185
  Unsupported architectures fail explicitly during configuration. Zui never silently compiles Qt,
187
186
  uses a system Qt installation, or downloads an asset for a different platform. See the complete
@@ -189,7 +188,7 @@ uses a system Qt installation, or downloads an asset for a different platform. S
189
188
 
190
189
  ## Component catalog
191
190
 
192
- Zui 0.0.7 registers **241 built-in components**. Every entry has a named Ruby builder method,
191
+ Zui 0.0.8 registers **241 built-in components**. Every entry has a named Ruby builder method,
193
192
  property and event schema, native QML renderer, reactive patch support, and contract tests.
194
193
 
195
194
  | Category | Count | Representative components |
@@ -218,12 +217,13 @@ an explicit error.
218
217
 
219
218
  | Command | Purpose |
220
219
  | --- | --- |
221
- | `zui new NAME` | Generate a pure-Ruby application with a reusable UI module and tests |
220
+ | `zui new NAME` | Generate a pure-Ruby application, `Gemfile`, distribution config, and reusable UI module |
222
221
  | `zui doctor` | Report Ruby, platform, native-client, run, and bundle readiness without changing anything |
223
- | `zui doctor --fix` | Download, verify, and install the missing versioned native client |
224
- | `zui configure` | Perform the same explicit native-client installation directly |
222
+ | `zui doctor --fix` | Download, verify, and install the missing native client and lite mruby runtime |
223
+ | `zui configure` | Perform the same explicit runtime installation directly |
225
224
  | `zui run FILE` | Launch a Ruby entry point through the private native client |
226
- | `zui bundle [DIRECTORY]` | Tree-shake and assemble the application, Zui framework, and native runtime for the current OS |
225
+ | `zui bundle [DIRECTORY]` | Build the default standalone `--lite` bundle with embedded mruby |
226
+ | `zui bundle --full [DIRECTORY]` | Embed private CRuby and only the gems locked by the project |
227
227
  | `zui bundle --name NAME --output PATH` | Override the generated product name and destination |
228
228
  | `zui bundle --no-tree-shake` | Retain the complete component and Qt feature catalog for metaprogrammed applications |
229
229
  | `zui bundle --dist [DIRECTORY]` | Build release installers from the required project-root `config.rb` |
@@ -284,11 +284,12 @@ zui bundle
284
284
  zui bundle path/to/application --name "Telemetry Console"
285
285
  ```
286
286
 
287
- Every distribution combines three deliberately separate payloads:
287
+ Every distribution combines four deliberately separate payloads:
288
288
 
289
289
  ```text
290
290
  application Ruby source and assets
291
291
  + Zui Ruby/QML framework runtime
292
+ + selected private Ruby runtime (`mruby` or `cruby`)
292
293
  + configured native Qt/QML client
293
294
  ```
294
295
 
@@ -298,8 +299,14 @@ application Ruby source and assets
298
299
  | macOS | Standard `.app` directory | Sign, notarize, and distribute with the application's release identity |
299
300
  | Windows | Self-contained directory with `run.cmd` | MSIX, MSI, WiX, Inno Setup, or another installer |
300
301
 
301
- No system Qt installation is used by the finished bundle. Signing, notarization, installer format,
302
- store submission, and application identity remain the release owner's responsibility.
302
+ No system Ruby or Qt installation is used by the finished bundle. Signing, notarization, installer
303
+ format, store submission, and application identity remain the release owner's responsibility.
304
+
305
+ `--lite` is the default. It compiles the project's local Ruby source into one mruby-compatible
306
+ program and rejects external gem `require` calls with a `--full` hint. Use `--full` for ordinary
307
+ CRuby behavior or third-party gems. Full bundles require a locked project `Gemfile`; run
308
+ `bundle install` after changing dependencies. Both modes are built for the current target OS and
309
+ architecture, and both use the same project-specific QML/native tree-shaking pass.
303
310
 
304
311
  `zui bundle` statically analyzes every production Ruby source file, including code in conditional
305
312
  branches, and retains only the referenced Zui adapters, QML modules, plugins, and native library
@@ -354,10 +361,9 @@ directory, and existing artifacts are never overwritten. Linux RPM creation requ
354
361
  (`rpm-build` or `rpm-tools`), macOS uses the system `hdiutil`, and Windows requires Inno Setup 6's
355
362
  `ISCC.exe` on `PATH`.
356
363
 
357
- The installers carry the application, Zui, Qt, and the selected native dependencies. Like the
358
- directory bundle, Ruby 3.1 or newer is currently required on the destination; DEB and RPM metadata
359
- declare that dependency. Code signing, Apple notarization, and Windows Authenticode signing remain
360
- release-owner steps.
364
+ The installers carry the application, selected Ruby runtime, Zui, Qt, and selected native
365
+ dependencies. They do not declare or require system Ruby. Code signing, Apple notarization, and
366
+ Windows Authenticode signing remain release-owner steps.
361
367
 
362
368
  ## Showcase applications
363
369
 
@@ -412,19 +418,21 @@ Build and install the gem directly:
412
418
 
413
419
  ```bash
414
420
  gem build zui.gemspec
415
- gem install ./zui-0.0.7.gem
421
+ gem install ./zui-*.gem
416
422
  ```
417
423
 
418
- Release CI additionally constructs and audits a relocatable native client, installs the built gem,
419
- repairs a fresh client cache, and launches a real application on every supported target.
424
+ Release CI additionally builds the pinned mruby runtime, audits both release archives, installs the
425
+ built gem, repairs a fresh cache, and launches real `--lite` and `--full` bundles on every supported
426
+ target.
420
427
 
421
428
  ## Troubleshooting
422
429
 
423
430
  | Symptom | Resolution |
424
431
  | --- | --- |
425
432
  | `Client: not configured` | Run `zui doctor --fix` once for the installed Zui version |
433
+ | `Lite runtime: not configured` | Run `zui doctor --fix` to install the checksummed mruby archive |
426
434
  | Platform or architecture is unsupported | Use one of the release-gated targets above or add a verified native-client runner and artifact |
427
- | A bundle cannot find Ruby | Install Ruby 3.1+ or point `ZUI_RUBY` at an application-private Ruby executable |
435
+ | `--lite` rejects a gem `require` | Use `--full` and lock the gem in the project `Gemfile.lock` |
428
436
  | A component reports a resource or module error | Check the declared asset path and run `zui doctor`; Zui will not substitute another component |
429
437
  | Audio, camera, or capture is unavailable | Confirm OS permissions, devices, codecs, and platform media services |
430
438
  | The native client looks stale after a framework update | Run `zui doctor --fix`; client caches are isolated by Zui version |
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Zui
6
+ class ApplicationRuntime
7
+ FORMAT = 1
8
+
9
+ attr_reader :engine, :version, :executable, :environment, :gems, :program, :load_path
10
+
11
+ def initialize(engine:, version:, executable:, environment: {}, gems: [], program: nil, load_path: nil)
12
+ @engine = engine.to_s
13
+ @version = version.to_s
14
+ @executable = executable.to_s
15
+ @environment = environment.transform_keys(&:to_s).transform_values do |paths|
16
+ Array(paths).map(&:to_s).freeze
17
+ end.freeze
18
+ @gems = Array(gems).map(&:to_s).sort.freeze
19
+ @program = program&.to_s
20
+ @load_path = load_path&.to_s
21
+ freeze
22
+ end
23
+
24
+ def write(directory)
25
+ File.write(File.join(directory, "runtime.json"), "#{JSON.pretty_generate(to_h)}\n")
26
+ self
27
+ end
28
+
29
+ def to_h
30
+ result = {
31
+ "format" => FORMAT,
32
+ "engine" => engine,
33
+ "version" => version,
34
+ "executable" => executable,
35
+ "environment" => environment,
36
+ "gems" => gems
37
+ }
38
+ result["program"] = program unless program.nil?
39
+ result["load_path"] = load_path unless load_path.nil?
40
+ result
41
+ end
42
+ end
43
+ end
data/lib/zui/cli.rb CHANGED
@@ -5,7 +5,7 @@ require "rbconfig"
5
5
 
6
6
  module Zui
7
7
  class CLI
8
- USAGE = "Usage: zui <new NAME|configure|doctor [--fix]|run FILE|bundle [--dist] [--no-tree-shake] [DIRECTORY]|version>"
8
+ USAGE = "Usage: zui <new NAME|configure|doctor [--fix]|run FILE|bundle [--dist] [--lite|--full] [--no-tree-shake] [DIRECTORY]|version>"
9
9
 
10
10
  def self.run(arguments, out: $stdout, err: $stderr)
11
11
  new(out:, err:).run(arguments.dup)
@@ -58,20 +58,24 @@ module Zui
58
58
  destination = option_value(arguments, "--output")
59
59
  create_installers = !arguments.delete("--dist").nil?
60
60
  tree_shake = arguments.delete("--no-tree-shake").nil?
61
+ lite = !arguments.delete("--lite").nil?
62
+ full = !arguments.delete("--full").nil?
63
+ raise ArgumentError, "bundle accepts only one of --lite or --full" if lite && full
64
+ runtime_mode = full ? :full : :lite
61
65
  source = File.expand_path(arguments.shift || Dir.pwd)
62
66
  raise ArgumentError, "bundle accepts one directory" unless arguments.empty?
63
67
 
64
68
  if create_installers
65
69
  raise ArgumentError, "--name cannot be used with --dist; set name in config.rb" if name
66
70
 
67
- packager = DistPackager.new(tree_shake:)
71
+ packager = DistPackager.new(tree_shake:, runtime_mode:)
68
72
  paths = packager.package(source, output: destination)
69
73
  paths.each { |path| @out.puts("Created distribution artifact #{path}") }
70
74
  report_tree_shaking(packager.tree_shake_report)
71
75
  return 0
72
76
  end
73
77
 
74
- distribution = Distribution.new(tree_shake:)
78
+ distribution = Distribution.new(tree_shake:, runtime_mode:)
75
79
  path = distribution.bundle(source, name:, destination:)
76
80
  @out.puts("Bundled #{Platform.current.os} application in #{path}")
77
81
  report_tree_shaking(distribution.tree_shake_report)
@@ -94,17 +98,27 @@ module Zui
94
98
  return 1 unless platform.supported?
95
99
 
96
100
  client = Client.new(platform:)
97
- if client.configured?
98
- report_ready(client)
101
+ lite_runtime = LiteRuntime.new(platform:)
102
+ client_ready = client.configured?
103
+ lite_ready = lite_runtime.configured?
104
+ if client_ready && lite_ready
105
+ report_ready(client, lite_runtime)
99
106
  0
100
107
  elsif fix
101
- @out.puts("Repair: downloading the verified native client from GitHub Releases...")
102
- client.configure!
103
- report_ready(client)
108
+ unless client_ready
109
+ @out.puts("Repair: downloading the verified native client from GitHub Releases...")
110
+ client.configure!
111
+ end
112
+ unless lite_ready
113
+ @out.puts("Repair: downloading the verified lite mruby runtime from GitHub Releases...")
114
+ lite_runtime.configure!
115
+ end
116
+ report_ready(client, lite_runtime)
104
117
  0
105
118
  else
106
- @out.puts("Client: not configured")
107
- @out.puts("Run `zui doctor --fix` to install the native client and bundle support.")
119
+ @out.puts(client_ready ? "Client: #{client.root}" : "Client: not configured")
120
+ @out.puts(lite_ready ? "Lite runtime: #{lite_runtime.root}" : "Lite runtime: not configured")
121
+ @out.puts("Run `zui doctor --fix` to install the native client and lite bundle runtime.")
108
122
  1
109
123
  end
110
124
  end
@@ -113,16 +127,20 @@ module Zui
113
127
  raise ArgumentError, "configure accepts no arguments" unless arguments.empty?
114
128
  platform = Platform.current.assert_supported!
115
129
  client = Client.new(platform:)
130
+ lite_runtime = LiteRuntime.new(platform:)
116
131
  @out.puts("Configuring Zui #{VERSION} for #{platform.id}...")
117
132
  client.configure!
118
- report_ready(client)
133
+ lite_runtime.configure!
134
+ report_ready(client, lite_runtime)
119
135
  0
120
136
  end
121
137
 
122
- def report_ready(client)
138
+ def report_ready(client, lite_runtime)
123
139
  @out.puts("Client: #{client.root}")
140
+ @out.puts("Lite runtime: #{lite_runtime.root}")
124
141
  @out.puts("Run: ready")
125
- @out.puts("Bundle: ready")
142
+ @out.puts("Bundle --lite: ready")
143
+ @out.puts("Bundle --full: ready (uses this Ruby and the project's locked gems)")
126
144
  end
127
145
 
128
146
  def option_value(arguments, name)
@@ -1,7 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zui
4
- Component = Struct.new(:name, :qml, :properties, :events, :property_map, :event_map, :container, :auto_bind, keyword_init: true) do
4
+ class Component
5
+ attr_reader :name, :qml, :properties, :events, :property_map, :event_map, :container, :auto_bind
6
+
7
+ def initialize(name:, qml:, properties:, events:, property_map:, event_map:, container:, auto_bind:)
8
+ @name = name
9
+ @qml = qml
10
+ @properties = properties
11
+ @events = events
12
+ @property_map = property_map
13
+ @event_map = event_map
14
+ @container = container
15
+ @auto_bind = auto_bind
16
+ end
17
+
5
18
  def to_h
6
19
  {
7
20
  "qml" => qml,
@@ -9,16 +9,22 @@ require "zlib"
9
9
 
10
10
  module Zui
11
11
  class DistPackager
12
- attr_reader :platform, :config, :tree_shake_report
12
+ attr_reader :platform, :config, :tree_shake_report, :runtime_mode
13
13
 
14
14
  def initialize(client: nil, platform: Platform.current, framework_root: FRAMEWORK_ROOT,
15
- ruby: RbConfig.ruby, tree_shake: true, environment: ENV)
15
+ ruby: RbConfig.ruby, tree_shake: true, environment: ENV, runtime_mode: :lite,
16
+ runtime_builder: nil)
16
17
  @platform = platform.assert_supported!
17
18
  @client = client || Client.new(platform: @platform)
18
19
  @framework_root = framework_root
19
20
  @ruby = File.expand_path(ruby)
20
21
  @tree_shake = tree_shake == true
22
+ @runtime_mode = runtime_mode.to_sym
23
+ unless Distribution::RUNTIME_MODES.include?(@runtime_mode)
24
+ raise ArgumentError, "unsupported application runtime: #{runtime_mode}"
25
+ end
21
26
  @environment = environment.to_h
27
+ @runtime_builder = runtime_builder
22
28
  @config = nil
23
29
  @tree_shake_report = nil
24
30
  end
@@ -62,7 +68,8 @@ module Zui
62
68
  end
63
69
  distribution = Distribution.new(
64
70
  client: @client, platform:, framework_root: @framework_root, ruby: @ruby,
65
- tree_shake: @tree_shake, release_config: config
71
+ tree_shake: @tree_shake, release_config: config, runtime_mode:,
72
+ runtime_builder: @runtime_builder
66
73
  )
67
74
  distribution.bundle(project, name: config.name, destination:)
68
75
  @tree_shake_report = distribution.tree_shake_report
@@ -164,7 +171,6 @@ module Zui
164
171
  "Architecture: #{deb_architecture}",
165
172
  "Maintainer: #{config.publisher}",
166
173
  "Installed-Size: #{installed_size}",
167
- "Depends: ruby (>= 3.1)",
168
174
  "Description: #{config.description}"
169
175
  ]
170
176
  values.insert(-2, "Homepage: #{config.homepage}") if config.homepage
@@ -196,7 +202,6 @@ module Zui
196
202
  License: #{rpm_escape(config.license)}
197
203
  #{homepage}BuildArch: #{rpm_architecture}
198
204
  AutoReqProv: no
199
- Requires: ruby >= 3.1
200
205
 
201
206
  %description
202
207
  #{description}
@@ -6,17 +6,26 @@ require "rbconfig"
6
6
 
7
7
  module Zui
8
8
  class Distribution
9
- attr_reader :platform, :tree_shake_report
9
+ RUNTIME_MODES = %i[lite full].freeze
10
+
11
+ attr_reader :platform, :tree_shake_report, :runtime_mode
10
12
 
11
13
  def initialize(client: nil, platform: Platform.current, framework_root: FRAMEWORK_ROOT,
12
- ruby: RbConfig.ruby, tree_shake: true, release_config: nil)
14
+ ruby: RbConfig.ruby, tree_shake: true, release_config: nil, runtime_mode: :lite,
15
+ runtime_builder: nil)
13
16
  @platform = platform.assert_supported!
14
17
  @client = client || Client.new(platform: @platform)
15
18
  @framework_root = framework_root
16
19
  @ruby = File.expand_path(ruby)
17
20
  @tree_shake = tree_shake == true
21
+ @runtime_mode = runtime_mode.to_sym
22
+ unless RUNTIME_MODES.include?(@runtime_mode)
23
+ raise ArgumentError, "unsupported application runtime: #{runtime_mode}"
24
+ end
18
25
  @tree_shake_report = nil
19
26
  @release_config = release_config
27
+ @runtime_builder = runtime_builder
28
+ @application_runtime = nil
20
29
  end
21
30
 
22
31
  def bundle(source, name: nil, destination: nil)
@@ -52,6 +61,7 @@ module Zui
52
61
  File.join(destination, "runtime"), File.join(destination, "share", "applications")])
53
62
  install_application(project, File.join(destination, "app"))
54
63
  install_runtime(File.join(destination, "runtime"))
64
+ install_application_runtime(project, File.join(destination, "runtime"))
55
65
  @client.copy_to(File.join(destination, "runtime", "native"))
56
66
  shake_bundle(project, File.join(destination, "runtime"))
57
67
  write_linux_launcher(destination, app_name)
@@ -74,6 +84,7 @@ module Zui
74
84
  FileUtils.mkdir_p([macos, File.join(resources, "app"), File.join(resources, "runtime")])
75
85
  install_application(project, File.join(resources, "app"))
76
86
  install_runtime(File.join(resources, "runtime"))
87
+ install_application_runtime(project, File.join(resources, "runtime"))
77
88
  @client.copy_to(File.join(resources, "runtime", "native"))
78
89
  shake_bundle(project, File.join(resources, "runtime"))
79
90
  icon_name = install_macos_release_icon(resources)
@@ -88,11 +99,16 @@ module Zui
88
99
  File.join(destination, "runtime")])
89
100
  install_application(project, File.join(destination, "app"))
90
101
  install_runtime(File.join(destination, "runtime"))
102
+ install_application_runtime(project, File.join(destination, "runtime"))
91
103
  @client.copy_to(File.join(destination, "runtime", "native"))
92
104
  shake_bundle(project, File.join(destination, "runtime"))
93
105
  install_windows_release_icon(destination)
94
- File.write(File.join(destination, "run.rb"), windows_ruby_launcher(app_name))
95
- File.write(File.join(destination, "run.cmd"), windows_command_launcher)
106
+ if @application_runtime
107
+ File.write(File.join(destination, "run.cmd"), windows_private_runtime_launcher(app_name))
108
+ else
109
+ File.write(File.join(destination, "run.rb"), windows_ruby_launcher(app_name))
110
+ File.write(File.join(destination, "run.cmd"), windows_command_launcher)
111
+ end
96
112
  write_manifest(destination, app_name)
97
113
  end
98
114
 
@@ -116,6 +132,15 @@ module Zui
116
132
  FileUtils.cp_r(entries.map { |entry| File.join(source, entry) }, target) unless entries.empty?
117
133
  end
118
134
 
135
+ def install_application_runtime(project, destination)
136
+ builder = @runtime_builder || if runtime_mode == :full
137
+ FullRuntime.new(platform:, ruby: @ruby)
138
+ else
139
+ LiteRuntime.new(platform:)
140
+ end
141
+ @application_runtime = builder.install(project:, destination: File.join(destination, "ruby"))
142
+ end
143
+
119
144
  def write_linux_launcher(destination, app_name)
120
145
  File.write(File.join(destination, "run"), <<~SH)
121
146
  #!/bin/sh
@@ -127,9 +152,9 @@ module Zui
127
152
  exec "$native_dir/#{@client.executable_relative_path}" \
128
153
  --qml-root "$bundle_dir/runtime/qml" \
129
154
  --project "$bundle_dir/app" \
130
- --program "$bundle_dir/app/main.rb" \
155
+ --program #{posix_program("$bundle_dir")} \
131
156
  --ruby "$ruby_command" \
132
- --load-path "$bundle_dir/runtime/lib" \
157
+ --load-path #{posix_load_path("$bundle_dir")} \
133
158
  --name #{shell_quote(app_name)}
134
159
  SH
135
160
  FileUtils.chmod(0o755, File.join(destination, "run"))
@@ -147,9 +172,9 @@ module Zui
147
172
  exec "$native_dir/#{@client.executable_relative_path}" \
148
173
  --qml-root "$resources/runtime/qml" \
149
174
  --project "$resources/app" \
150
- --program "$resources/app/main.rb" \
175
+ --program #{posix_program("$resources")} \
151
176
  --ruby "$ruby_command" \
152
- --load-path "$resources/runtime/lib" \
177
+ --load-path #{posix_load_path("$resources")} \
153
178
  --name #{shell_quote(app_name)}
154
179
  SH
155
180
  end
@@ -190,6 +215,35 @@ module Zui
190
215
  CMD
191
216
  end
192
217
 
218
+ def windows_private_runtime_launcher(app_name)
219
+ executable = @application_runtime.executable.tr("/", "\\")
220
+ host = @client.executable_relative_path.tr("/", "\\")
221
+ environment = @application_runtime.environment.map do |name, paths|
222
+ value = paths.map { |path| "%ruby_root%\\#{path.tr('/', '\\')}" }.join(";")
223
+ if name == "PATH"
224
+ %(set "PATH=#{value};%PATH%")
225
+ else
226
+ %(set "#{name}=#{value}")
227
+ end
228
+ end
229
+ environment.unshift(%(set "PATH=%ruby_root%\\bin;%ruby_root%\\lib;%PATH%"))
230
+ <<~CMD.gsub("\n", "\r\n")
231
+ @echo off
232
+ setlocal
233
+ set "bundle_dir=%~dp0"
234
+ set "ruby_root=%bundle_dir%runtime\\ruby"
235
+ #{environment.join("\n")}
236
+ "%bundle_dir%runtime\\native\\#{host}" ^
237
+ --qml-root "%bundle_dir%runtime\\qml" ^
238
+ --project "%bundle_dir%app" ^
239
+ --program #{windows_program} ^
240
+ --ruby "%ruby_root%\\#{executable}" ^
241
+ --load-path #{windows_load_path} ^
242
+ --name #{windows_quote(app_name)}
243
+ exit /b %ERRORLEVEL%
244
+ CMD
245
+ end
246
+
193
247
  def info_plist(app_name, icon_name: nil)
194
248
  identifier = @release_config&.identifier || "dev.zui.#{slug(app_name).tr("-", ".")}"
195
249
  application_version = @release_config&.version || VERSION
@@ -215,7 +269,7 @@ module Zui
215
269
  "format" => 1, "framework" => "zui", "version" => VERSION,
216
270
  "platform" => platform.os.to_s, "architecture" => platform.arch.to_s,
217
271
  "name" => app_name, "client_version" => @client.manifest.fetch("client_version"),
218
- "tree_shaken" => !@tree_shake_report.nil?
272
+ "tree_shaken" => !@tree_shake_report.nil?, "ruby_runtime" => runtime_mode.to_s
219
273
  }
220
274
  manifest["tree_shake"] = @tree_shake_report.to_h if @tree_shake_report
221
275
  if @release_config
@@ -256,6 +310,7 @@ module Zui
256
310
  def titleize(value) = value.split(/[-_]/).map(&:capitalize).join(" ")
257
311
  def slug(value) = value.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|-\z/, "")
258
312
  def shell_quote(value) = "'#{value.to_s.gsub("'", %q('"'"'))}'"
313
+ def windows_quote(value) = %Q("#{value.to_s.gsub('%', '%%').gsub('"', '""')}")
259
314
  def xml_escape(value) = value.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
260
315
 
261
316
  def posix_client_environment
@@ -270,6 +325,8 @@ module Zui
270
325
  end
271
326
 
272
327
  def posix_ruby_command
328
+ return posix_private_ruby_command if @application_runtime
329
+
273
330
  <<~SH.chomp
274
331
  ruby_command=${ZUI_RUBY:-}
275
332
  if [ -z "$ruby_command" ]; then
@@ -284,6 +341,53 @@ module Zui
284
341
  SH
285
342
  end
286
343
 
344
+ def posix_private_ruby_command
345
+ lines = ["ruby_root=\"$bundle_dir/runtime/ruby\""]
346
+ if platform.macos?
347
+ lines[0] = "ruby_root=\"$resources/runtime/ruby\""
348
+ end
349
+ @application_runtime.environment.each do |name, paths|
350
+ value = paths.map { |path| "${ruby_root}/#{path}" }.join(File::PATH_SEPARATOR)
351
+ if %w[LD_LIBRARY_PATH DYLD_LIBRARY_PATH].include?(name)
352
+ lines << "zui_environment=\"#{value}\""
353
+ lines << "[ -z \"${#{name}:-}\" ] || zui_environment=\"$zui_environment:${#{name}}\""
354
+ lines << "export #{name}=\"$zui_environment\""
355
+ else
356
+ lines << "export #{name}=\"#{value}\""
357
+ end
358
+ end
359
+ lines << "ruby_command=\"$ruby_root/#{@application_runtime.executable}\""
360
+ lines.join("\n")
361
+ end
362
+
363
+ def posix_program(bundle_root)
364
+ relative = @application_runtime&.program
365
+ relative ? %("#{bundle_root}/runtime/ruby/#{relative}") : %("#{bundle_root}/app/main.rb")
366
+ end
367
+
368
+ def posix_load_path(bundle_root)
369
+ relative = @application_runtime&.load_path
370
+ return %("#{bundle_root}/runtime/lib") if relative.nil?
371
+ return '""' if relative.empty?
372
+
373
+ %("#{bundle_root}/runtime/ruby/#{relative}")
374
+ end
375
+
376
+ def windows_program
377
+ relative = @application_runtime&.program
378
+ return '"%bundle_dir%app\\main.rb"' unless relative
379
+
380
+ %("%ruby_root%\\#{relative.tr('/', '\\')}")
381
+ end
382
+
383
+ def windows_load_path
384
+ relative = @application_runtime&.load_path
385
+ return '"%bundle_dir%runtime\\lib"' if relative.nil?
386
+ return '""' if relative.empty?
387
+
388
+ %("%ruby_root%\\#{relative.tr('/', '\\')}")
389
+ end
390
+
287
391
  def windows_client_environment = @client.environment_entries
288
392
 
289
393
  def windows_environment_builder
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler"
4
+ require "fileutils"
5
+ require "json"
6
+ require "rbconfig"
7
+ require "set"
8
+
9
+ module Zui
10
+ class FullRuntime
11
+ SYSTEM_LIBRARIES = /\A(?:ld-linux|libc\.|libdl\.|libgcc_s\.|libm\.|libpthread\.|librt\.|libSystem\.|libutil\.)/.freeze
12
+
13
+ attr_reader :platform
14
+
15
+ def initialize(platform: Platform.current, ruby: RbConfig.ruby, environment: ENV,
16
+ rbconfig: RbConfig::CONFIG, spec_loader: nil)
17
+ @platform = platform.assert_supported!
18
+ @ruby = File.expand_path(ruby)
19
+ @environment = environment.to_h
20
+ @rbconfig = rbconfig.to_h
21
+ @spec_loader = spec_loader || method(:project_specs)
22
+ end
23
+
24
+ def install(project:, destination:)
25
+ project = File.expand_path(project)
26
+ destination = File.expand_path(destination)
27
+ raise ArgumentError, "Ruby executable not found: #{@ruby}" unless File.file?(@ruby)
28
+ raise ArgumentError, "full runtime destination already exists: #{destination}" if File.exist?(destination)
29
+
30
+ FileUtils.mkdir_p(destination)
31
+ executable = install_executable(destination)
32
+ library_paths = install_standard_library(destination)
33
+ install_runtime_libraries(destination)
34
+ install_native_dependencies(destination)
35
+ gems = install_project_gems(project, destination)
36
+ environment = {
37
+ "RUBYLIB" => library_paths,
38
+ "GEM_HOME" => ["gems"],
39
+ "GEM_PATH" => ["gems"]
40
+ }
41
+ dynamic_library_variable = platform.macos? ? "DYLD_LIBRARY_PATH" : "LD_LIBRARY_PATH"
42
+ environment[dynamic_library_variable] = ["lib"] unless platform.windows?
43
+
44
+ ApplicationRuntime.new(
45
+ engine: "cruby",
46
+ version: ruby_version,
47
+ executable:,
48
+ environment:,
49
+ gems: gems.map(&:full_name)
50
+ ).write(destination)
51
+ rescue StandardError
52
+ FileUtils.remove_entry(destination) if destination && File.exist?(destination)
53
+ raise
54
+ end
55
+
56
+ private
57
+
58
+ def install_executable(destination)
59
+ name = platform.windows? ? "ruby.exe" : "ruby"
60
+ target = File.join(destination, "bin", name)
61
+ FileUtils.mkdir_p(File.dirname(target))
62
+ FileUtils.cp(@ruby, target)
63
+ FileUtils.chmod(0o755, target) unless platform.windows?
64
+ strip_binary(target)
65
+ File.join("bin", name).tr(File::SEPARATOR, "/")
66
+ end
67
+
68
+ def install_standard_library(destination)
69
+ sources = [@rbconfig["rubylibdir"], @rbconfig["archdir"]].compact.map { |path| File.expand_path(path) }
70
+ sources.select! { |path| File.directory?(path) }
71
+ raise ArgumentError, "Ruby standard library was not found" if sources.empty?
72
+
73
+ entries = sources.map do |source|
74
+ { source:, relative: runtime_relative_path(source) }
75
+ end
76
+ installed = []
77
+ entries.sort_by { |entry| entry.fetch(:source).length }.each do |entry|
78
+ source = entry.fetch(:source)
79
+ relative = entry.fetch(:relative)
80
+ target = File.join(destination, relative)
81
+ next if installed.any? { |entry| inside?(target, entry.fetch(:target)) }
82
+
83
+ FileUtils.mkdir_p(File.dirname(target))
84
+ FileUtils.cp_r(source, target)
85
+ installed << { source:, target:, relative: relative.tr(File::SEPARATOR, "/") }
86
+ end
87
+ entries.map { |entry| entry.fetch(:relative).tr(File::SEPARATOR, "/") }.uniq
88
+ end
89
+
90
+ def install_runtime_libraries(destination)
91
+ patterns = if platform.windows?
92
+ [File.join(@rbconfig.fetch("bindir", File.dirname(@ruby)), "*.dll")]
93
+ else
94
+ libdir = @rbconfig["libdir"]
95
+ libdir ? [File.join(libdir, "libruby*.so*"), File.join(libdir, "libruby*.dylib")] : []
96
+ end
97
+ target = platform.windows? ? File.join(destination, "bin") : File.join(destination, "lib")
98
+ FileUtils.mkdir_p(target)
99
+ patterns.flat_map { |pattern| Dir[pattern] }.uniq.each do |source|
100
+ next unless File.file?(source)
101
+
102
+ FileUtils.cp(source, File.join(target, File.basename(source)))
103
+ end
104
+ end
105
+
106
+ def install_native_dependencies(destination)
107
+ return if platform.windows?
108
+
109
+ library_root = File.join(destination, "lib")
110
+ FileUtils.mkdir_p(library_root)
111
+ binaries = [File.join(destination, "bin", "ruby")]
112
+ binaries.concat(Dir[File.join(destination, "**", "*.{so,dylib}")])
113
+ inspected = Set.new
114
+ until binaries.empty?
115
+ binary = binaries.shift
116
+ next unless File.file?(binary) && inspected.add?(binary)
117
+
118
+ dependencies(binary).each do |source|
119
+ name = File.basename(source)
120
+ next if SYSTEM_LIBRARIES.match?(name)
121
+
122
+ target = File.join(library_root, name)
123
+ next if File.file?(target)
124
+
125
+ FileUtils.cp(source, target)
126
+ binaries << target
127
+ end
128
+ end
129
+ end
130
+
131
+ def dependencies(binary)
132
+ if platform.linux?
133
+ output = IO.popen(["ldd", binary], err: File::NULL, &:read)
134
+ output.lines.filter_map do |line|
135
+ path = line[/=>\s+(\/\S+)/, 1] || line[/^\s*(\/\S+)/, 1]
136
+ path if path && File.file?(path)
137
+ end
138
+ else
139
+ output = IO.popen(["otool", "-L", binary], err: File::NULL, &:read)
140
+ output.lines.drop(1).filter_map do |line|
141
+ path = line.strip.split.first
142
+ next if path.nil? || path.start_with?("/System/", "/usr/lib/") || path.start_with?("@")
143
+
144
+ path if File.file?(path)
145
+ end
146
+ end
147
+ rescue Errno::ENOENT
148
+ []
149
+ end
150
+
151
+ def install_project_gems(project, destination)
152
+ specs = @spec_loader.call(project).reject { |spec| spec.name == "zui" || spec.default_gem? }
153
+ gem_home = File.join(destination, "gems")
154
+ FileUtils.mkdir_p([File.join(gem_home, "gems"), File.join(gem_home, "specifications")])
155
+ names = Set.new
156
+ specs.sort_by(&:full_name).each do |spec|
157
+ raise ArgumentError, "duplicate bundled gem: #{spec.full_name}" unless names.add?(spec.full_name)
158
+ unless File.directory?(spec.full_gem_path)
159
+ raise ArgumentError, "project gem is not installed: #{spec.full_name}; run `bundle install`"
160
+ end
161
+
162
+ FileUtils.cp_r(spec.full_gem_path, File.join(gem_home, "gems", spec.full_name))
163
+ File.write(File.join(gem_home, "specifications", "#{spec.full_name}.gemspec"), spec.to_ruby)
164
+ install_gem_extensions(spec, gem_home)
165
+ end
166
+ specs
167
+ end
168
+
169
+ def install_gem_extensions(spec, gem_home)
170
+ source = spec.extension_dir
171
+ return unless source && File.directory?(source)
172
+
173
+ parts = File.expand_path(source).split(File::SEPARATOR)
174
+ index = parts.rindex("extensions")
175
+ return unless index
176
+
177
+ relative = File.join(*parts[(index + 1)..])
178
+ target = File.join(gem_home, "extensions", relative)
179
+ FileUtils.mkdir_p(File.dirname(target))
180
+ FileUtils.cp_r(source, target)
181
+ end
182
+
183
+ def project_specs(project)
184
+ gemfile = File.join(project, "Gemfile")
185
+ lockfile = File.join(project, "Gemfile.lock")
186
+ raise ArgumentError, "Gemfile not found; add project gems before using --full" unless File.file?(gemfile)
187
+ unless File.file?(lockfile)
188
+ raise ArgumentError, "Gemfile.lock not found; run `bundle install` before using --full"
189
+ end
190
+
191
+ previous = @environment["BUNDLE_GEMFILE"]
192
+ ENV["BUNDLE_GEMFILE"] = gemfile
193
+ Bundler.reset!
194
+ definition = Bundler::Definition.build(gemfile, lockfile, nil)
195
+ definition.validate_runtime!
196
+ definition.specs.to_a
197
+ rescue Bundler::BundlerError => error
198
+ raise ArgumentError, "project gems are not ready: #{error.message}"
199
+ ensure
200
+ previous.nil? ? ENV.delete("BUNDLE_GEMFILE") : ENV["BUNDLE_GEMFILE"] = previous
201
+ Bundler.reset!
202
+ end
203
+
204
+ def runtime_relative_path(path)
205
+ prefix = File.expand_path(@rbconfig.fetch("prefix"))
206
+ return path.delete_prefix("#{prefix}#{File::SEPARATOR}") if inside?(path, prefix)
207
+
208
+ File.join("lib", "ruby", File.basename(path))
209
+ end
210
+
211
+ def ruby_version
212
+ @rbconfig["RUBY_PROGRAM_VERSION"] || RUBY_VERSION
213
+ end
214
+
215
+ def inside?(path, parent)
216
+ path == parent || path.start_with?("#{parent}#{File::SEPARATOR}")
217
+ end
218
+
219
+ def strip_binary(path)
220
+ magic = File.binread(path, 4)
221
+ mach_o = ["\xCF\xFA\xED\xFE".b, "\xFE\xED\xFA\xCF".b, "\xCA\xFE\xBA\xBE".b]
222
+ return unless magic.start_with?("\x7FELF".b, "MZ".b) || mach_o.include?(magic)
223
+
224
+ strip = find_command(platform.macos? ? %w[strip] : %w[strip llvm-strip])
225
+ return unless strip
226
+
227
+ arguments = platform.macos? ? [strip, "-x", path] : [strip, "--strip-unneeded", path]
228
+ system(*arguments, out: File::NULL, err: File::NULL)
229
+ end
230
+
231
+ def find_command(names)
232
+ @environment.fetch("PATH", "").split(File::PATH_SEPARATOR).each do |directory|
233
+ names.each do |name|
234
+ path = File.join(directory, name)
235
+ return path if File.file?(path) && File.executable?(path)
236
+ end
237
+ end
238
+ nil
239
+ end
240
+ end
241
+ end
data/lib/zui/generator.rb CHANGED
@@ -14,6 +14,7 @@ module Zui
14
14
  created = true
15
15
  FileUtils.mkdir_p(File.join(@path, "components"))
16
16
  File.write(File.join(@path, "main.rb"), main_program)
17
+ File.write(File.join(@path, "Gemfile"), gemfile)
17
18
  File.write(File.join(@path, "components", "welcome.rb"), welcome_component)
18
19
  File.write(File.join(@path, "config.rb"), distribution_config)
19
20
  FileUtils.mkdir_p(File.join(@path, "assets"))
@@ -50,6 +51,16 @@ module Zui
50
51
  RUBY
51
52
  end
52
53
 
54
+ def gemfile
55
+ <<~RUBY
56
+ source "https://rubygems.org"
57
+
58
+ gem "zui", "~> #{VERSION}"
59
+
60
+ # Add application gems here. They are included by `zui bundle --full`.
61
+ RUBY
62
+ end
63
+
53
64
  def welcome_component
54
65
  <<~RUBY
55
66
  # frozen_string_literal: true
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zui
4
+ def self.app(&definition)
5
+ Application.new(&definition).run
6
+ end
7
+
8
+ class << self
9
+ alias application app
10
+ end
11
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+
6
+ module Zui
7
+ class LiteRuntime < Client
8
+ FORMAT = 1
9
+ MRUBY_VERSION = "4.0.0"
10
+ MRUBY_REVISION = "831da26b9021de0369d17b71b5667e2941a1a32d"
11
+ MRUBY_JSON_REVISION = "f99d9428025469f2400f93c53b185f65f963e507"
12
+
13
+ def root
14
+ override = @environment["ZUI_LITE_RUNTIME_ROOT"]
15
+ return File.expand_path(override) if override && !override.empty?
16
+
17
+ File.join(cache_root, "zui", "runtimes", version, platform.id, "lite")
18
+ end
19
+
20
+ def archive_name = "zui-runtime-lite-#{platform.id}.tar.gz"
21
+
22
+ def configure!
23
+ return root if configured?
24
+ if client_root_override?
25
+ raise ArgumentError, "ZUI_LITE_RUNTIME_ROOT is not a valid configured lite runtime: #{root}"
26
+ end
27
+
28
+ super
29
+ end
30
+
31
+ def archive_url
32
+ override = @environment["ZUI_LITE_RUNTIME_ARCHIVE"]
33
+ return File.expand_path(override) if override && !override.empty?
34
+
35
+ URI.join("#{release_base_url}/", archive_name)
36
+ end
37
+
38
+ def checksum_url
39
+ override = @environment["ZUI_LITE_RUNTIME_CHECKSUM"]
40
+ return File.expand_path(override) if override && !override.empty?
41
+
42
+ source = archive_url
43
+ source.is_a?(URI) ? URI("#{source}.sha256") : "#{source}.sha256"
44
+ end
45
+
46
+ def install(project:, destination:)
47
+ unless configured?
48
+ raise ArgumentError,
49
+ "Zui lite runtime is not configured for #{platform.id}; run `zui doctor --fix` before bundling"
50
+ end
51
+
52
+ copy_to(destination)
53
+ File.write(File.join(destination, "app.rb"), LiteSource.new(project:).call)
54
+ ApplicationRuntime.new(
55
+ engine: "mruby", version: manifest.fetch("engine_version"),
56
+ executable: manifest.fetch("executable"), program: "app.rb", load_path: ""
57
+ ).write(destination)
58
+ end
59
+
60
+ private
61
+
62
+ def validate!(directory)
63
+ manifest_path = File.join(directory, "lite-runtime.json")
64
+ raise ArgumentError, "Zui lite runtime manifest not found: #{manifest_path}" unless File.file?(manifest_path)
65
+ raise ArgumentError, "Zui lite runtime manifest is too large" if File.size(manifest_path) > 65_536
66
+
67
+ manifest = JSON.parse(File.read(manifest_path))
68
+ raise ArgumentError, "invalid Zui lite runtime manifest root" unless manifest.is_a?(Hash)
69
+ expected = {
70
+ "format" => FORMAT,
71
+ "framework" => "zui",
72
+ "zui_version" => version,
73
+ "platform" => platform.id,
74
+ "engine" => "mruby",
75
+ "engine_version" => MRUBY_VERSION,
76
+ "engine_revision" => MRUBY_REVISION,
77
+ "json_revision" => MRUBY_JSON_REVISION,
78
+ "payload" => ["mruby"]
79
+ }
80
+ expected.each do |key, value|
81
+ actual = manifest[key]
82
+ unless actual == value
83
+ raise ArgumentError,
84
+ "invalid Zui lite runtime #{key}: expected #{value.inspect}, got #{actual.inspect}"
85
+ end
86
+ end
87
+
88
+ required_paths = manifest["required_paths"]
89
+ unless required_paths.is_a?(Array) && !required_paths.empty? &&
90
+ required_paths.all? { |path| path.is_a?(String) }
91
+ raise ArgumentError, "invalid Zui lite runtime required paths"
92
+ end
93
+ required_paths.each do |path|
94
+ relative = safe_manifest_path!(path, "lite runtime required path")
95
+ resolved = File.join(directory, relative)
96
+ raise ArgumentError, "Zui lite runtime required path is missing: #{resolved}" unless File.exist?(resolved)
97
+ end
98
+
99
+ relative = safe_manifest_path!(manifest["executable"], "lite runtime executable")
100
+ executable = File.join(directory, relative)
101
+ unless File.file?(executable) && executable_for_target?(executable)
102
+ raise ArgumentError, "Zui lite runtime executable is missing or not executable: #{executable}"
103
+ end
104
+ manifest
105
+ rescue JSON::ParserError => error
106
+ raise ArgumentError, "invalid Zui lite runtime manifest: #{error.message}"
107
+ end
108
+
109
+ def release_base_url
110
+ @release_base_url || @environment["ZUI_LITE_RUNTIME_BASE_URL"] ||
111
+ "https://github.com/AdamMusa/zui/releases/download/v#{version}"
112
+ end
113
+
114
+ def client_root_override?
115
+ value = @environment["ZUI_LITE_RUNTIME_ROOT"]
116
+ value && !value.empty?
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zui
4
+ class LiteSource
5
+ RUNTIME_FILES = %w[
6
+ lite_support.rb protocol.rb value.rb state_store.rb node.rb animation.rb scheduler.rb
7
+ component_registry.rb components.rb builder.rb application.rb lite_entry.rb
8
+ ].freeze
9
+ REQUIRE = /^\s*require\s*(?:\(\s*)?["']([^"']+)["']/
10
+
11
+ def initialize(project:, framework_root: FRAMEWORK_ROOT)
12
+ @project = File.expand_path(project)
13
+ @framework_root = File.expand_path(framework_root)
14
+ end
15
+
16
+ def call
17
+ application = SourceBundle.new(
18
+ File.join(@project, "main.rb"), root: @project, embedded_frameworks: ["zui"]
19
+ ).call
20
+ external = application.scan(REQUIRE).flatten.uniq
21
+ unless external.empty?
22
+ raise ArgumentError,
23
+ "--lite does not support CRuby gem requires (#{external.join(', ')}); use --full"
24
+ end
25
+
26
+ runtime = RUNTIME_FILES.map do |name|
27
+ path = File.join(@framework_root, "lib", "zui", name)
28
+ raise ArgumentError, "lite runtime source is missing: #{path}" unless File.file?(path)
29
+
30
+ source = File.readlines(path).reject { |line| REQUIRE.match?(line) }.join
31
+ "# runtime: #{name}\n#{source}"
32
+ end
33
+ runtime << "module Zui\n VERSION = #{VERSION.dump}\nend"
34
+ runtime << application
35
+ "#{runtime.join("\n\n")}\n"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless Object.const_defined?(:Mutex)
4
+ class Mutex
5
+ def synchronize
6
+ yield
7
+ end
8
+ end
9
+ end
10
+
11
+ unless Object.const_defined?(:ConditionVariable)
12
+ class ConditionVariable
13
+ def wait(_mutex, _seconds = nil)
14
+ self
15
+ end
16
+
17
+ def broadcast
18
+ self
19
+ end
20
+ end
21
+ end
data/lib/zui/node.rb CHANGED
@@ -25,6 +25,27 @@ module Zui
25
25
  end
26
26
  end
27
27
 
28
- Binding = Struct.new(:node, :property, :reader, :last_value, :animation, keyword_init: true)
29
- StructuralBinding = Struct.new(:node, :renderer, :last_children, keyword_init: true)
28
+ class Binding
29
+ attr_accessor :last_value
30
+ attr_reader :node, :property, :reader, :animation
31
+
32
+ def initialize(node:, property:, reader:, last_value:, animation:)
33
+ @node = node
34
+ @property = property
35
+ @reader = reader
36
+ @last_value = last_value
37
+ @animation = animation
38
+ end
39
+ end
40
+
41
+ class StructuralBinding
42
+ attr_accessor :last_children
43
+ attr_reader :node, :renderer
44
+
45
+ def initialize(node:, renderer:, last_children:)
46
+ @node = node
47
+ @renderer = renderer
48
+ @last_children = last_children
49
+ end
50
+ end
30
51
  end
data/lib/zui.rb CHANGED
@@ -12,11 +12,15 @@ require_relative "zui/components"
12
12
  require_relative "zui/builder"
13
13
  require_relative "zui/application"
14
14
  require_relative "zui/source_bundle"
15
+ require_relative "zui/lite_source"
15
16
  require_relative "zui/platform"
16
17
  require_relative "zui/runtime"
18
+ require_relative "zui/application_runtime"
19
+ require_relative "zui/full_runtime"
17
20
  require_relative "zui/tree_shaker"
18
21
  require_relative "zui/dist_config"
19
22
  require_relative "zui/client"
23
+ require_relative "zui/lite_runtime"
20
24
  require_relative "zui/host"
21
25
  require_relative "zui/runner"
22
26
  require_relative "zui/generator"
@@ -24,7 +28,7 @@ require_relative "zui/distribution"
24
28
  require_relative "zui/dist_packager"
25
29
 
26
30
  module Zui
27
- VERSION = "0.0.7"
31
+ VERSION = "0.0.8"
28
32
  FRAMEWORK_ROOT = File.expand_path("..", __dir__)
29
33
 
30
34
  def self.app(&definition)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Moussa Ali
@@ -320,6 +320,7 @@ files:
320
320
  - lib/zui.rb
321
321
  - lib/zui/animation.rb
322
322
  - lib/zui/application.rb
323
+ - lib/zui/application_runtime.rb
323
324
  - lib/zui/builder.rb
324
325
  - lib/zui/cli.rb
325
326
  - lib/zui/client.rb
@@ -329,8 +330,13 @@ files:
329
330
  - lib/zui/dist_config.rb
330
331
  - lib/zui/dist_packager.rb
331
332
  - lib/zui/distribution.rb
333
+ - lib/zui/full_runtime.rb
332
334
  - lib/zui/generator.rb
333
335
  - lib/zui/host.rb
336
+ - lib/zui/lite_entry.rb
337
+ - lib/zui/lite_runtime.rb
338
+ - lib/zui/lite_source.rb
339
+ - lib/zui/lite_support.rb
334
340
  - lib/zui/node.rb
335
341
  - lib/zui/platform.rb
336
342
  - lib/zui/protocol.rb