Global Trend Radar
Dev.to US tech 2026-05-08 22:10

RustでGNOMEアプリを構築する:パート4 - ブループリント

原題: Building GNOME Apps with Rust, Part 4: Blueprint

元記事を開く →

分析結果

カテゴリ
不動産
重要度
55
トレンドスコア
17
要約
この記事では、Rustを使用してGNOMEアプリケーションを構築する際のブループリントの重要性について説明しています。ブループリントは、アプリの設計や機能を視覚的に表現する手法であり、開発プロセスを効率化します。具体的な例や手順を通じて、開発者がどのようにブループリントを活用してアプリを計画し、実装するかを解説しています。
キーワード
This is Part 4 of a series taking a GNOME app from an empty directory to GNOME Circle. Part 3 walked through every file Builder generated for our gazette project. Now we're going to start changing things. If you're new to this stack and wondering why GTK and libadwaita are separate libraries, why GObject's type system feels like 1990s C, or why Flatpak ships its own runtime alongside your app, there's a short companion piece on the history of the stack . Skim it for context or skip it for code. The XML problem At the end of Part 3, we had a working app. A header bar, a hamburger menu, a "Hello, World!" label, and a window.ui file that looked like this: <?xml version="1.0" encoding="UTF-8"?> <interface> <requires lib= "gtk" version= "4.0" /> <requires lib= "Adw" version= "1.0" /> <template class= "GazetteWindow" parent= "AdwApplicationWindow" > <property name= "title" translatable= "yes" > Gazette </property> <property name= "default-width" > 800 </property> <property name= "default-height" > 600 </property> <property name= "content" > <object class= "AdwToolbarView" > <child type= "top" > <object class= "AdwHeaderBar" > <child type= "end" > <object class= "GtkMenuButton" > <property name= "primary" > True </property> <property name= "icon-name" > open-menu-symbolic </property> <property name= "tooltip-text" translatable= "yes" > Main Menu </property> <property name= "menu-model" > primary_menu </property> </object> </child> </object> </child> ... Read that aloud. Notice how much of it is structural noise — <object class="..."> , <property name="..."> , opening and closing tags wrapping single values. The actual information — "there's a header bar with a menu button on the right" — is buried under a layer of XML scaffolding. And we haven't even built any UI yet. This isn't a fixable problem in XML. It's how XML works. So GNOME has a different answer. Blueprint Blueprint is a markup language built for GTK. It compiles to the same .ui XML GTK has always loaded — the runtime artefact is identical — but the file you actually write looks like real code: using Gtk 4.0; using Adw 1; template $GazetteWindow : Adw.ApplicationWindow { title: _("Gazette"); default-width: 800; default-height: 600; content: Adw.ToolbarView { [top] Adw.HeaderBar { [end] MenuButton { primary: true; icon-name: "open-menu-symbolic"; tooltip-text: _("Main Menu"); menu-model: primary_menu; } } content: Label label { label: _("Hello, World!"); styles ["title-1"] }; }; } menu primary_menu { section { item { label: _("_Preferences"); action: "app.preferences"; } item { label: _("_Keyboard Shortcuts"); action: "app.shortcuts"; } item { label: _("_About Gazette"); action: "app.about"; } } } Same widget tree. Same template binding. Same translatable strings. About a third the line count, and you can actually scan the structure without your eyes glazing over. Two bits of syntax are worth knowing up front. The $ on $GazetteWindow is there because it's a user-defined type — the compiler can't validate it against a .gir , so the $ is your acknowledgement that you know what you're doing. Built-in types like Adw.ApplicationWindow never need it. And [top] , [end] are child types, equivalent to <child type="top"> in XML, placed immediately before the child they apply to. The rest — using for namespace imports, _("...") for translatable strings, styles ["..."] for CSS classes — you can pick up by porting. Wiring Blueprint into the build Blueprint is a separate compiler, not a built-in GTK feature. Before we change any UI, we need to teach Meson how to invoke it and update the GResource manifest to bundle the generated .ui files instead of the original ones. Is blueprint-compiler available? blueprint-compiler ships in the GNOME 50 SDK (which we upgraded to at the end of Part 3), so inside the Flatpak build sandbox we don't need to install anything. If you're targeting an older runtime where it isn't included, you'd add it as a module in the Flatpak manifest: "modules" : [ { "name" : "blueprint-compiler" , "buildsystem" : "meson" , "cleanup" : [ "*" ], "sources" : [ { "type" : "git" , "url" : "https://gitlab.gnome.org/GNOME/blueprint-compiler.git" , "tag" : "v0.20.4" } ] }, { "name" : "gazette" , ... } ] For us on GNOME 50, that block isn't necessary. Just confirming the binary exists is enough. Updating src/meson.build Open src/meson.build . We need to add a custom_target that runs blueprint-compiler batch-compile over our .blp files and produces matching .ui files in the build directory. Then we point the existing gnome.compile_resources call at those generated files via a dependency. Here's the relevant addition, sitting just above the existing compile_resources call: blueprints = custom_target ( 'blueprints' , input : files ( 'window.blp' , 'shortcuts-dialog.blp' , ), output : '.' , command : [ find_program ( 'blueprint-compiler' ), 'batch-compile' , '@OUTPUT@' , '@CURRENT_SOURCE_DIR@' , '@INPUT@' , ], ) gnome . compile_resources ( 'gazette' , 'gazette.gresource.xml' , dependencies : blueprints , gresource_bundle : true , install : true , install_dir : pkgdatadir , ) The dependencies: blueprints line is what ties the two together. Without it, Meson might try to compile resources before Blueprint has produced the .ui files, and you'll get a mystifying "file not found" error mid-build. gazette.gresource.xml is unchanged The resource manifest still references window.ui and shortcuts-dialog.ui . It doesn't mention .blp at all — and that's the part that catches people. <?xml version="1.0" encoding="UTF-8"?> <gresources> <gresource prefix= "/io/github/fromthearchitect/gazette" > <file preprocess= "xml-stripblanks" > window.ui </file> <file preprocess= "xml-stripblanks" > shortcuts-dialog.ui </file> </gresource> </gresources> That's because gnome.compile_resources looks for the listed files first in the source directory, then in the build directory — and our custom_target writes the generated .ui files into the build directory at exactly the right relative path. The Rust code referencing /io/github/fromthearchitect/gazette/window.ui doesn't know or care that the file went through a Blueprint compile step on the way in. This is the bit I want you to internalise: Blueprint is purely a build-time concern . The runtime artefact is identical. If anything goes wrong at runtime, it's a GTK problem, not a Blueprint problem — and the error messages will reference XML structures because that's what GTK sees. Updating POTFILES.in Open po/POTFILES.in . Builder's scaffold lists one source file alongside the desktop integration files: data/io.github.fromthearchitect.gazette.desktop.in data/io.github.fromthearchitect.gazette.metainfo.xml.in data/io.github.fromthearchitect.gazette.gschema.xml src/window.ui Two changes. First, point at window.blp instead of window.ui . Second, add shortcuts-dialog.blp — Builder doesn't list it, but the strings in there need extracting too: data/io.github.fromthearchitect.gazette.desktop.in data/io.github.fromthearchitect.gazette.metainfo.xml.in data/io.github.fromthearchitect.gazette.gschema.xml src/shortcuts-dialog.blp src/window.blp Why point at the .blp files rather than the generated .ui files? Gettext extracts strings by parsing source files for known patterns — _("...") in Blueprint, translatable="yes" in XML, gettext!() in Rust, etc. If POTFILES.in lists the generated .ui files, extraction will work, but the line numbers in your .po files will reference auto-generated paths in the build directory that change between builds. Pointing at the .blp source means translators see meaningful filenames and stable line numbers. The Meson i18n module's xgettext invocation already scans for _(...) and C_(...) calls regardless of file type, so listing .blp files works without any Blueprint-specific extractor. Porting the existing UI files Now the actual conversion. Two files: window.ui and shortcuts-dialog.ui . If you had dozens of files to convert, you'd run blueprint-compiler port from the project root and it would scan the project, generate .blp versions of every .ui file, and update the references for you. We've got two files, so it's just as quick to do it by hand and learn the syntax in the process. window.blp Delete src/window.ui . Create src/window.blp : using Gtk 4.0; using Adw 1; template $GazetteWindow : Adw.ApplicationWindow { title: _("Gazette"); default-width: 800; default-height: 600; content: Adw.ToolbarView { [top] Adw.HeaderBar { [end] MenuButton { primary: true; icon-name: "open-menu-symbolic"; tooltip-text: _("Main Menu"); menu-model: primary_menu; } } content: Label label { label: _("Hello, World!"); styles ["title-1"] }; }; } menu primary_menu { section { item { label: _("_Preferences"); action: "app.preferences"; } item { label: _("_Keyboard Shortcuts"); action: "app.shortcuts"; } item { label: _("_About Gazette"); action: "app.about"; } } } Compare against the original XML if you want — every property, child, and string carries across one-to-one. The one thing worth pointing out is Label label . In Blueprint, when you want to give a widget an ID (so the Rust side can grab it as a TemplateChild ), you put the ID after the type, with no id: keyword. It feels weird at first if you're coming from XML's <object class="GtkLabel" id="label"> , but it's consistent with how the rest of the language treats names. shortcuts-dialog.blp Builder's template for shortcuts-dialog.ui is built around Gtk.ShortcutsWindow , which was deprecated in GTK 4.18; libadwaita 1.8 shipped a successor, Adw.ShortcutsDialog . Since Part 3 put us on GNOME 50 (libadwaita 1.9), we'll port to the modern widget while we're already in the file. Delete src/shortcuts-dialog.ui and create src/shortcuts-dialog.blp : using Gtk 4.0; using Adw 1; Adw.ShortcutsDialog shortcuts_dialog { Adw.ShortcutsSection { title: C_("shortcut window", "General"); Adw.ShortcutsItem { title: C_("shortcut window", "Show Shortcuts"); action-name: "app.shortcuts"; } Adw.ShortcutsItem { title: C_(