Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

## 0.1.12 - 2026-08-26

- Resolve Web and desktop binaries from the selected Cargo workspace package
instead of requiring entry files at the client root.

## 0.1.11 - 2026-08-26

- Make the one-shot Igniter quick start complete in non-interactive
Expand Down
16 changes: 1 addition & 15 deletions lib/rekindle/build.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ defmodule Rekindle.Build do

with :ok <- validate_target(target),
:ok <- validate_profile(profile),
{:ok, target_config} <- enabled_target(project, target),
:ok <- entry_exists(project, target_config) do
{:ok, target_config} <- enabled_target(project, target) do
dispatch(project, target_config, profile, options)
end
end
Expand Down Expand Up @@ -50,19 +49,6 @@ defmodule Rekindle.Build do
end
end

defp entry_exists(project, target) do
path = Path.join(project.root, target.entry)

if File.regular?(path) do
:ok
else
error(
:missing_entry,
"#{target.name} is enabled but #{target.entry} is missing; restore the entry or disable the target"
)
end
end

defp dispatch(project, %{name: :web} = target, profile, options),
do: Rekindle.Web.Builder.build(project, target, profile, options)

Expand Down
22 changes: 10 additions & 12 deletions lib/rekindle/cargo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ defmodule Rekindle.Cargo do

@spec resolve(Metadata.t(), Rekindle.Config.t(), Target.t()) ::
{:ok, Metadata.package(), String.t()} | {:error, Error.t()}
def resolve(metadata, project, target) do
def resolve(metadata, _project, target) do
workspace_packages =
Enum.filter(metadata.packages, &MapSet.member?(metadata.workspace_members, &1.id))

with {:ok, package} <- package(workspace_packages, target.package),
{:ok, binary} <- binary(package, project, target) do
{:ok, binary} <- binary(package, target) do
{:ok, package, binary}
end
end
Expand Down Expand Up @@ -81,17 +81,12 @@ defmodule Rekindle.Cargo do
end
end

defp binary(package, project, target) do
expected_entry =
project.client_root
|> Path.join(Path.relative_to(target.entry, "client"))
|> Path.expand()
defp binary(package, target) do
name = target.binary || Atom.to_string(target.name)

candidates =
Enum.filter(package.targets, fn candidate ->
"bin" in candidate["kind"] and
(is_nil(target.binary) or candidate["name"] == target.binary) and
Path.expand(candidate["src_path"]) == expected_entry
"bin" in candidate["kind"] and candidate["name"] == name
end)

case candidates do
Expand All @@ -101,11 +96,14 @@ defmodule Rekindle.Cargo do
[] ->
error(
:binary_not_found,
"Cargo package #{inspect(package.name)} has no binary for #{target.entry}"
"Cargo package #{inspect(package.name)} has no binary named #{inspect(name)}"
)

_ ->
error(:ambiguous_binary, "Cargo metadata returned multiple binaries for #{target.entry}")
error(
:ambiguous_binary,
"Cargo metadata returned multiple binaries named #{inspect(name)}"
)
end
end

Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Rekindle.MixProject do
use Mix.Project

@version "0.1.11"
@version "0.1.12"
@source_url "https://github.com/refmdio/rekindle"
@docs_url "https://rekindle.hexdocs.pm"

Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/cargo_workspace/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[workspace]
members = ["crates/fixture_ui"]
resolver = "2"
18 changes: 18 additions & 0 deletions test/fixtures/cargo_workspace/crates/fixture_ui/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "fixture_ui"
version = "0.1.0"
edition = "2024"

[features]
desktop = []
web = []

[[bin]]
name = "desktop"
path = "src/bin/desktop.rs"
required-features = ["desktop"]

[[bin]]
name = "web"
path = "src/bin/web.rs"
required-features = ["web"]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fn main() {}
18 changes: 1 addition & 17 deletions test/rekindle/build_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ defmodule Rekindle.BuildTest do
Rekindle.build(:web)
end

test "rejects unknown and disabled targets before accessing an entry", %{root: root} do
test "rejects unknown and disabled targets before invoking Cargo", %{root: root} do
Application.put_env(:rekindle_build_test, Rekindle,
plugin: Rekindle.Plugin.GPUI,
targets: [desktop: [features: []]]
Expand All @@ -44,22 +44,6 @@ defmodule Rekindle.BuildTest do
Rekindle.build(:web, otp_app: :rekindle_build_test, project_root: root)
end

test "rejects missing canonical entries before dispatch", %{root: root} do
Application.put_env(:rekindle_build_test, Rekindle,
plugin: Rekindle.Plugin.GPUI,
targets: [desktop: [features: []]]
)

assert {:error, %Rekindle.Build.Error{kind: :missing_entry, message: message}} =
Rekindle.build(:desktop,
otp_app: :rekindle_build_test,
project_root: root,
profile: :release
)

assert message =~ "client/src/bin/desktop.rs"
end

test "returns a Cargo artifact without starting it", %{root: root} do
File.cp_r!("test/fixtures/cargo_project", Path.join(root, "client"))

Expand Down
41 changes: 41 additions & 0 deletions test/rekindle/cargo_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ defmodule Rekindle.CargoTest do
assert Path.basename(result.artifact) == "desktop"
end

test "builds a binary from a nested workspace package", %{project: project} do
File.rm_rf!(project.client_root)
File.cp_r!("test/fixtures/cargo_workspace", project.client_root)
target = %{target(:desktop) | package: "fixture_ui", features: ["desktop"]}

assert {:ok, result} = Cargo.build(project, target, :dev)
assert result.package == "fixture_ui"
assert result.binary == "desktop"
assert File.regular?(result.artifact)
end

test "discovers the Web Wasm artifact from Cargo messages", %{project: project} do
target = target(:web)
cargo = rustup_path("cargo")
Expand Down Expand Up @@ -101,6 +112,36 @@ defmodule Rekindle.CargoTest do
Cargo.build(project, target, :dev)
end

test "resolves a binary from a nested workspace package", %{project: project} do
package = cargo_package(project, "fixture_ui", "fixture_ui 0.1.0")

nested = %{
package
| manifest_path: Path.join(project.client_root, "crates/fixture_ui/Cargo.toml"),
targets:
Enum.map(package.targets, fn target ->
%{
target
| "src_path" =>
Path.join(
project.client_root,
"crates/fixture_ui/src/bin/#{target["name"]}.rs"
)
}
end)
}

metadata = %Cargo.Metadata{
packages: [nested],
workspace_members: MapSet.new([nested.id]),
target_directory: Path.join(project.client_root, "target")
}

configured = %{target(:web) | package: "fixture_ui"}

assert {:ok, ^nested, "web"} = Cargo.resolve(metadata, project, configured)
end

test "passes the configured Cargo selection and build arguments exactly", %{project: project} do
{cargo, arguments_file, _started_file, metadata_cwd_file, build_cwd_file} =
fake_cargo(project, :artifact)
Expand Down
Loading