diff --git a/Cargo.toml b/Cargo.toml index ddd3b20a..197ea29d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,3 @@ libc = "0.2" # Used for PSBTs base64 = "0.13" - -[patch.crates-io] - diff --git a/contrib/guix/README.md b/contrib/guix/README.md new file mode 100644 index 00000000..c6dacda9 --- /dev/null +++ b/contrib/guix/README.md @@ -0,0 +1,176 @@ +## Bootstrappable Liana builds + +This repository contains the scripts to perform [reproducible](https://reproducible-builds.org/) and +[bootstrappable](https://bootstrappable.org/) builds of Liana using [Guix](https://guix.gnu.org/), a +functional package manager. + +For a short high-level introduction to the purpose of bootstrappable builds, see [this +talk](https://www.youtube.com/watch?v=I2iShmUTEl8) by Carl Dong in the context of the Bitcoin Core +project. + +For now, only `x86_64` Linux binaries are supported. We aim to extend bootstrappable builds to more +targets in the future. + + +### Installation + +See the very [detailed document about GUIX installation in the Bitcoin Core +project](https://github.com/bitcoin/bitcoin/blob/master/contrib/guix/INSTALL.md). Almost all of it +is directly applicable to Liana as well. + + +### Usage + +First of all, you need to decide on the amount of trust in the building process. Of course this +decision needs to fall within a broader threat model, but it is still interesting to consider. + +*(Note: this section was taken from the Bitcoin Core documentation and initially written by Carl +Dong.)* + +#### Choosing your security model + +No matter how you installed Guix, you need to decide on your security model for +building packages with Guix. + +Guix allows us to achieve better binary security by using our CPU time to build +everything from scratch. However, it doesn't sacrifice user choice in pursuit of +this: users can decide whether or not to use **substitutes** (pre-built +packages). + +##### Option 1: Building with substitutes + +###### Step 1: Authorize the signing keys + +Depending on the installation procedure you followed, you may have already +authorized the Guix build farm key. In particular, the official shell installer +script asks you if you want the key installed, and the debian distribution +package authorized the key during installation. + +You can check the current list of authorized keys at `/etc/guix/acl`. + +At the time of writing, a `/etc/guix/acl` with just the Guix build farm key +authorized looks something like: + +```lisp +(acl + (entry + (public-key + (ecc + (curve Ed25519) + (q #8D156F295D24B0D9A86FA5741A840FF2D24F60F7B6C4134814AD55625971B394#) + ) + ) + (tag + (guix import) + ) + ) + ) +``` + +If you've determined that the official Guix build farm key hasn't been +authorized, and you would like to authorize it, run the following as root: + +``` +guix archive --authorize < /var/guix/profiles/per-user/root/current-guix/share/guix/ci.guix.gnu.org.pub +``` + +If +`/var/guix/profiles/per-user/root/current-guix/share/guix/ci.guix.gnu.org.pub` +doesn't exist, try: + +```sh +guix archive --authorize < /share/guix/ci.guix.gnu.org.pub +``` + +Where `` is likely: +- `/usr` if you installed from a distribution package +- `/usr/local` if you installed Guix from source and didn't supply any + prefix-modifying flags to Guix's `./configure` + +For dongcarl's substitute server at https://guix.carldong.io, run as root: + +```sh +wget -qO- 'https://guix.carldong.io/signing-key.pub' | guix archive --authorize +``` + +To remove previously authorized keys, simply edit `/etc/guix/acl` and remove the +`(entry (public-key ...))` entry. + +###### Step 2: Specify the substitute servers + +Once its key is authorized, the official Guix build farm at +https://ci.guix.gnu.org is automatically used unless the `--no-substitutes` flag +is supplied. This default list of substitute servers is overridable both on a +`guix-daemon` level and when you invoke `guix` commands. See examples below for +the various ways of adding dongcarl's substitute server after having [authorized +his signing key](#step-1-authorize-the-signing-keys). + +Change the **default list** of substitute servers by starting `guix-daemon` with +the `--substitute-urls` option (you will likely need to edit your init script): + +```sh +guix-daemon --substitute-urls='https://guix.carldong.io https://ci.guix.gnu.org' +``` + +Override the default list of substitute servers by passing the +`--substitute-urls` option for invocations of `guix` commands: + +```sh +guix --substitute-urls='https://guix.carldong.io https://ci.guix.gnu.org' +``` + +For scripts under `./contrib/guix`, set the `SUBSTITUTE_URLS` environment +variable: + +```sh +export SUBSTITUTE_URLS='https://guix.carldong.io https://ci.guix.gnu.org' +``` + +##### Option 2: Disabling substitutes on an ad-hoc basis + +If you prefer not to use any substitutes, make sure to supply `--no-substitutes` +like in the following snippet. The first build will take a while, but the +resulting packages will be cached for future builds. + +For direct invocations of `guix`: +```sh +guix --no-substitutes +``` + +The build script doesn't yet allow you to provide this via an environment variable, you'd +have to modify it yourself (or contribute a patch :p). + +##### Option 3: Disabling substitutes by default + +`guix-daemon` accepts a `--no-substitutes` flag, which will make sure that, +unless otherwise overridden by a command line invocation, no substitutes will be +used. + +If you start `guix-daemon` using an init script, you can edit said script to +supply this flag. + + +#### Building Liana + +For both the daemon (`lianad`) and the GUI (`liana-gui`), the [`guix-build.sh`](./guix-build.sh) +script will vendor the dependencies of the project (as pinned in the `Cargo.lock`) and start a [GUIX +container](https://guix.gnu.org/manual/devel/en/html_node/Invoking-guix-shell.html) that will run +the `build.sh` script that will take care of building the dependencies and the project using `cargo`. + +To start a build, simply run the `guix-build.sh` script from the root of the repository: +``` +$ ./contrib/guix/guix-build.sh +``` + +The script shouldn't contain any bash-ism, so it should work with other shells as well. + + +#### Customization + +Environment variables are available to configure the build. + +`BUILD_ROOT` allows you to specify the root folder that will contain the vendored dependencies' +source, the build cache as well as the resulting binaries (in `$BUILD_ROOT/out`). + +`JOBS` allows you to specify the number of cores to dedicate to the build both for bootstrapping the +toolchain with GUIX and building the project using `cargo`. diff --git a/contrib/guix/build.sh b/contrib/guix/build.sh new file mode 100755 index 00000000..7c9f00a5 --- /dev/null +++ b/contrib/guix/build.sh @@ -0,0 +1,45 @@ +# ========================================================================== +# The script ran within the GUIX container to build the Liana daemon or GUI. +# ========================================================================== + +set -ex + +# Instruct cargo to use our vendored sources +mkdir -p ~/.cargo +cat <~/.cargo/config.toml +[source.vendored_sources] +directory = "/vendor" + +[source.crates-io] +replace-with = "vendored_sources" + +[source."https://github.com/darosior/rust-miniscript"] +git = "https://github.com/darosior/rust-miniscript" +branch = "multipath_descriptors_on_8.0" +replace-with = "vendored_sources" + +[source."https://github.com/revault/liana"] +git = "https://github.com/revault/liana" +branch = "master" +replace-with = "vendored_sources" +EOF + +# We need to set RUSTC_BOOTSTRAP=1 as a workaround to be able to use unstable +# features in the GUI dependencies +RUSTC_BOOTSTRAP=1 cargo -vvv \ + --color always \ + --frozen \ + --offline \ + rustc \ + --jobs "$JOBS" \ + --release \ + --target-dir "/out" + +# Assume 64bits. Even bitcoind doesn't ship 32bits binaries for x86. +# FIXME: is there a cleaner way than using patchelf for this? +patchelf --set-interpreter /lib64/ld-linux-x86-64.so.2 "/out/release/$BINARY_NAME" + +# FIXME: Find a way to use GUIX_LD_WRAPPER_DISABLE_RPATH=yes instead +patchelf --remove-rpath "/out/release/$BINARY_NAME" + +set +ex diff --git a/contrib/guix/guix-build.sh b/contrib/guix/guix-build.sh new file mode 100755 index 00000000..816b3466 --- /dev/null +++ b/contrib/guix/guix-build.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env sh + +set -ex + +# How many cores to allocate to Guix building. +JOBS="${JOBS:-$(nproc)}" + +# The binary to check the hash of downloaded archives. +SHASUM_BIN="${SHASUM_BIN:-sha256sum}" + +# We do everything in a single directory. That's the root of it, configurable +# through the environment. +BUILD_ROOT="${BUILD_ROOT:-$(mktemp -d)}" + +# Various folders we expose to the container. The vendor directory will contain +# the sources of all our dependencies. Because we restrict network access from +# within the container, this is pulled beforehand. +# The out directory will contain the resulting binaries. It's wired to the --target-dir +# for a cargo build. +VENDOR_DIR="$BUILD_ROOT/vendor" +OUT_DIR="${OUT_DIR:-"$BUILD_ROOT/out"}" +BIN_DIR="${BIN_DIR:-"$BUILD_ROOT/bin"}" + +# Create the directory if it doesn't exist already +maybe_create_dir() { + if ! [ -d "$@" ]; then + mkdir -p "$@" + fi +} +maybe_create_dir "$BIN_DIR" + +# That's what Guix comes with. +RUST_VERSION="1.60.0" +CARGO_BIN="$BIN_DIR/cargo" + +# First off get the cargo binary to run on the host to vendor dependencies. +# We assume the host is a 64bit Linux system. +if ! [ -f "$CARGO_BIN" ]; then + ARCHIVE_PATH="$BIN_DIR/rust-for-cargo.tar.gz" + curl -o "$ARCHIVE_PATH" "https://static.rust-lang.org/dist/rust-$RUST_VERSION-x86_64-unknown-linux-gnu.tar.gz" + echo "b8a4c3959367d053825e31f90a5eb86418eb0d80cacda52bfa80b078e18150d5 $ARCHIVE_PATH" | $SHASUM_BIN -c + # Path of the cargo binary within the archive + CARGO_BIN_PATH="rust-$RUST_VERSION-x86_64-unknown-linux-gnu/cargo/bin/cargo" + ( cd $BIN_DIR && tar -xzf $ARCHIVE_PATH $CARGO_BIN_PATH && mv $CARGO_BIN_PATH $CARGO_BIN ) +fi + +# Execute "$@" in a pinned, possibly older version of Guix, for reproducibility +# across time. +time_machine() { + guix time-machine --url=https://git.savannah.gnu.org/git/guix.git \ + --commit=059d38dc3f8b087f4a42df586daeb05761ee18d7 \ + --cores="$JOBS" \ + --keep-failed \ + --fallback \ + -- "$@" +} + +# Build both the daemon (at the root of the repository) and the GUI (in gui/) +for project_folder in "" "gui"; do + PROJECT_ROOT="$PWD/$project_folder" + PROJECT_VENDOR_DIR="$VENDOR_DIR/$project_folder" + PROJECT_OUT_DIR="$OUT_DIR/$project_folder" + PROJECT_PATCHES_ROOT="$PWD/contrib/guix/patches/$project_folder" + + project_needs_patches() { + test $(ls -A1q "$PROJECT_PATCHES_ROOT" |grep patch) + } + + maybe_create_dir "$PROJECT_OUT_DIR" + + # Pull the sources of our dependencies before building them in the container. + if ! [ -d "$PROJECT_VENDOR_DIR" ]; then + # Download the dependencies + ( cd "$project_folder" && $CARGO_BIN vendor "$PROJECT_VENDOR_DIR" ) + + # Patch some dependencies sources if needed for this project + if project_needs_patches; then + ( + cd "$PROJECT_VENDOR_DIR" + for patch_file in $(ls "$PROJECT_PATCHES_ROOT"); do + patch -p1 < "$PROJECT_PATCHES_ROOT/$patch_file" + done + ) + + # Some of the checksums will be incorrect. Instead of cherry-picking remove them + # altogether, since they aren't useful anyways (see comment below). + for dep in $(ls "$PROJECT_VENDOR_DIR"); do + echo "{\"files\":{}}" > "$PROJECT_VENDOR_DIR/$dep/.cargo-checksum.json" + done + fi + fi + + cp "$PROJECT_ROOT/Cargo.lock" "$BUILD_ROOT/Cargo.lock" + if project_needs_patches; then + # Remove the checksums from the Cargo.lock. In the container `cargo rustc` would compare + # them against the .cargo-checksum.json to make sure they weren't tampered with since they + # where vendored. But we just removed the checksums from the .cargo-checksum.json. + # There is little point in checking integrity between the above vendor step and now anyways. + # What matters is checking integrity after downloading the crates from the internet and + # `cargo vendor` does that already. + sed -i '/checksum/d' "$BUILD_ROOT/Cargo.lock" + fi + + # FIXME: find a cleaner way to get the binary name, or get rid of patchelf entirely + # Note: we also rely on it in manifest.scm + if [ "$project_folder" = "" ]; then + BINARY_NAME="lianad" + elif [ "$project_folder" = "gui" ]; then + BINARY_NAME="liana-gui" + else + echo "Can't determine binary name" + exit 1 + fi + + # Bootstrap a reproducible environment as specified by the manifest in an isolated + # container, and build the project. + # NOTE: it looks like "--rebuild-cache" is necessary for the BINARY_NAME variable to + # be taken into account when building the container (otherwise the GUI container could + # miss some dependencies). + BINARY_NAME="$BINARY_NAME" time_machine shell --no-cwd \ + --expose="$PROJECT_ROOT/src=/liana/src" \ + --expose="$PWD/gui/static=/liana/static" \ + --expose="$PROJECT_ROOT/Cargo.toml=/liana/Cargo.toml" \ + --expose="$BUILD_ROOT/Cargo.lock=/liana/Cargo.lock" \ + --expose="$PWD/contrib/guix/build.sh=/liana/build.sh" \ + --expose="$PROJECT_VENDOR_DIR=/vendor" \ + --share="$PROJECT_OUT_DIR=/out" \ + --cores="$JOBS" \ + --container \ + --pure \ + --fallback \ + --rebuild-cache \ + -m $PWD/contrib/guix/manifest.scm \ + -- env CC=gcc VENDOR_DIR="$PROJECT_VENDOR_DIR" TARGET_DIR="$PROJECT_OUT_DIR" BINARY_NAME="$BINARY_NAME" JOBS="$JOBS" \ + /bin/sh -c "cd /liana && ./build.sh" +done + +set +ex + +echo "Build successful. Output available at $OUT_DIR" diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm new file mode 100644 index 00000000..5ea7a81a --- /dev/null +++ b/contrib/guix/manifest.scm @@ -0,0 +1,15 @@ +(specifications->manifest + (append + (list "rust" + "rust:cargo" + "coreutils" + "patchelf" + "gcc-toolchain@10.3.0") + ;; Additional dependencies for building the GUI + (let ((binary_name (getenv "BINARY_NAME"))) + (if + (string=? binary_name "liana-gui") + (list "pkg-config" + "eudev" + "fontconfig") + '())))) diff --git a/contrib/guix/patches/gui/iced_default_enum.patch b/contrib/guix/patches/gui/iced_default_enum.patch new file mode 100644 index 00000000..e040e9aa --- /dev/null +++ b/contrib/guix/patches/gui/iced_default_enum.patch @@ -0,0 +1,459 @@ +commit f71bc71bf6724a1c5e2a3246dd8bd0a90fcf0e15 +Author: Antoine Poinsot +Date: Thu Dec 1 15:38:32 2022 +0100 + + Do not use, or special-case them, unstable features as of 1.60.0 + + This is in order to make the GUI MSRV effectively 1.60.0. + + Default derivation on enums is removed. + + Usage of bool_to_option is removed. + + Generic associated types is unfortunately special cased as enabled. This + forces use to set RUSTC_BOOTSTRAP=1 in the Guix container... + +diff --git a/iced_glow/src/lib.rs b/iced_glow/src/lib.rs +index e3690a69..b5b9cc38 100644 +--- a/iced_glow/src/lib.rs ++++ b/iced_glow/src/lib.rs +@@ -20,6 +20,7 @@ + #![forbid(rust_2018_idioms)] + #![allow(clippy::inherent_to_string, clippy::type_complexity)] + #![cfg_attr(docsrs, feature(doc_cfg))] ++#![feature(generic_associated_types)] + + pub use glow; + +diff --git a/iced_graphics/src/lib.rs b/iced_graphics/src/lib.rs +index d39dd90c..876b478f 100644 +--- a/iced_graphics/src/lib.rs ++++ b/iced_graphics/src/lib.rs +@@ -21,6 +21,7 @@ + #![forbid(rust_2018_idioms)] + #![allow(clippy::inherent_to_string, clippy::type_complexity)] + #![cfg_attr(docsrs, feature(doc_cfg))] ++#![feature(generic_associated_types)] + mod antialiasing; + mod error; + mod primitive; +diff --git a/iced_native/src/program/state.rs b/iced_native/src/program/state.rs +index 8ae1cacb..25a3028b 100644 +--- a/iced_native/src/program/state.rs ++++ b/iced_native/src/program/state.rs +@@ -120,7 +120,11 @@ where + .iter() + .zip(event_statuses) + .filter_map(|(event, status)| { +- matches!(status, event::Status::Ignored).then_some(event) ++ if matches!(status, event::Status::Ignored) { ++ Some(event) ++ } else { ++ None ++ } + }) + .cloned() + .collect(); +diff --git a/iced_style/src/theme.rs b/iced_style/src/theme.rs +index d7ebb827..224a04fc 100644 +--- a/iced_style/src/theme.rs ++++ b/iced_style/src/theme.rs +@@ -25,10 +25,9 @@ use iced_core::{Background, Color, Vector}; + use std::rc::Rc; + + /// A built-in theme. +-#[derive(Debug, Clone, PartialEq, Default)] ++#[derive(Debug, Clone, PartialEq)] + pub enum Theme { + /// The built-in light variant. +- #[default] + Light, + /// The built-in dark variant. + Dark, +@@ -61,6 +60,12 @@ impl Theme { + } + } + ++impl Default for Theme { ++ fn default() -> Self { ++ Self::Light ++ } ++} ++ + /// A [`Theme`] with a customized [`Palette`]. + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct Custom { +@@ -79,15 +84,19 @@ impl Custom { + } + + /// The style of an application. +-#[derive(Default)] + pub enum Application { + /// The default style. +- #[default] + Default, + /// A custom style. + Custom(Box>), + } + ++impl Default for Application { ++ fn default() -> Self { ++ Self::Default ++ } ++} ++ + impl application::StyleSheet for Theme { + type Style = Application; + +@@ -119,10 +128,11 @@ impl From application::Appearance> for Application { + } + + /// The style of a button. +-#[derive(Default)] ++/* ++ * Button ++ */ + pub enum Button { + /// The primary style. +- #[default] + Primary, + /// The secondary style. + Secondary, +@@ -138,6 +148,12 @@ pub enum Button { + Custom(Box>), + } + ++impl Default for Button { ++ fn default() -> Self { ++ Self::Primary ++ } ++} ++ + impl button::StyleSheet for Theme { + type Style = Button; + +@@ -227,10 +243,11 @@ impl button::StyleSheet for Theme { + } + + /// The style of a checkbox. +-#[derive(Default)] ++/* ++ * Checkbox ++ */ + pub enum Checkbox { + /// The primary style. +- #[default] + Primary, + /// The secondary style. + Secondary, +@@ -242,6 +259,12 @@ pub enum Checkbox { + Custom(Box>), + } + ++impl Default for Checkbox { ++ fn default() -> Self { ++ Self::Primary ++ } ++} ++ + impl checkbox::StyleSheet for Theme { + type Style = Checkbox; + +@@ -339,10 +362,11 @@ fn checkbox_appearance( + } + + /// The style of a container. +-#[derive(Default)] ++/* ++ * Container ++ */ + pub enum Container { + /// No style. +- #[default] + Transparent, + /// A simple box. + Box, +@@ -350,6 +374,12 @@ pub enum Container { + Custom(Box>), + } + ++impl Default for Container { ++ fn default() -> Self { ++ Self::Transparent ++ } ++} ++ + impl From container::Appearance> for Container { + fn from(f: fn(&Theme) -> container::Appearance) -> Self { + Self::Custom(Box::new(f)) +@@ -387,15 +417,19 @@ impl container::StyleSheet for fn(&Theme) -> container::Appearance { + } + + /// The style of a slider. +-#[derive(Default)] + pub enum Slider { + /// The default style. +- #[default] + Default, + /// A custom style. + Custom(Box>), + } + ++impl Default for Slider { ++ fn default() -> Self { ++ Self::Default ++ } ++} ++ + impl slider::StyleSheet for Theme { + type Style = Slider; + +@@ -468,15 +502,20 @@ impl slider::StyleSheet for Theme { + } + + /// The style of a menu. +-#[derive(Clone, Default)] ++#[derive(Clone)] + pub enum Menu { + /// The default style. +- #[default] + Default, + /// A custom style. + Custom(Rc>), + } + ++impl Default for Menu { ++ fn default() -> Self { ++ Self::Default ++ } ++} ++ + impl menu::StyleSheet for Theme { + type Style = Menu; + +@@ -510,10 +549,9 @@ impl From for Menu { + } + + /// The style of a pick list. +-#[derive(Clone, Default)] ++#[derive(Clone)] + pub enum PickList { + /// The default style. +- #[default] + Default, + /// A custom style. + Custom( +@@ -522,6 +560,12 @@ pub enum PickList { + ), + } + ++impl Default for PickList { ++ fn default() -> Self { ++ Self::Default ++ } ++} ++ + impl pick_list::StyleSheet for Theme { + type Style = PickList; + +@@ -565,15 +609,19 @@ impl pick_list::StyleSheet for Theme { + } + + /// The style of a radio button. +-#[derive(Default)] + pub enum Radio { + /// The default style. +- #[default] + Default, + /// A custom style. + Custom(Box>), + } + ++impl Default for Radio { ++ fn default() -> Self { ++ Self::Default ++ } ++} ++ + impl radio::StyleSheet for Theme { + type Style = Radio; + +@@ -620,15 +668,19 @@ impl radio::StyleSheet for Theme { + } + + /// The style of a toggler. +-#[derive(Default)] + pub enum Toggler { + /// The default style. +- #[default] + Default, + /// A custom style. + Custom(Box>), + } + ++impl Default for Toggler { ++ fn default() -> Self { ++ Self::Default ++ } ++} ++ + impl toggler::StyleSheet for Theme { + type Style = Toggler; + +@@ -687,15 +739,19 @@ impl toggler::StyleSheet for Theme { + } + + /// The style of a pane grid. +-#[derive(Default)] + pub enum PaneGrid { + /// The default style. +- #[default] + Default, + /// A custom style. + Custom(Box>), + } + ++impl Default for PaneGrid { ++ fn default() -> Self { ++ Self::Default ++ } ++} ++ + impl pane_grid::StyleSheet for Theme { + type Style = PaneGrid; + +@@ -729,10 +785,11 @@ impl pane_grid::StyleSheet for Theme { + } + + /// The style of a progress bar. +-#[derive(Default)] ++/* ++ * Progress Bar ++ */ + pub enum ProgressBar { + /// The primary style. +- #[default] + Primary, + /// The success style. + Success, +@@ -742,6 +799,12 @@ pub enum ProgressBar { + Custom(Box>), + } + ++impl Default for ProgressBar { ++ fn default() -> Self { ++ Self::Primary ++ } ++} ++ + impl From progress_bar::Appearance> for ProgressBar { + fn from(f: fn(&Theme) -> progress_bar::Appearance) -> Self { + Self::Custom(Box::new(f)) +@@ -782,15 +845,22 @@ impl progress_bar::StyleSheet for fn(&Theme) -> progress_bar::Appearance { + } + + /// The style of a rule. +-#[derive(Default)] ++/* ++ * Rule ++ */ + pub enum Rule { + /// The default style. +- #[default] + Default, + /// A custom style. + Custom(Box>), + } + ++impl Default for Rule { ++ fn default() -> Self { ++ Self::Default ++ } ++} ++ + impl From rule::Appearance> for Rule { + fn from(f: fn(&Theme) -> rule::Appearance) -> Self { + Self::Custom(Box::new(f)) +@@ -824,15 +894,19 @@ impl rule::StyleSheet for fn(&Theme) -> rule::Appearance { + } + + /// The style of a scrollable. +-#[derive(Default)] + pub enum Scrollable { + /// The default style. +- #[default] + Default, + /// A custom style. + Custom(Box>), + } + ++impl Default for Scrollable { ++ fn default() -> Self { ++ Self::Default ++ } ++} ++ + impl scrollable::StyleSheet for Theme { + type Style = Scrollable; + +@@ -889,15 +963,23 @@ impl scrollable::StyleSheet for Theme { + } + + /// The style of text. +-#[derive(Clone, Copy, Default)] ++/* ++ * Text ++ */ ++#[derive(Clone, Copy)] + pub enum Text { + /// The default style. +- #[default] + Default, + /// Colored text. + Color(Color), + } + ++impl Default for Text { ++ fn default() -> Self { ++ Self::Default ++ } ++} ++ + impl From for Text { + fn from(color: Color) -> Self { + Text::Color(color) +@@ -916,15 +998,19 @@ impl text::StyleSheet for Theme { + } + + /// The style of a text input. +-#[derive(Default)] + pub enum TextInput { + /// The default style. +- #[default] + Default, + /// A custom style. + Custom(Box>), + } + ++impl Default for TextInput { ++ fn default() -> Self { ++ Self::Default ++ } ++} ++ + impl text_input::StyleSheet for Theme { + type Style = TextInput; + +diff --git a/iced_wgpu/src/lib.rs b/iced_wgpu/src/lib.rs +index dcb699e8..5b3cda57 100644 +--- a/iced_wgpu/src/lib.rs ++++ b/iced_wgpu/src/lib.rs +@@ -37,6 +37,7 @@ + #![forbid(rust_2018_idioms)] + #![allow(clippy::inherent_to_string, clippy::type_complexity)] + #![cfg_attr(docsrs, feature(doc_cfg))] ++#![feature(generic_associated_types)] + + pub mod settings; + pub mod triangle; +