omarchy-ui 0.0.8-x86_64-linux → 0.0.9-x86_64-linux
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 +56 -48
- data/lib/omarchy_ui/cli.rb +30 -10
- data/lib/omarchy_ui/generator.rb +4 -3
- data/lib/omarchy_ui/project_config.rb +1 -0
- data/lib/omarchy_ui/qml_compiler.rb +386 -0
- data/lib/omarchy_ui/runtime.rb +10 -3
- data/lib/omarchy_ui.rb +2 -1
- data/vendor/runtime/x86_64-linux/RUNTIME_PROVENANCE.md +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 82c12f3f51c0d9242be7386a4994c4f3fe8b333d04489e4055b671054cb2f0e2
|
|
4
|
+
data.tar.gz: e693d5b68a5ad055b40fbb910b4bb6ef41b6de7405027396305f256ad744875d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 807cb5e30c33130681351065bc7fce902a7c4c125f1c6b47e8827db0ec6b49695745ab60d61da9475153f8abf1422940df280154af797ede39ef218a9097c449
|
|
7
|
+
data.tar.gz: c2f3532f53a5725e7c6e7cd9de667ebbb2dddd086204af8172f8b262c010e4fef2470b73716a7496c9859d396f43b22814e03926949944e2e1c080e1e64ca5a2
|
data/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
-
<img alt="Omarchy UI 0.0.
|
|
13
|
+
<img alt="Omarchy UI 0.0.9" src="https://img.shields.io/badge/Omarchy_UI-0.0.9-b7ff5a?style=flat-square&labelColor=111711">
|
|
14
14
|
<img alt="Ruby 3.1 or newer" src="https://img.shields.io/badge/Ruby-3.1%2B-cc342d?style=flat-square&logo=ruby&logoColor=white">
|
|
15
15
|
<img alt="Zui 0.0.10" src="https://img.shields.io/badge/Zui-0.0.10-7ee14b?style=flat-square&labelColor=111711">
|
|
16
16
|
<img alt="241 components" src="https://img.shields.io/badge/components-241-75d943?style=flat-square&labelColor=111711">
|
|
@@ -46,8 +46,8 @@ and the Omarchy adapter.
|
|
|
46
46
|
</table>
|
|
47
47
|
|
|
48
48
|
> [!IMPORTANT]
|
|
49
|
-
> Omarchy UI currently targets **Omarchy on x86-64 Linux
|
|
50
|
-
>
|
|
49
|
+
> Omarchy UI currently targets **Omarchy on x86-64 Linux**. Reusable Zui application modules can
|
|
50
|
+
> also run through Zui's native host on Linux, macOS, and Windows.
|
|
51
51
|
|
|
52
52
|
## Choose a workflow
|
|
53
53
|
|
|
@@ -59,15 +59,16 @@ and the Omarchy adapter.
|
|
|
59
59
|
|
|
60
60
|
## Requirements
|
|
61
61
|
|
|
62
|
-
- Omarchy on x86-64 Linux
|
|
63
|
-
- Ruby 3.1 or newer
|
|
64
|
-
-
|
|
62
|
+
- Omarchy on x86-64 Linux
|
|
63
|
+
- Ruby 3.1 or newer for installation and development
|
|
64
|
+
- Qt 6 development tools, CMake, Ninja, and a C++ compiler for `bundle`, `validate`, and `push`
|
|
65
|
+
- Optional Qt modules for components that use features such as Quick 3D or Multimedia
|
|
65
66
|
|
|
66
|
-
Omarchy UI
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
Omarchy UI turns Ruby and assets into an Omarchy-compatible application or plugin. It owns the
|
|
68
|
+
metadata, entry points, adapter, validation, tree-shaken and AOT-compiled Zui QML, and embedded mruby
|
|
69
|
+
runtime. Omarchy UI invokes the build tools; projects do not contain CMake files or distribution
|
|
70
|
+
scripts. The finished package does not require Ruby or framework gems. Components report a clear
|
|
71
|
+
error when a required Qt module or resource is unavailable.
|
|
71
72
|
|
|
72
73
|
## Quick start
|
|
73
74
|
|
|
@@ -79,7 +80,7 @@ gem install omarchy-ui
|
|
|
79
80
|
|
|
80
81
|
The gem installs the compatible Zui version as a dependency.
|
|
81
82
|
|
|
82
|
-
### 2. Generate a
|
|
83
|
+
### 2. Generate a project
|
|
83
84
|
|
|
84
85
|
```bash
|
|
85
86
|
omarchy_ui new Counter
|
|
@@ -93,13 +94,12 @@ omarchy_ui new "System Status" --plugin
|
|
|
93
94
|
cd system-status
|
|
94
95
|
```
|
|
95
96
|
|
|
96
|
-
Both commands create a ready-to-run Ruby project.
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
Both commands create a ready-to-run Ruby project. Developers write the interface and behavior;
|
|
98
|
+
Omarchy UI generates the Omarchy package without application-owned QML, shell entry points, or
|
|
99
|
+
handwritten manifests.
|
|
99
100
|
|
|
100
|
-
Each project includes a Ruby-native `config.rb`
|
|
101
|
-
|
|
102
|
-
single configuration when assembling the complete package.
|
|
101
|
+
Each project includes a Ruby-native `config.rb` with its type, identity, version, entry point, and
|
|
102
|
+
plugin metadata. `bundle`, `validate`, and `push` use this single configuration.
|
|
103
103
|
|
|
104
104
|
The generator creates a small, host-independent project:
|
|
105
105
|
|
|
@@ -120,8 +120,8 @@ counter/
|
|
|
120
120
|
omarchy_ui run main.rb
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
`run` bundles the project's Ruby entry point, asks Zui to tree-shake the QML runtime, prepares the
|
|
124
|
-
|
|
123
|
+
`run` bundles the project's Ruby entry point, asks Zui to tree-shake the QML runtime, prepares the
|
|
124
|
+
Omarchy host, and launches it through Quickshell.
|
|
125
125
|
|
|
126
126
|
## Application model
|
|
127
127
|
|
|
@@ -246,16 +246,15 @@ QML is not required.
|
|
|
246
246
|
|
|
247
247
|
## Omarchy plugins
|
|
248
248
|
|
|
249
|
-
Generate a
|
|
249
|
+
Generate a plugin project with one command:
|
|
250
250
|
|
|
251
251
|
```bash
|
|
252
252
|
omarchy_ui new "System Status" --plugin
|
|
253
253
|
cd system-status
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
tree-shaken Zui package, validation, and install lifecycle.
|
|
256
|
+
Developers work in Ruby and add their own assets. Omarchy UI supplies the plugin metadata, shell
|
|
257
|
+
surfaces, native host, tree-shaken Zui package, validation, and installation lifecycle.
|
|
259
258
|
|
|
260
259
|
The Ruby entry point uses ordinary Zui components:
|
|
261
260
|
|
|
@@ -282,8 +281,8 @@ OmarchyUI.plugin do
|
|
|
282
281
|
end
|
|
283
282
|
```
|
|
284
283
|
|
|
285
|
-
`bar_widget` and `panel :status` are the
|
|
286
|
-
|
|
284
|
+
`bar_widget` and `panel :status` are the developer-facing surface declarations. Omarchy UI maps them
|
|
285
|
+
to the required shell entry points when it builds or stages the plugin.
|
|
287
286
|
|
|
288
287
|
### Validate and install
|
|
289
288
|
|
|
@@ -292,8 +291,8 @@ omarchy_ui validate
|
|
|
292
291
|
omarchy_ui push
|
|
293
292
|
```
|
|
294
293
|
|
|
295
|
-
`validate` generates and checks the
|
|
296
|
-
|
|
294
|
+
`validate` generates and checks the plugin without modifying the Ruby project. `push` builds,
|
|
295
|
+
validates, installs, enables, and reloads it for development.
|
|
297
296
|
|
|
298
297
|
During development, suppress either lifecycle action when needed:
|
|
299
298
|
|
|
@@ -302,10 +301,6 @@ omarchy_ui push --no-enable
|
|
|
302
301
|
omarchy_ui push --no-restart
|
|
303
302
|
```
|
|
304
303
|
|
|
305
|
-
> [!WARNING]
|
|
306
|
-
> Omarchy plugins execute as unsandboxed user code inside the shell environment. Review the Ruby,
|
|
307
|
-
> assets, generated package, and provenance before installing a third-party plugin.
|
|
308
|
-
|
|
309
304
|
## Command-line reference
|
|
310
305
|
|
|
311
306
|
```text
|
|
@@ -314,11 +309,11 @@ omarchy_ui <command> [arguments]
|
|
|
314
309
|
|
|
315
310
|
| Command | Arguments | Result |
|
|
316
311
|
| --- | --- | --- |
|
|
317
|
-
| `omarchy_ui new NAME` | One project name | Creates
|
|
318
|
-
| `omarchy_ui new NAME --plugin` | One project name and `--plugin` | Creates
|
|
312
|
+
| `omarchy_ui new NAME` | One project name | Creates an Omarchy application project |
|
|
313
|
+
| `omarchy_ui new NAME --plugin` | One project name and `--plugin` | Creates an Omarchy plugin project |
|
|
319
314
|
| `omarchy_ui run FILE` | One Ruby entry point | Runs an application through the Omarchy/Quickshell host |
|
|
320
|
-
| `omarchy_ui bundle [DIRECTORY]` | Zero or one project directory | Generates the
|
|
321
|
-
| `omarchy_ui validate [DIRECTORY]` | Zero or one plugin directory | Stages the
|
|
315
|
+
| `omarchy_ui bundle [DIRECTORY]` | Zero or one project directory | Generates the distributable package and prints its location |
|
|
316
|
+
| `omarchy_ui validate [DIRECTORY]` | Zero or one plugin directory | Stages the plugin and delegates validation to `omarchy plugin validate` |
|
|
322
317
|
| `omarchy_ui push [DIRECTORY]` | Optional `--no-enable` and `--no-restart` | Atomically installs and activates a validated plugin |
|
|
323
318
|
| `omarchy_ui version` | None | Prints the installed Omarchy UI version |
|
|
324
319
|
|
|
@@ -327,19 +322,29 @@ not forward arbitrary Ruby arguments.
|
|
|
327
322
|
|
|
328
323
|
## Packaging
|
|
329
324
|
|
|
330
|
-
Create the
|
|
325
|
+
Create the distributable package from the project root:
|
|
331
326
|
|
|
332
327
|
```bash
|
|
333
328
|
omarchy_ui bundle
|
|
334
329
|
```
|
|
335
330
|
|
|
336
|
-
Omarchy UI
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
Zui 0.0.10 so
|
|
341
|
-
|
|
342
|
-
|
|
331
|
+
Omarchy UI assembles the Ruby source and assets, Omarchy entry points, native host, checksummed
|
|
332
|
+
runtime, and only the Zui components used by the project. It then AOT-compiles the generated QML into
|
|
333
|
+
a content-addressed Qt module and discards the generated component, control, theme, and font source
|
|
334
|
+
tree. Applications receive a direct launcher; plugins must pass Omarchy validation before the
|
|
335
|
+
command succeeds. Omarchy UI pins Zui 0.0.10 so the compiled UI matches the Zui core embedded in the
|
|
336
|
+
deterministic mruby runtime.
|
|
337
|
+
|
|
338
|
+
Current Omarchy manifests resolve entry points by filename, so a package retains four minimal loader
|
|
339
|
+
files: `App.qml`, `Service.qml`, `Panel.qml`, and `BarWidget.qml`. They contain only an import and a
|
|
340
|
+
compiled type instance; the application interface is stored in native `.so` artifacts. The package
|
|
341
|
+
also includes `omarchy-ui-qml-bundle.json` with the module URI and exact Qt version, plus
|
|
342
|
+
`omarchy-ui-qml-bundle.sha256` for artifact verification. `QML_PROVENANCE.md` explains the native
|
|
343
|
+
artifacts for package reviewers. Compiled packages should be built against the Qt version shipped by
|
|
344
|
+
their target Omarchy release.
|
|
345
|
+
|
|
346
|
+
The source `config.rb` is the build configuration. Omarchy UI generates plugin manifests from it;
|
|
347
|
+
developers never maintain a separate manifest.
|
|
343
348
|
|
|
344
349
|
## Component catalog
|
|
345
350
|
|
|
@@ -406,10 +411,12 @@ Zui DSL · state · events · bindings · scheduler
|
|
|
406
411
|
│
|
|
407
412
|
│ versioned JSON render, patch, event, and effect protocol
|
|
408
413
|
▼
|
|
409
|
-
Omarchy
|
|
414
|
+
Omarchy process bridge ───────────────── embedded mruby runtime
|
|
410
415
|
│
|
|
411
416
|
▼
|
|
412
|
-
Zui
|
|
417
|
+
Tree-shaken Zui host + component catalog
|
|
418
|
+
│
|
|
419
|
+
│ bundle: Qt AOT module + four loader shims
|
|
413
420
|
│
|
|
414
421
|
▼
|
|
415
422
|
App window · Omarchy panel · Omarchy bar widget
|
|
@@ -427,8 +434,9 @@ The dependency direction is deliberate:
|
|
|
427
434
|
- **Applications own** Ruby source, assets, state, behavior, and reusable UI modules.
|
|
428
435
|
|
|
429
436
|
At bundle, validation, and push time, Omarchy UI asks Zui to analyze the bundled Ruby syntax and
|
|
430
|
-
generate only the required component adapters plus their renderer dependencies.
|
|
431
|
-
host
|
|
437
|
+
generate only the required component adapters plus their renderer dependencies. Omarchy UI overlays
|
|
438
|
+
its host, compiles the complete generated QML graph into a content-addressed Qt module, and emits the
|
|
439
|
+
minimal loader files required by Omarchy. Read the full [QML ownership and adapter
|
|
432
440
|
boundary](docs/qml-support.md).
|
|
433
441
|
|
|
434
442
|
## Runtime integrity
|
data/lib/omarchy_ui/cli.rb
CHANGED
|
@@ -114,14 +114,17 @@ module OmarchyUI
|
|
|
114
114
|
config = ProjectConfig.load(source)
|
|
115
115
|
entrypoint = config&.entrypoint || "main.rb"
|
|
116
116
|
raise ArgumentError, "#{entrypoint} not found: #{source}" unless File.file?(File.join(source, entrypoint))
|
|
117
|
+
precompiled = config.nil? && compiled_package?(source)
|
|
117
118
|
destination = File.join(source, "dist", config&.slug || File.basename(source))
|
|
118
119
|
raise ArgumentError, "bundle destination already exists: #{destination}" if File.exist?(destination)
|
|
119
120
|
FileUtils.mkdir_p(destination)
|
|
120
|
-
entries = application_entries(source, config:)
|
|
121
|
+
entries = precompiled ? package_entries(source) : application_entries(source, config:)
|
|
121
122
|
FileUtils.cp_r(entries.map { |entry| File.join(source, entry) }, destination)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
unless precompiled
|
|
124
|
+
config.write_manifest(destination) if config&.plugin?
|
|
125
|
+
bundle_ruby_entrypoint(source, destination, entrypoint:)
|
|
126
|
+
Runtime.install_package(destination, compiled: !config.nil?)
|
|
127
|
+
end
|
|
125
128
|
plugin = config ? config.plugin? : File.file?(File.join(destination, "manifest.json"))
|
|
126
129
|
if plugin
|
|
127
130
|
raise ArgumentError, "bundled plugin validation failed" unless system("omarchy", "plugin", "validate", destination)
|
|
@@ -140,6 +143,8 @@ module OmarchyUI
|
|
|
140
143
|
export OMARCHY_UI_RUNTIME="$app_dir/omarchy-ui-runtime"
|
|
141
144
|
export OMARCHY_UI_PROJECT_DIR="$app_dir"
|
|
142
145
|
export OMARCHY_UI_RUBY_PROGRAM="$app_dir/#{entrypoint}"
|
|
146
|
+
export QML2_IMPORT_PATH="$app_dir${QML2_IMPORT_PATH:+:$QML2_IMPORT_PATH}"
|
|
147
|
+
export QML_IMPORT_PATH="$app_dir${QML_IMPORT_PATH:+:$QML_IMPORT_PATH}"
|
|
143
148
|
export QT_LOGGING_RULES="${QT_LOGGING_RULES:+$QT_LOGGING_RULES;}qt.qpa.services.warning=false"
|
|
144
149
|
exec quickshell --path "$app_dir/App.qml"
|
|
145
150
|
SH
|
|
@@ -161,6 +166,18 @@ module OmarchyUI
|
|
|
161
166
|
Dir.children(source).reject { |entry| %w[.git dist].include?(entry) || generated.include?(entry) }
|
|
162
167
|
end
|
|
163
168
|
|
|
169
|
+
def package_entries(source)
|
|
170
|
+
Dir.children(source).reject { |entry| %w[.git dist].include?(entry) }
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def compiled_package?(source)
|
|
174
|
+
report = File.join(source, QmlCompiler::REPORT)
|
|
175
|
+
return false unless File.file?(report)
|
|
176
|
+
|
|
177
|
+
QmlCompiler.verify!(source)
|
|
178
|
+
true
|
|
179
|
+
end
|
|
180
|
+
|
|
164
181
|
def push(arguments)
|
|
165
182
|
enable = !arguments.delete("--no-enable")
|
|
166
183
|
restart = !arguments.delete("--no-restart")
|
|
@@ -213,14 +230,17 @@ module OmarchyUI
|
|
|
213
230
|
def stage_project(source, parent: nil, prefix: ".omarchy-ui-staging-")
|
|
214
231
|
raise ArgumentError, "project directory not found: #{source}" unless File.directory?(source)
|
|
215
232
|
config = ProjectConfig.load(source)
|
|
233
|
+
precompiled = config.nil? && compiled_package?(source)
|
|
216
234
|
staging = parent ? Dir.mktmpdir(prefix, parent) : Dir.mktmpdir(prefix)
|
|
217
|
-
entries = application_entries(source, config:)
|
|
235
|
+
entries = precompiled ? package_entries(source) : application_entries(source, config:)
|
|
218
236
|
FileUtils.cp_r(entries.map { |entry| File.join(source, entry) }, staging) unless entries.empty?
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
237
|
+
unless precompiled
|
|
238
|
+
config.write_manifest(staging) if config&.plugin?
|
|
239
|
+
entrypoint = config&.entrypoint || "main.rb"
|
|
240
|
+
if File.file?(File.join(staging, entrypoint))
|
|
241
|
+
bundle_ruby_entrypoint(source, staging, entrypoint:)
|
|
242
|
+
Runtime.install_package(staging, compiled: !config.nil?)
|
|
243
|
+
end
|
|
224
244
|
end
|
|
225
245
|
staging
|
|
226
246
|
rescue StandardError
|
data/lib/omarchy_ui/generator.rb
CHANGED
|
@@ -139,7 +139,8 @@ module OmarchyUI
|
|
|
139
139
|
omarchy_ui bundle
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
Omarchy UI generates the complete self-contained
|
|
142
|
+
Omarchy UI tree-shakes and compiles the UI, then generates the complete self-contained
|
|
143
|
+
application package required by Omarchy.
|
|
143
144
|
MARKDOWN
|
|
144
145
|
end
|
|
145
146
|
|
|
@@ -168,8 +169,8 @@ module OmarchyUI
|
|
|
168
169
|
```
|
|
169
170
|
|
|
170
171
|
Write the plugin interface and behavior in Ruby under `main.rb` and `components/`.
|
|
171
|
-
Public plugin identity and release metadata live in `config.rb`. Omarchy UI
|
|
172
|
-
|
|
172
|
+
Public plugin identity and release metadata live in `config.rb`. Omarchy UI tree-shakes and
|
|
173
|
+
compiles the UI, then generates and validates the complete Omarchy-compatible plugin package.
|
|
173
174
|
MARKDOWN
|
|
174
175
|
end
|
|
175
176
|
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "json"
|
|
6
|
+
require "open3"
|
|
7
|
+
require "pathname"
|
|
8
|
+
require "tmpdir"
|
|
9
|
+
|
|
10
|
+
module OmarchyUI
|
|
11
|
+
class QmlCompiler
|
|
12
|
+
FORMAT_VERSION = 1
|
|
13
|
+
ENTRY_TYPES = {
|
|
14
|
+
"App.qml" => "App",
|
|
15
|
+
"BarWidget.qml" => "BarWidget",
|
|
16
|
+
"Panel.qml" => "Panel",
|
|
17
|
+
"Service.qml" => "Service"
|
|
18
|
+
}.freeze
|
|
19
|
+
SOURCE_ENTRIES = (ENTRY_TYPES.keys + %w[ControlNode.qml Components Controls Theme Fonts]).freeze
|
|
20
|
+
COMPILED_ROOT = "OmarchyUI"
|
|
21
|
+
REPORT = "omarchy-ui-qml-bundle.json"
|
|
22
|
+
CHECKSUM = "omarchy-ui-qml-bundle.sha256"
|
|
23
|
+
PROVENANCE = "QML_PROVENANCE.md"
|
|
24
|
+
|
|
25
|
+
def self.compile!(path)
|
|
26
|
+
new(path).compile!
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.verify!(path)
|
|
30
|
+
new(path).verify!
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def initialize(path)
|
|
34
|
+
@path = File.expand_path(path)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def compile!
|
|
38
|
+
validate_sources!
|
|
39
|
+
tools = build_tools
|
|
40
|
+
qt_version = qt_version!(tools.fetch(:qtpaths))
|
|
41
|
+
|
|
42
|
+
Dir.mktmpdir("omarchy-ui-qml-compile-") do |temporary|
|
|
43
|
+
source = File.join(temporary, "source")
|
|
44
|
+
build = File.join(temporary, "build")
|
|
45
|
+
stage_sources(source)
|
|
46
|
+
qml_files = relative_files(source, "**/*.qml")
|
|
47
|
+
resources = relative_files(source, "Fonts/**/*") +
|
|
48
|
+
%w[Controls/qmldir Theme/qmldir].select { |entry| File.file?(File.join(source, entry)) }
|
|
49
|
+
fingerprint = source_fingerprint(source, qml_files + resources, qt_version:)
|
|
50
|
+
segment = "B#{fingerprint[0, 20]}"
|
|
51
|
+
uri = "OmarchyUI.Bundles.#{segment}"
|
|
52
|
+
target = "omarchy_ui_bundle_#{segment.downcase}"
|
|
53
|
+
module_path = File.join(*uri.split("."))
|
|
54
|
+
|
|
55
|
+
File.write(File.join(source, "CMakeLists.txt"), cmake_project(
|
|
56
|
+
qml_files:,
|
|
57
|
+
resources: resources.sort,
|
|
58
|
+
uri:,
|
|
59
|
+
target:,
|
|
60
|
+
module_path:
|
|
61
|
+
))
|
|
62
|
+
configure!(tools.fetch(:qt_cmake), source, build)
|
|
63
|
+
build!(tools.fetch(:cmake), build)
|
|
64
|
+
deploy!(
|
|
65
|
+
build:,
|
|
66
|
+
module_path:,
|
|
67
|
+
target:,
|
|
68
|
+
uri:,
|
|
69
|
+
fingerprint:,
|
|
70
|
+
qml_files:,
|
|
71
|
+
resources:,
|
|
72
|
+
source:,
|
|
73
|
+
expected_qt_version: qt_version
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def verify!
|
|
79
|
+
report_path = File.join(@path, REPORT)
|
|
80
|
+
raise ArgumentError, "compiled QML report is missing: #{report_path}" unless File.file?(report_path)
|
|
81
|
+
|
|
82
|
+
report = JSON.parse(File.read(report_path))
|
|
83
|
+
unless report["format"] == "qt-aot-qml-module" && report["format_version"] == FORMAT_VERSION
|
|
84
|
+
raise ArgumentError, "unsupported compiled QML package format"
|
|
85
|
+
end
|
|
86
|
+
ENTRY_TYPES.each_key do |entry|
|
|
87
|
+
raise ArgumentError, "compiled QML entry shim is missing: #{entry}" unless File.file?(File.join(@path, entry))
|
|
88
|
+
end
|
|
89
|
+
module_path = safe_relative_path(report.fetch("module_path"))
|
|
90
|
+
raise ArgumentError, "compiled QML qmldir is missing" unless File.file?(File.join(@path, module_path, "qmldir"))
|
|
91
|
+
|
|
92
|
+
artifacts = report.fetch("artifacts")
|
|
93
|
+
raise ArgumentError, "compiled QML package must contain two libraries" unless artifacts.length == 2
|
|
94
|
+
artifacts.each do |artifact|
|
|
95
|
+
relative = safe_relative_path(artifact.fetch("path"))
|
|
96
|
+
unless relative.start_with?("#{module_path}/") && relative.end_with?(".so")
|
|
97
|
+
raise ArgumentError, "compiled QML artifact is outside its module: #{relative}"
|
|
98
|
+
end
|
|
99
|
+
absolute = File.join(@path, relative)
|
|
100
|
+
raise ArgumentError, "compiled QML artifact is missing: #{relative}" unless File.file?(absolute)
|
|
101
|
+
unless File.size(absolute) == artifact.fetch("bytes") &&
|
|
102
|
+
Digest::SHA256.file(absolute).hexdigest == artifact.fetch("sha256")
|
|
103
|
+
raise ArgumentError, "compiled QML artifact checksum mismatch: #{relative}"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
report
|
|
107
|
+
rescue JSON::ParserError, KeyError, TypeError => error
|
|
108
|
+
raise ArgumentError, "invalid compiled QML report: #{error.message}"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def safe_relative_path(path)
|
|
114
|
+
value = path.to_s
|
|
115
|
+
clean = File.expand_path(value, @path)
|
|
116
|
+
unless !value.empty? && !Pathname.new(value).absolute? && clean.start_with?("#{@path}/")
|
|
117
|
+
raise ArgumentError, "unsafe compiled QML path: #{value.inspect}"
|
|
118
|
+
end
|
|
119
|
+
clean.delete_prefix("#{@path}/")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def validate_sources!
|
|
123
|
+
missing = ENTRY_TYPES.keys.reject { |entry| File.file?(File.join(@path, entry)) }
|
|
124
|
+
missing << "ControlNode.qml" unless File.file?(File.join(@path, "ControlNode.qml"))
|
|
125
|
+
return if missing.empty?
|
|
126
|
+
|
|
127
|
+
raise ArgumentError, "cannot compile QML; generated files are missing: #{missing.join(', ')}"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def build_tools
|
|
131
|
+
{
|
|
132
|
+
qt_cmake: find_executable("/usr/lib/qt6/bin/qt-cmake", "qt-cmake"),
|
|
133
|
+
qtpaths: find_executable("/usr/lib/qt6/bin/qtpaths", "qtpaths6", "qtpaths"),
|
|
134
|
+
cmake: find_executable("cmake"),
|
|
135
|
+
ninja: find_executable("ninja"),
|
|
136
|
+
compiler: find_executable("c++", "g++", "clang++")
|
|
137
|
+
}
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def qt_version!(qtpaths)
|
|
141
|
+
output, error, status = Open3.capture3(qtpaths, "--query", "QT_VERSION")
|
|
142
|
+
version = output.strip
|
|
143
|
+
return version if status.success? && !version.empty?
|
|
144
|
+
|
|
145
|
+
detail = error.strip
|
|
146
|
+
raise ArgumentError, "could not determine the Qt build version#{detail.empty? ? '' : ": #{detail}"}"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def find_executable(*candidates)
|
|
150
|
+
candidates.each do |candidate|
|
|
151
|
+
if candidate.include?(File::SEPARATOR)
|
|
152
|
+
return candidate if File.file?(candidate) && File.executable?(candidate)
|
|
153
|
+
next
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).each do |directory|
|
|
157
|
+
path = File.join(directory, candidate)
|
|
158
|
+
return path if File.file?(path) && File.executable?(path)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
raise ArgumentError, "compiled QML packaging requires #{candidates.first}"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def stage_sources(destination)
|
|
165
|
+
FileUtils.mkdir_p(destination)
|
|
166
|
+
SOURCE_ENTRIES.each do |entry|
|
|
167
|
+
source = File.join(@path, entry)
|
|
168
|
+
next unless File.exist?(source)
|
|
169
|
+
|
|
170
|
+
FileUtils.cp_r(source, File.join(destination, entry))
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def relative_files(root, pattern)
|
|
175
|
+
Dir.glob(File.join(root, pattern), File::FNM_DOTMATCH)
|
|
176
|
+
.select { |path| File.file?(path) }
|
|
177
|
+
.map { |path| path.delete_prefix("#{root}/") }
|
|
178
|
+
.sort
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def source_fingerprint(root, files, qt_version:)
|
|
182
|
+
digest = Digest::SHA256.new
|
|
183
|
+
digest << "omarchy-ui-qml-bundle\0" << FORMAT_VERSION.to_s << "\0"
|
|
184
|
+
digest << "qt\0" << qt_version << "\0"
|
|
185
|
+
files.sort.each do |relative|
|
|
186
|
+
digest << relative << "\0" << File.binread(File.join(root, relative)) << "\0"
|
|
187
|
+
end
|
|
188
|
+
digest.hexdigest
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def cmake_project(qml_files:, resources:, uri:, target:, module_path:)
|
|
192
|
+
<<~CMAKE
|
|
193
|
+
cmake_minimum_required(VERSION 3.21)
|
|
194
|
+
|
|
195
|
+
project(OmarchyUICompiledBundle LANGUAGES CXX)
|
|
196
|
+
|
|
197
|
+
find_package(Qt6 6.8 REQUIRED COMPONENTS Core Qml Quick QuickControls2)
|
|
198
|
+
qt_standard_project_setup(REQUIRES 6.8)
|
|
199
|
+
|
|
200
|
+
qt_add_library(#{target} SHARED)
|
|
201
|
+
qt_add_qml_module(#{target}
|
|
202
|
+
URI #{uri}
|
|
203
|
+
VERSION 1.0
|
|
204
|
+
OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/#{module_path}"
|
|
205
|
+
QML_FILES
|
|
206
|
+
#{cmake_entries(qml_files)}
|
|
207
|
+
RESOURCES
|
|
208
|
+
#{cmake_entries(resources)}
|
|
209
|
+
DISCARD_QML_CONTENTS
|
|
210
|
+
NO_LINT
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
set(module_output "${CMAKE_BINARY_DIR}/#{module_path}")
|
|
214
|
+
set_target_properties(
|
|
215
|
+
#{target}
|
|
216
|
+
#{target}plugin
|
|
217
|
+
PROPERTIES
|
|
218
|
+
LIBRARY_OUTPUT_DIRECTORY "${module_output}"
|
|
219
|
+
BUILD_RPATH "\\$ORIGIN"
|
|
220
|
+
BUILD_WITH_INSTALL_RPATH TRUE
|
|
221
|
+
INSTALL_RPATH "\\$ORIGIN"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
target_compile_options(
|
|
225
|
+
#{target}
|
|
226
|
+
PRIVATE
|
|
227
|
+
"-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=."
|
|
228
|
+
"-ffile-prefix-map=${CMAKE_CURRENT_BINARY_DIR}=."
|
|
229
|
+
)
|
|
230
|
+
target_compile_options(
|
|
231
|
+
#{target}plugin
|
|
232
|
+
PRIVATE
|
|
233
|
+
"-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=."
|
|
234
|
+
"-ffile-prefix-map=${CMAKE_CURRENT_BINARY_DIR}=."
|
|
235
|
+
)
|
|
236
|
+
target_link_options(#{target} PRIVATE "-Wl,--build-id=none")
|
|
237
|
+
target_link_options(#{target}plugin PRIVATE "-Wl,--build-id=none")
|
|
238
|
+
target_link_libraries(
|
|
239
|
+
#{target}
|
|
240
|
+
PRIVATE Qt6::Core Qt6::Qml Qt6::Quick Qt6::QuickControls2
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
file(WRITE "${CMAKE_BINARY_DIR}/qt-version.txt" "${Qt6Core_VERSION}\n")
|
|
244
|
+
CMAKE
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def cmake_entries(entries)
|
|
248
|
+
entries.map { |entry| " #{cmake_string(entry)}" }.join("\n")
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def cmake_string(value)
|
|
252
|
+
escaped = value.gsub("\\", "\\\\").gsub('"', '\\"')
|
|
253
|
+
%Q("#{escaped}")
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def configure!(qt_cmake, source, build)
|
|
257
|
+
run_build_command!(
|
|
258
|
+
"QML configure",
|
|
259
|
+
qt_cmake,
|
|
260
|
+
"-S", source,
|
|
261
|
+
"-B", build,
|
|
262
|
+
"-G", "Ninja",
|
|
263
|
+
"-DCMAKE_BUILD_TYPE=Release"
|
|
264
|
+
)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def build!(cmake, build)
|
|
268
|
+
run_build_command!("QML compilation", cmake, "--build", build, "--parallel")
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def run_build_command!(label, *command)
|
|
272
|
+
environment = {
|
|
273
|
+
"LC_ALL" => "C.UTF-8",
|
|
274
|
+
"SOURCE_DATE_EPOCH" => "1",
|
|
275
|
+
"TZ" => "UTC"
|
|
276
|
+
}
|
|
277
|
+
output, error, status = Open3.capture3(environment, *command)
|
|
278
|
+
return if status.success?
|
|
279
|
+
|
|
280
|
+
detail = "#{output}\n#{error}".lines.last(40).join.strip
|
|
281
|
+
raise ArgumentError, "#{label} failed#{detail.empty? ? '' : ":\n#{detail}"}"
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def deploy!(build:, module_path:, target:, uri:, fingerprint:, qml_files:, resources:, source:,
|
|
285
|
+
expected_qt_version:)
|
|
286
|
+
built_module = File.join(build, module_path)
|
|
287
|
+
artifact_names = ["lib#{target}.so", "lib#{target}plugin.so", "qmldir"]
|
|
288
|
+
missing = artifact_names.reject { |name| File.file?(File.join(built_module, name)) }
|
|
289
|
+
unless missing.empty?
|
|
290
|
+
raise ArgumentError, "QML compilation did not produce: #{missing.join(', ')}"
|
|
291
|
+
end
|
|
292
|
+
qt_version = File.read(File.join(build, "qt-version.txt")).strip
|
|
293
|
+
unless qt_version == expected_qt_version
|
|
294
|
+
raise ArgumentError,
|
|
295
|
+
"Qt compiler mismatch: qtpaths reported #{expected_qt_version}, CMake found #{qt_version}"
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
compiled_root = File.join(@path, COMPILED_ROOT)
|
|
299
|
+
FileUtils.remove_entry(compiled_root) if File.directory?(compiled_root)
|
|
300
|
+
destination = File.join(@path, module_path)
|
|
301
|
+
FileUtils.mkdir_p(destination)
|
|
302
|
+
artifact_names.each { |name| FileUtils.cp(File.join(built_module, name), destination) }
|
|
303
|
+
qmldir = File.join(destination, "qmldir")
|
|
304
|
+
File.write(qmldir, File.read(qmldir).rstrip + "\n")
|
|
305
|
+
|
|
306
|
+
SOURCE_ENTRIES.each { |entry| remove_generated(File.join(@path, entry)) }
|
|
307
|
+
ENTRY_TYPES.each do |entry, type|
|
|
308
|
+
File.write(File.join(@path, entry), entry_shim(module_path, type, uri:))
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
artifact_paths = artifact_names.filter_map do |name|
|
|
312
|
+
next if name == "qmldir"
|
|
313
|
+
|
|
314
|
+
relative = File.join(module_path, name)
|
|
315
|
+
absolute = File.join(@path, relative)
|
|
316
|
+
{
|
|
317
|
+
"path" => relative,
|
|
318
|
+
"sha256" => Digest::SHA256.file(absolute).hexdigest,
|
|
319
|
+
"bytes" => File.size(absolute)
|
|
320
|
+
}
|
|
321
|
+
end
|
|
322
|
+
report = {
|
|
323
|
+
"format" => "qt-aot-qml-module",
|
|
324
|
+
"format_version" => FORMAT_VERSION,
|
|
325
|
+
"qt_version" => qt_version,
|
|
326
|
+
"module_uri" => uri,
|
|
327
|
+
"module_path" => module_path,
|
|
328
|
+
"source_fingerprint" => fingerprint,
|
|
329
|
+
"source_files" => qml_files.length,
|
|
330
|
+
"source_bytes" => (qml_files + resources).sum { |file| File.size(File.join(source, file)) },
|
|
331
|
+
"entry_shims" => ENTRY_TYPES.keys,
|
|
332
|
+
"artifacts" => artifact_paths
|
|
333
|
+
}
|
|
334
|
+
File.write(File.join(@path, REPORT), JSON.pretty_generate(report) + "\n")
|
|
335
|
+
checksums = artifact_paths.map { |artifact| "#{artifact.fetch('sha256')} #{artifact.fetch('path')}" }
|
|
336
|
+
File.write(File.join(@path, CHECKSUM), checksums.join("\n") + "\n")
|
|
337
|
+
File.write(File.join(@path, PROVENANCE), provenance(report))
|
|
338
|
+
report
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def remove_generated(path)
|
|
342
|
+
File.directory?(path) ? FileUtils.remove_entry(path) : FileUtils.rm_f(path)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def entry_shim(module_path, type, uri:)
|
|
346
|
+
import = type == "App" ? uri : %Q("#{module_path}")
|
|
347
|
+
<<~QML
|
|
348
|
+
import QtQuick
|
|
349
|
+
import #{import} as Compiled
|
|
350
|
+
|
|
351
|
+
Compiled.#{type} {}
|
|
352
|
+
QML
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def provenance(report)
|
|
356
|
+
artifacts = report.fetch("artifacts").map do |artifact|
|
|
357
|
+
"- `#{artifact.fetch('path')}` — `#{artifact.fetch('sha256')}`"
|
|
358
|
+
end.join("\n")
|
|
359
|
+
<<~MARKDOWN
|
|
360
|
+
# Compiled QML provenance
|
|
361
|
+
|
|
362
|
+
Omarchy UI generated this package's native Qt module from the tree-shaken Zui and
|
|
363
|
+
Omarchy host QML graph. Generated QML source contents were discarded after AOT compilation.
|
|
364
|
+
|
|
365
|
+
- Format: `#{report.fetch('format')}` version #{report.fetch('format_version')}
|
|
366
|
+
- Qt: `#{report.fetch('qt_version')}`
|
|
367
|
+
- Module: `#{report.fetch('module_uri')}`
|
|
368
|
+
- Source fingerprint: `#{report.fetch('source_fingerprint')}`
|
|
369
|
+
|
|
370
|
+
## Artifacts
|
|
371
|
+
|
|
372
|
+
#{artifacts}
|
|
373
|
+
|
|
374
|
+
Verify the packaged libraries from the plugin directory:
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
sha256sum --check #{CHECKSUM}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
`App.qml`, `Service.qml`, `Panel.qml`, and `BarWidget.qml` are the minimal loader shims
|
|
381
|
+
required by Omarchy's file-based entry-point contract. Application UI lives in the compiled
|
|
382
|
+
module recorded by `#{REPORT}`.
|
|
383
|
+
MARKDOWN
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|
data/lib/omarchy_ui/runtime.rb
CHANGED
|
@@ -16,8 +16,14 @@ module OmarchyUI
|
|
|
16
16
|
AUDIT_FILES = %w[
|
|
17
17
|
omarchy-ui-runtime.sha256 runtime-provenance.json RUNTIME_PROVENANCE.md
|
|
18
18
|
].freeze
|
|
19
|
-
GENERATED_ENTRIES = (ADAPTER_FILES + ZUI_GENERATED_ENTRIES +
|
|
20
|
-
|
|
19
|
+
GENERATED_ENTRIES = (ADAPTER_FILES + ZUI_GENERATED_ENTRIES + [
|
|
20
|
+
TREE_SHAKE_REPORT,
|
|
21
|
+
QmlCompiler::COMPILED_ROOT,
|
|
22
|
+
QmlCompiler::REPORT,
|
|
23
|
+
QmlCompiler::CHECKSUM,
|
|
24
|
+
QmlCompiler::PROVENANCE,
|
|
25
|
+
"omarchy-ui-runtime"
|
|
26
|
+
]).freeze
|
|
21
27
|
|
|
22
28
|
def executable
|
|
23
29
|
override = ENV["OMARCHY_UI_RUNTIME"]
|
|
@@ -39,7 +45,7 @@ module OmarchyUI
|
|
|
39
45
|
FileUtils.rm_f(temporary) if temporary && File.exist?(temporary)
|
|
40
46
|
end
|
|
41
47
|
|
|
42
|
-
def install_package(path, framework_root: FRAMEWORK_ROOT, project: path)
|
|
48
|
+
def install_package(path, framework_root: FRAMEWORK_ROOT, project: path, compiled: false)
|
|
43
49
|
FileUtils.mkdir_p(path)
|
|
44
50
|
GENERATED_ENTRIES.each do |entry|
|
|
45
51
|
generated = File.join(path, entry)
|
|
@@ -67,6 +73,7 @@ module OmarchyUI
|
|
|
67
73
|
source = File.join(framework_root, "vendor", "runtime", "x86_64-linux", file)
|
|
68
74
|
FileUtils.cp(source, File.join(path, file)) if File.file?(source)
|
|
69
75
|
end
|
|
76
|
+
QmlCompiler.compile!(path) if compiled
|
|
70
77
|
path
|
|
71
78
|
end
|
|
72
79
|
|
data/lib/omarchy_ui.rb
CHANGED
|
@@ -11,7 +11,7 @@ rescue LoadError
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
module OmarchyUI
|
|
14
|
-
VERSION = "0.0.
|
|
14
|
+
VERSION = "0.0.9"
|
|
15
15
|
ZUI_VERSION = Zui::VERSION
|
|
16
16
|
FRAMEWORK_ROOT = File.expand_path("..", __dir__)
|
|
17
17
|
|
|
@@ -128,4 +128,5 @@ end
|
|
|
128
128
|
|
|
129
129
|
require_relative "omarchy_ui/project_config"
|
|
130
130
|
require_relative "omarchy_ui/generator"
|
|
131
|
+
require_relative "omarchy_ui/qml_compiler"
|
|
131
132
|
require_relative "omarchy_ui/runtime"
|
|
@@ -17,4 +17,4 @@ The workflow checks out every external input at a full commit digest, performs t
|
|
|
17
17
|
builds, requires byte-identical executables, and signs the resulting SHA through GitHub's
|
|
18
18
|
artifact attestation service. Machine-readable provenance is included in
|
|
19
19
|
`runtime-provenance.json`; independent verification instructions are in
|
|
20
|
-
[
|
|
20
|
+
the [runtime build documentation](https://github.com/AdamMusa/omarchy-ui/blob/main/docs/runtime-build.md).
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: omarchy-ui
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.9
|
|
5
5
|
platform: x86_64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Adam Moussa Ali
|
|
@@ -40,6 +40,7 @@ files:
|
|
|
40
40
|
- lib/omarchy_ui/cli.rb
|
|
41
41
|
- lib/omarchy_ui/generator.rb
|
|
42
42
|
- lib/omarchy_ui/project_config.rb
|
|
43
|
+
- lib/omarchy_ui/qml_compiler.rb
|
|
43
44
|
- lib/omarchy_ui/runtime.rb
|
|
44
45
|
- vendor/runtime/x86_64-linux/RUNTIME_PROVENANCE.md
|
|
45
46
|
- vendor/runtime/x86_64-linux/omarchy-ui-runtime
|