From 8555b02752c8f7db6c4f2ea52e501076f94b1387 Mon Sep 17 00:00:00 2001 From: eric Date: Thu, 12 Mar 2026 18:58:43 +0100 Subject: [PATCH] Inital commit --- BUILD.bazel | 1 + MODULE.bazel | 36 ++ MODULE.bazel.lock | 631 ++++++++++++++++++++++++ README.md | 44 ++ bazel-bin | 1 + bazel-out | 1 + bazel-testlogs | 1 + bazel-wails_tools | 1 + examples/basic_go/BUILD.bazel | 1 + examples/basic_go/README.md | 50 ++ examples/bun_vite/BUILD.bazel | 1 + examples/bun_vite/README.md | 25 + go.mod | 3 + wails/BUILD.bazel | 12 + wails/README.md | 28 ++ wails/defs.bzl | 17 + wails/private/build_assets.bzl | 58 +++ wails/private/common.bzl | 58 +++ wails/private/generate_bindings.bzl | 205 ++++++++ wails/private/macros.bzl | 27 + wails/private/run.bzl | 87 ++++ wails/private/toolchain.bzl | 48 ++ wails/tools/BUILD.bazel | 25 + wails/tools/build_assets_action.go | 225 +++++++++ wails/tools/generate_bindings_action.go | 239 +++++++++ wails/tools/launch_app.go | 139 ++++++ wails_bun/BUILD.bazel | 5 + wails_bun/README.md | 23 + wails_bun/defs.bzl | 13 + wails_bun/private/dev_session.bzl | 198 ++++++++ wails_bun/private/frontend_dist.bzl | 274 ++++++++++ wails_bun/private/macros.bzl | 71 +++ wails_bun/tools/BUILD.bazel | 25 + wails_bun/tools/bun_dev_session.go | 177 +++++++ wails_bun/tools/frontend_dev_server.go | 139 ++++++ wails_bun/tools/frontend_dist_action.go | 423 ++++++++++++++++ 36 files changed, 3312 insertions(+) create mode 100644 BUILD.bazel create mode 100644 MODULE.bazel create mode 100644 MODULE.bazel.lock create mode 100644 README.md create mode 120000 bazel-bin create mode 120000 bazel-out create mode 120000 bazel-testlogs create mode 120000 bazel-wails_tools create mode 100644 examples/basic_go/BUILD.bazel create mode 100644 examples/basic_go/README.md create mode 100644 examples/bun_vite/BUILD.bazel create mode 100644 examples/bun_vite/README.md create mode 100644 go.mod create mode 100644 wails/BUILD.bazel create mode 100644 wails/README.md create mode 100644 wails/defs.bzl create mode 100644 wails/private/build_assets.bzl create mode 100644 wails/private/common.bzl create mode 100644 wails/private/generate_bindings.bzl create mode 100644 wails/private/macros.bzl create mode 100644 wails/private/run.bzl create mode 100644 wails/private/toolchain.bzl create mode 100644 wails/tools/BUILD.bazel create mode 100644 wails/tools/build_assets_action.go create mode 100644 wails/tools/generate_bindings_action.go create mode 100644 wails/tools/launch_app.go create mode 100644 wails_bun/BUILD.bazel create mode 100644 wails_bun/README.md create mode 100644 wails_bun/defs.bzl create mode 100644 wails_bun/private/dev_session.bzl create mode 100644 wails_bun/private/frontend_dist.bzl create mode 100644 wails_bun/private/macros.bzl create mode 100644 wails_bun/tools/BUILD.bazel create mode 100644 wails_bun/tools/bun_dev_session.go create mode 100644 wails_bun/tools/frontend_dev_server.go create mode 100644 wails_bun/tools/frontend_dist_action.go diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..316774c --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1 @@ +exports_files(["README.md"]) diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000..a270b92 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,36 @@ +module( + name = "rules_wails", + version = "0.1.0", +) + +bazel_dep( + name = "rules_go", + repo_name = "io_bazel_rules_go", + version = "0.60.0", +) +bazel_dep(name = "rules_bun", version = "0.2.3") +bazel_dep(name = "rules_shell", version = "0.6.1") + +archive_override( + module_name = "rules_bun", + strip_prefix = "rules_bun", + urls = ["https://git.dgren.dev/eric/rules_bun/archive/v0.2.3.tar.gz"], +) + +bun_ext = use_extension("@rules_bun//bun:extensions.bzl", "bun") +use_repo( + bun_ext, + "bun_darwin_aarch64", + "bun_darwin_x64", + "bun_linux_aarch64", + "bun_linux_x64", + "bun_windows_x64", +) + +register_toolchains( + "@rules_bun//bun:darwin_aarch64_toolchain", + "@rules_bun//bun:darwin_x64_toolchain", + "@rules_bun//bun:linux_aarch64_toolchain", + "@rules_bun//bun:linux_x64_toolchain", + "@rules_bun//bun:windows_x64_toolchain", +) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock new file mode 100644 index 0000000..0231026 --- /dev/null +++ b/MODULE.bazel.lock @@ -0,0 +1,631 @@ +{ + "lockFileVersion": 26, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16", + "https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1", + "https://bcr.bazel.build/modules/abseil-cpp/20250512.1/MODULE.bazel": "d209fdb6f36ffaf61c509fcc81b19e81b411a999a934a032e10cd009a0226215", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/MODULE.bazel": "51f2312901470cdab0dbdf3b88c40cd21c62a7ed58a3de45b365ddc5b11bcab2", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json": "cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c", + "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896", + "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", + "https://bcr.bazel.build/modules/apple_support/1.21.0/MODULE.bazel": "ac1824ed5edf17dee2fdd4927ada30c9f8c3b520be1b5fd02a5da15bc10bff3e", + "https://bcr.bazel.build/modules/apple_support/1.21.1/MODULE.bazel": "5809fa3efab15d1f3c3c635af6974044bac8a4919c62238cce06acee8a8c11f1", + "https://bcr.bazel.build/modules/apple_support/1.24.2/MODULE.bazel": "0e62471818affb9f0b26f128831d5c40b074d32e6dda5a0d3852847215a41ca4", + "https://bcr.bazel.build/modules/apple_support/1.24.2/source.json": "2c22c9827093250406c5568da6c54e6fdf0ef06238def3d99c71b12feb057a8d", + "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", + "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", + "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", + "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", + "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", + "https://bcr.bazel.build/modules/bazel_features/1.23.0/MODULE.bazel": "fd1ac84bc4e97a5a0816b7fd7d4d4f6d837b0047cf4cbd81652d616af3a6591a", + "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", + "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", + "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", + "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", + "https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6", + "https://bcr.bazel.build/modules/bazel_features/1.36.0/MODULE.bazel": "596cb62090b039caf1cad1d52a8bc35cf188ca9a4e279a828005e7ee49a1bec3", + "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/source.json": "fcd4396b2df85f64f2b3bb436ad870793ecf39180f1d796f913cc9276d355309", + "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", + "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", + "https://bcr.bazel.build/modules/buildozer/8.5.1/MODULE.bazel": "a35d9561b3fc5b18797c330793e99e3b834a473d5fbd3d7d7634aafc9bdb6f8f", + "https://bcr.bazel.build/modules/buildozer/8.5.1/source.json": "e3386e6ff4529f2442800dee47ad28d3e6487f36a1f75ae39ae56c70f0cd2fbd", + "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", + "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", + "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", + "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", + "https://bcr.bazel.build/modules/gazelle/0.36.0/source.json": "0823f097b127e0201ae55d85647c94095edfe27db0431a7ae880dcab08dfaa04", + "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", + "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", + "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", + "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", + "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108", + "https://bcr.bazel.build/modules/googletest/1.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46", + "https://bcr.bazel.build/modules/googletest/1.17.0/source.json": "38e4454b25fc30f15439c0378e57909ab1fd0a443158aa35aec685da727cd713", + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000", + "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", + "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74", + "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9", + "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", + "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", + "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", + "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", + "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", + "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", + "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", + "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", + "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", + "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", + "https://bcr.bazel.build/modules/protobuf/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", + "https://bcr.bazel.build/modules/protobuf/32.1/MODULE.bazel": "89cd2866a9cb07fee9ff74c41ceace11554f32e0d849de4e23ac55515cfada4d", + "https://bcr.bazel.build/modules/protobuf/33.4/MODULE.bazel": "114775b816b38b6d0ca620450d6b02550c60ceedfdc8d9a229833b34a223dc42", + "https://bcr.bazel.build/modules/protobuf/33.4/source.json": "555f8686b4c7d6b5ba731fbea13bf656b4bfd9a7ff629c1d9d3f6e1d6155de79", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", + "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34", + "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/source.json": "6900fdc8a9e95866b8c0d4ad4aba4d4236317b5c1cd04c502df3f0d33afed680", + "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", + "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/MODULE.bazel": "b4963dda9b31080be1905ef085ecd7dd6cd47c05c79b9cdf83ade83ab2ab271a", + "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/source.json": "2ff292be6ef3340325ce8a045ecc326e92cbfab47c7cbab4bd85d28971b97ac4", + "https://bcr.bazel.build/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa", + "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", + "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", + "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a", + "https://bcr.bazel.build/modules/rules_apple/4.1.0/MODULE.bazel": "76e10fd4a48038d3fc7c5dc6e63b7063bbf5304a2e3bd42edda6ec660eebea68", + "https://bcr.bazel.build/modules/rules_apple/4.1.0/source.json": "8ee81e1708756f81b343a5eb2b2f0b953f1d25c4ab3d4a68dc02754872e80715", + "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", + "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", + "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", + "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", + "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", + "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", + "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", + "https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0", + "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", + "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", + "https://bcr.bazel.build/modules/rules_cc/0.2.13/MODULE.bazel": "eecdd666eda6be16a8d9dc15e44b5c75133405e820f620a234acc4b1fdc5aa37", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", + "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", + "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8", + "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270", + "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd", + "https://bcr.bazel.build/modules/rules_go/0.60.0/MODULE.bazel": "4a57ff2ffc2a3570e3c5646575c5a4b07287e91bcdac5d1f72383d51502b48cb", + "https://bcr.bazel.build/modules/rules_go/0.60.0/source.json": "1e21368c5e0c3013a110bd79a8fcff8ca46b5bcb2b561713a7273cbfcff7c464", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", + "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", + "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", + "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", + "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", + "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", + "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", + "https://bcr.bazel.build/modules/rules_java/9.0.3/MODULE.bazel": "1f98ed015f7e744a745e0df6e898a7c5e83562d6b759dfd475c76456dda5ccea", + "https://bcr.bazel.build/modules/rules_java/9.0.3/source.json": "b038c0c07e12e658135bbc32cc1a2ded6e33785105c9d41958014c592de4593e", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", + "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", + "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/source.json": "5426f412d0a7fc6b611643376c7e4a82dec991491b9ce5cb1cfdd25fe2e92be4", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", + "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", + "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483", + "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", + "https://bcr.bazel.build/modules/rules_proto/7.1.0/MODULE.bazel": "002d62d9108f75bb807cd56245d45648f38275cb3a99dcd45dfb864c5d74cb96", + "https://bcr.bazel.build/modules/rules_proto/7.1.0/source.json": "39f89066c12c24097854e8f57ab8558929f9c8d474d34b2c00ac04630ad8940e", + "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", + "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", + "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", + "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", + "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", + "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937", + "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13", + "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", + "https://bcr.bazel.build/modules/rules_python/1.6.0/MODULE.bazel": "7e04ad8f8d5bea40451cf80b1bd8262552aa73f841415d20db96b7241bd027d8", + "https://bcr.bazel.build/modules/rules_python/1.7.0/MODULE.bazel": "d01f995ecd137abf30238ad9ce97f8fc3ac57289c8b24bd0bf53324d937a14f8", + "https://bcr.bazel.build/modules/rules_python/1.7.0/source.json": "028a084b65dcf8f4dc4f82f8778dbe65df133f234b316828a82e060d81bdce32", + "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", + "https://bcr.bazel.build/modules/rules_shell/0.6.1/MODULE.bazel": "72e76b0eea4e81611ef5452aa82b3da34caca0c8b7b5c0c9584338aa93bae26b", + "https://bcr.bazel.build/modules/rules_shell/0.6.1/source.json": "20ec05cd5e592055e214b2da8ccb283c7f2a421ea0dc2acbf1aa792e11c03d0c", + "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", + "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", + "https://bcr.bazel.build/modules/rules_swift/2.4.0/MODULE.bazel": "1639617eb1ede28d774d967a738b4a68b0accb40650beadb57c21846beab5efd", + "https://bcr.bazel.build/modules/rules_swift/3.1.2/MODULE.bazel": "72c8f5cf9d26427cee6c76c8e3853eb46ce6b0412a081b2b6db6e8ad56267400", + "https://bcr.bazel.build/modules/rules_swift/3.1.2/source.json": "e85761f3098a6faf40b8187695e3de6d97944e98abd0d8ce579cb2daf6319a66", + "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", + "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", + "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/source.json": "5fba48bbe0ba48761f9e9f75f92876cafb5d07c0ce059cc7a8027416de94a05b", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", + "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" + }, + "selectedYankedVersions": {}, + "moduleExtensions": { + "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { + "general": { + "bzlTransitiveDigest": "ABI1D/sbS1ovwaW/kHDoj8nnXjQ0oKU9fzmzEG4iT8o=", + "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", + "recordedInputs": [ + "REPO_MAPPING:rules_kotlin+,bazel_tools bazel_tools" + ], + "generatedRepoSpecs": { + "com_github_jetbrains_kotlin_git": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", + "attributes": { + "urls": [ + "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" + ], + "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" + } + }, + "com_github_jetbrains_kotlin": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", + "attributes": { + "git_repository_name": "com_github_jetbrains_kotlin_git", + "compiler_version": "1.9.23" + } + }, + "com_github_google_ksp": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", + "attributes": { + "urls": [ + "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" + ], + "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", + "strip_version": "1.9.23-1.0.20" + } + }, + "com_github_pinterest_ktlint": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", + "urls": [ + "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" + ], + "executable": true + } + }, + "rules_android": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", + "strip_prefix": "rules_android-0.1.1", + "urls": [ + "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" + ] + } + } + } + } + }, + "@@rules_python+//python/extensions:config.bzl%config": { + "general": { + "bzlTransitiveDigest": "2hLgIvNVTLgxus0ZuXtleBe70intCfo0cHs8qvt6cdM=", + "usagesDigest": "ZVSXMAGpD+xzVNPuvF1IoLBkty7TROO0+akMapt1pAg=", + "recordedInputs": [ + "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_python+,pypi__build rules_python++config+pypi__build", + "REPO_MAPPING:rules_python+,pypi__click rules_python++config+pypi__click", + "REPO_MAPPING:rules_python+,pypi__colorama rules_python++config+pypi__colorama", + "REPO_MAPPING:rules_python+,pypi__importlib_metadata rules_python++config+pypi__importlib_metadata", + "REPO_MAPPING:rules_python+,pypi__installer rules_python++config+pypi__installer", + "REPO_MAPPING:rules_python+,pypi__more_itertools rules_python++config+pypi__more_itertools", + "REPO_MAPPING:rules_python+,pypi__packaging rules_python++config+pypi__packaging", + "REPO_MAPPING:rules_python+,pypi__pep517 rules_python++config+pypi__pep517", + "REPO_MAPPING:rules_python+,pypi__pip rules_python++config+pypi__pip", + "REPO_MAPPING:rules_python+,pypi__pip_tools rules_python++config+pypi__pip_tools", + "REPO_MAPPING:rules_python+,pypi__pyproject_hooks rules_python++config+pypi__pyproject_hooks", + "REPO_MAPPING:rules_python+,pypi__setuptools rules_python++config+pypi__setuptools", + "REPO_MAPPING:rules_python+,pypi__tomli rules_python++config+pypi__tomli", + "REPO_MAPPING:rules_python+,pypi__wheel rules_python++config+pypi__wheel", + "REPO_MAPPING:rules_python+,pypi__zipp rules_python++config+pypi__zipp" + ], + "generatedRepoSpecs": { + "rules_python_internal": { + "repoRuleId": "@@rules_python+//python/private:internal_config_repo.bzl%internal_config_repo", + "attributes": { + "transition_setting_generators": {}, + "transition_settings": [] + } + }, + "pypi__build": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/e2/03/f3c8ba0a6b6e30d7d18c40faab90807c9bb5e9a1e3b2fe2008af624a9c97/build-1.2.1-py3-none-any.whl", + "sha256": "75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__click": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", + "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__colorama": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", + "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__importlib_metadata": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/2d/0a/679461c511447ffaf176567d5c496d1de27cbe34a87df6677d7171b2fbd4/importlib_metadata-7.1.0-py3-none-any.whl", + "sha256": "30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__installer": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", + "sha256": "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__more_itertools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/50/e2/8e10e465ee3987bb7c9ab69efb91d867d93959095f4807db102d07995d94/more_itertools-10.2.0-py3-none-any.whl", + "sha256": "686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__packaging": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", + "sha256": "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pep517": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/25/6e/ca4a5434eb0e502210f591b97537d322546e4833dcb4d470a48c375c5540/pep517-0.13.1-py3-none-any.whl", + "sha256": "31b206f67165b3536dd577c5c3f1518e8fbaf38cbc57efff8369a392feff1721", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pip": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl", + "sha256": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pip_tools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/0d/dc/38f4ce065e92c66f058ea7a368a9c5de4e702272b479c0992059f7693941/pip_tools-7.4.1-py3-none-any.whl", + "sha256": "4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pyproject_hooks": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/ae/f3/431b9d5fe7d14af7a32340792ef43b8a714e7726f1d7b69cc4e8e7a3f1d7/pyproject_hooks-1.1.0-py3-none-any.whl", + "sha256": "7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__setuptools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/90/99/158ad0609729111163fc1f674a5a42f2605371a4cf036d0441070e2f7455/setuptools-78.1.1-py3-none-any.whl", + "sha256": "c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__tomli": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", + "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__wheel": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl", + "sha256": "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__zipp": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/da/55/a03fd7240714916507e1fcf7ae355bd9d9ed2e6db492595f1a67f61681be/zipp-3.18.2-py3-none-any.whl", + "sha256": "dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + } + } + } + }, + "@@rules_python+//python/uv:uv.bzl%uv": { + "general": { + "bzlTransitiveDigest": "ijW9KS7qsIY+yBVvJ+Nr1mzwQox09j13DnE3iIwaeTM=", + "usagesDigest": "H8dQoNZcoqP+Mu0tHZTi4KHATzvNkM5ePuEqoQdklIU=", + "recordedInputs": [ + "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_python+,platforms platforms" + ], + "generatedRepoSpecs": { + "uv": { + "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", + "attributes": { + "toolchain_type": "'@@rules_python+//python/uv:uv_toolchain_type'", + "toolchain_names": [ + "none" + ], + "toolchain_implementations": { + "none": "'@@rules_python+//python:none'" + }, + "toolchain_compatible_with": { + "none": [ + "@platforms//:incompatible" + ] + }, + "toolchain_target_settings": {} + } + } + } + } + } + }, + "facts": { + "@@rules_go+//go:extensions.bzl%go_sdk": { + "1.25.0": { + "aix_ppc64": [ + "go1.25.0.aix-ppc64.tar.gz", + "e5234a7dac67bc86c528fe9752fc9d63557918627707a733ab4cac1a6faed2d4" + ], + "darwin_amd64": [ + "go1.25.0.darwin-amd64.tar.gz", + "5bd60e823037062c2307c71e8111809865116714d6f6b410597cf5075dfd80ef" + ], + "darwin_arm64": [ + "go1.25.0.darwin-arm64.tar.gz", + "544932844156d8172f7a28f77f2ac9c15a23046698b6243f633b0a0b00c0749c" + ], + "dragonfly_amd64": [ + "go1.25.0.dragonfly-amd64.tar.gz", + "5ed3cf9a810a1483822538674f1336c06b51aa1b94d6d545a1a0319a48177120" + ], + "freebsd_386": [ + "go1.25.0.freebsd-386.tar.gz", + "abea5d5c6697e6b5c224731f2158fe87c602996a2a233ac0c4730cd57bf8374e" + ], + "freebsd_amd64": [ + "go1.25.0.freebsd-amd64.tar.gz", + "86e6fe0a29698d7601c4442052dac48bd58d532c51cccb8f1917df648138730b" + ], + "freebsd_arm": [ + "go1.25.0.freebsd-arm.tar.gz", + "d90b78e41921f72f30e8bbc81d9dec2cff7ff384a33d8d8debb24053e4336bfe" + ], + "freebsd_arm64": [ + "go1.25.0.freebsd-arm64.tar.gz", + "451d0da1affd886bfb291b7c63a6018527b269505db21ce6e14724f22ab0662e" + ], + "freebsd_riscv64": [ + "go1.25.0.freebsd-riscv64.tar.gz", + "7b565f76bd8bda46549eeaaefe0e53b251e644c230577290c0f66b1ecdb3cdbe" + ], + "illumos_amd64": [ + "go1.25.0.illumos-amd64.tar.gz", + "b1e1fdaab1ad25aa1c08d7a36c97d45d74b98b89c3f78c6d2145f77face54a2c" + ], + "linux_386": [ + "go1.25.0.linux-386.tar.gz", + "8c602dd9d99bc9453b3995d20ce4baf382cc50855900a0ece5de9929df4a993a" + ], + "linux_amd64": [ + "go1.25.0.linux-amd64.tar.gz", + "2852af0cb20a13139b3448992e69b868e50ed0f8a1e5940ee1de9e19a123b613" + ], + "linux_arm64": [ + "go1.25.0.linux-arm64.tar.gz", + "05de75d6994a2783699815ee553bd5a9327d8b79991de36e38b66862782f54ae" + ], + "linux_armv6l": [ + "go1.25.0.linux-armv6l.tar.gz", + "a5a8f8198fcf00e1e485b8ecef9ee020778bf32a408a4e8873371bfce458cd09" + ], + "linux_loong64": [ + "go1.25.0.linux-loong64.tar.gz", + "cab86b1cf761b1cb3bac86a8877cfc92e7b036fc0d3084123d77013d61432afc" + ], + "linux_mips": [ + "go1.25.0.linux-mips.tar.gz", + "d66b6fb74c3d91b9829dc95ec10ca1f047ef5e89332152f92e136cf0e2da5be1" + ], + "linux_mips64": [ + "go1.25.0.linux-mips64.tar.gz", + "4082e4381a8661bc2a839ff94ba3daf4f6cde20f8fb771b5b3d4762dc84198a2" + ], + "linux_mips64le": [ + "go1.25.0.linux-mips64le.tar.gz", + "70002c299ec7f7175ac2ef673b1b347eecfa54ae11f34416a6053c17f855afcc" + ], + "linux_mipsle": [ + "go1.25.0.linux-mipsle.tar.gz", + "b00a3a39eff099f6df9f1c7355bf28e4589d0586f42d7d4a394efb763d145a73" + ], + "linux_ppc64": [ + "go1.25.0.linux-ppc64.tar.gz", + "df166f33bd98160662560a72ff0b4ba731f969a80f088922bddcf566a88c1ec1" + ], + "linux_ppc64le": [ + "go1.25.0.linux-ppc64le.tar.gz", + "0f18a89e7576cf2c5fa0b487a1635d9bcbf843df5f110e9982c64df52a983ad0" + ], + "linux_riscv64": [ + "go1.25.0.linux-riscv64.tar.gz", + "c018ff74a2c48d55c8ca9b07c8e24163558ffec8bea08b326d6336905d956b67" + ], + "linux_s390x": [ + "go1.25.0.linux-s390x.tar.gz", + "34e5a2e19f2292fbaf8783e3a241e6e49689276aef6510a8060ea5ef54eee408" + ], + "netbsd_386": [ + "go1.25.0.netbsd-386.tar.gz", + "f8586cdb7aa855657609a5c5f6dbf523efa00c2bbd7c76d3936bec80aa6c0aba" + ], + "netbsd_amd64": [ + "go1.25.0.netbsd-amd64.tar.gz", + "ae8dc1469385b86a157a423bb56304ba45730de8a897615874f57dd096db2c2a" + ], + "netbsd_arm": [ + "go1.25.0.netbsd-arm.tar.gz", + "1ff7e4cc764425fc9dd6825eaee79d02b3c7cafffbb3691687c8d672ade76cb7" + ], + "netbsd_arm64": [ + "go1.25.0.netbsd-arm64.tar.gz", + "e1b310739f26724216aa6d7d7208c4031f9ff54c9b5b9a796ddc8bebcb4a5f16" + ], + "openbsd_386": [ + "go1.25.0.openbsd-386.tar.gz", + "4802a9b20e533da91adb84aab42e94aa56cfe3e5475d0550bed3385b182e69d8" + ], + "openbsd_amd64": [ + "go1.25.0.openbsd-amd64.tar.gz", + "c016cd984bebe317b19a4f297c4f50def120dc9788490540c89f28e42f1dabe1" + ], + "openbsd_arm": [ + "go1.25.0.openbsd-arm.tar.gz", + "a1e31d0bf22172ddde42edf5ec811ef81be43433df0948ece52fecb247ccfd8d" + ], + "openbsd_arm64": [ + "go1.25.0.openbsd-arm64.tar.gz", + "343ea8edd8c218196e15a859c6072d0dd3246fbbb168481ab665eb4c4140458d" + ], + "openbsd_ppc64": [ + "go1.25.0.openbsd-ppc64.tar.gz", + "694c14da1bcaeb5e3332d49bdc2b6d155067648f8fe1540c5de8f3cf8e157154" + ], + "openbsd_riscv64": [ + "go1.25.0.openbsd-riscv64.tar.gz", + "aa510ad25cf54c06cd9c70b6d80ded69cb20188ac6e1735655eef29ff7e7885f" + ], + "plan9_386": [ + "go1.25.0.plan9-386.tar.gz", + "46f8cef02086cf04bf186c5912776b56535178d4cb319cd19c9fdbdd29231986" + ], + "plan9_amd64": [ + "go1.25.0.plan9-amd64.tar.gz", + "29b34391d84095e44608a228f63f2f88113a37b74a79781353ec043dfbcb427b" + ], + "plan9_arm": [ + "go1.25.0.plan9-arm.tar.gz", + "0a047107d13ebe7943aaa6d54b1d7bbd2e45e68ce449b52915a818da715799c2" + ], + "solaris_amd64": [ + "go1.25.0.solaris-amd64.tar.gz", + "9977f9e4351984364a3b2b78f8b88bfd1d339812356d5237678514594b7d3611" + ], + "windows_386": [ + "go1.25.0.windows-386.zip", + "df9f39db82a803af0db639e3613a36681ab7a42866b1384b3f3a1045663961a7" + ], + "windows_amd64": [ + "go1.25.0.windows-amd64.zip", + "89efb4f9b30812eee083cc1770fdd2913c14d301064f6454851428f9707d190b" + ], + "windows_arm64": [ + "go1.25.0.windows-arm64.zip", + "27bab004c72b3d7bd05a69b6ec0fc54a309b4b78cc569dd963d8b3ec28bfdb8c" + ] + } + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..8fa85a6 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# rules_wails + +Reusable Bazel rules for Wails applications. + +Layout: + +- `wails/`: core Wails rules and Go-backed action helpers. +- `wails_bun/`: optional Bun frontend integration for projects that stage frontend packages through Bazel. +- `examples/`: minimal usage examples for core Go and Bun-backed Wails apps. + +Public entrypoints: + +- `@rules_wails//wails:defs.bzl` +- `@rules_wails//wails_bun:defs.bzl` + +## Bzlmod usage + +```starlark +bazel_dep(name = "rules_wails", version = "0.1.0") +``` + +The Bun integration expects the standard `rules_bun` repositories and toolchains to be available via this ruleset's module setup. + +## Public API + +Core rules: + +- `wails_toolchain` +- `wails_build_assets` +- `wails_generate_bindings` +- `wails_run` +- `wails_app` + +Bun integration: + +- `wails_bun_frontend_dist` +- `wails_bun_frontend_dev` +- `wails_bun_dev_session` +- `wails_bun_app` + +See: + +- [core README](wails/README.md) +- [Bun README](wails_bun/README.md) diff --git a/bazel-bin b/bazel-bin new file mode 120000 index 0000000..b4ba602 --- /dev/null +++ b/bazel-bin @@ -0,0 +1 @@ +/Users/eric/Library/Caches/bazel/_bazel_eric/cc2f40429f1d5fbfb8b6a40c23bb6094/execroot/_main/bazel-out/darwin_arm64-fastbuild/bin \ No newline at end of file diff --git a/bazel-out b/bazel-out new file mode 120000 index 0000000..87757b1 --- /dev/null +++ b/bazel-out @@ -0,0 +1 @@ +/Users/eric/Library/Caches/bazel/_bazel_eric/cc2f40429f1d5fbfb8b6a40c23bb6094/execroot/_main/bazel-out \ No newline at end of file diff --git a/bazel-testlogs b/bazel-testlogs new file mode 120000 index 0000000..09a2773 --- /dev/null +++ b/bazel-testlogs @@ -0,0 +1 @@ +/Users/eric/Library/Caches/bazel/_bazel_eric/cc2f40429f1d5fbfb8b6a40c23bb6094/execroot/_main/bazel-out/darwin_arm64-fastbuild/testlogs \ No newline at end of file diff --git a/bazel-wails_tools b/bazel-wails_tools new file mode 120000 index 0000000..4bf5c73 --- /dev/null +++ b/bazel-wails_tools @@ -0,0 +1 @@ +/Users/eric/Library/Caches/bazel/_bazel_eric/cc2f40429f1d5fbfb8b6a40c23bb6094/execroot/_main \ No newline at end of file diff --git a/examples/basic_go/BUILD.bazel b/examples/basic_go/BUILD.bazel new file mode 100644 index 0000000..316774c --- /dev/null +++ b/examples/basic_go/BUILD.bazel @@ -0,0 +1 @@ +exports_files(["README.md"]) diff --git a/examples/basic_go/README.md b/examples/basic_go/README.md new file mode 100644 index 0000000..659fd0f --- /dev/null +++ b/examples/basic_go/README.md @@ -0,0 +1,50 @@ +# basic_go + +Minimal core-only shape: + +```bzl +load("@io_bazel_rules_go//go:def.bzl", "go_binary") +load("@rules_wails//wails:defs.bzl", "wails_build_assets", "wails_generate_bindings", "wails_run", "wails_toolchain") + +go_binary( + name = "wails3", + srcs = ["scripts/wails3.go"], +) + +wails_toolchain( + name = "example_wails_toolchain_impl", + go = "@my_go_sdk//:bin/go", + wails = ":wails3", +) + +toolchain( + name = "example_wails_toolchain", + toolchain = ":example_wails_toolchain_impl", + toolchain_type = "@rules_wails//wails:toolchain_type", +) + +go_binary( + name = "app", + srcs = ["main.go"], +) + +wails_build_assets( + name = "build_assets", + srcs = ["build/config.yml"], + app_name = "example", + binary_name = "example", + strip_prefix = "build/", +) + +wails_generate_bindings( + name = "bindings", + out_dir = "frontend/src/lib/bindings", + package_dir = ".", +) + +wails_run( + name = "run", + binary = ":app", + build_assets = ":build_assets", +) +``` diff --git a/examples/bun_vite/BUILD.bazel b/examples/bun_vite/BUILD.bazel new file mode 100644 index 0000000..316774c --- /dev/null +++ b/examples/bun_vite/BUILD.bazel @@ -0,0 +1 @@ +exports_files(["README.md"]) diff --git a/examples/bun_vite/README.md b/examples/bun_vite/README.md new file mode 100644 index 0000000..aa760c6 --- /dev/null +++ b/examples/bun_vite/README.md @@ -0,0 +1,25 @@ +# bun_vite + +Minimal Bun-layer shape: + +```bzl +load("@rules_wails//wails_bun:defs.bzl", "wails_bun_app") + +wails_bun_app( + name = "desktop", + app_binary = ":desktop_bin", + frontend_srcs = [ + ":frontend_sources", + "//pkgs/ui-kit:ui_kit_sources", + ], + package_json = "frontend/package.json", + node_modules = "@deps//:node_modules", + build_asset_srcs = ["build/config.yml"], + app_name = "desktop", + binary_name = "desktop", + frontend_strip_prefix = "frontend/", + build_strip_prefix = "build/", +) +``` + +For Bun staging, each workspace package source target should include `package.json`. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b1b4a94 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/Eriyc/rules_wails + +go 1.26 diff --git a/wails/BUILD.bazel b/wails/BUILD.bazel new file mode 100644 index 0000000..3b22375 --- /dev/null +++ b/wails/BUILD.bazel @@ -0,0 +1,12 @@ +load(":private/toolchain.bzl", "wails_toolchain") + +toolchain_type( + name = "toolchain_type", + visibility = ["//visibility:public"], +) + +exports_files([ + "defs.bzl", + "README.md", +]) + diff --git a/wails/README.md b/wails/README.md new file mode 100644 index 0000000..545e264 --- /dev/null +++ b/wails/README.md @@ -0,0 +1,28 @@ +# rules_wails core + +Core Bazel rules for Wails applications. + +Public API: + +- `wails_toolchain` +- `wails_build_assets` +- `wails_generate_bindings` +- `wails_run` +- `wails_app` + +The caller provides a registered toolchain with: + +- `wails`: an executable target used to invoke Wails. +- `go`: an executable file used by that Wails wrapper for hermetic `go run` execution. + +Notes: + +- Core action helpers are Go binaries. +- `wails_run` launches the built application but does not build the Go binary for you. +- `wails_generate_bindings` stages the Bazel package tree so bindings generation runs from the correct module root. + +Load with: + +```starlark +load("@rules_wails//wails:defs.bzl", "wails_build_assets", "wails_generate_bindings", "wails_run", "wails_toolchain") +``` diff --git a/wails/defs.bzl b/wails/defs.bzl new file mode 100644 index 0000000..863d068 --- /dev/null +++ b/wails/defs.bzl @@ -0,0 +1,17 @@ +"""Public API surface for core Wails rules.""" + +load(":private/build_assets.bzl", _wails_build_assets = "wails_build_assets") +load(":private/generate_bindings.bzl", _wails_generate_bindings = "wails_generate_bindings") +load(":private/macros.bzl", _wails_app = "wails_app") +load(":private/run.bzl", _wails_run = "wails_run") +load(":private/toolchain.bzl", _WailsToolchainInfo = "WailsToolchainInfo", _wails_toolchain = "wails_toolchain") + +visibility("public") + +WailsToolchainInfo = _WailsToolchainInfo +wails_toolchain = _wails_toolchain +wails_build_assets = _wails_build_assets +wails_generate_bindings = _wails_generate_bindings +wails_run = _wails_run +wails_app = _wails_app + diff --git a/wails/private/build_assets.bzl b/wails/private/build_assets.bzl new file mode 100644 index 0000000..9cd9241 --- /dev/null +++ b/wails/private/build_assets.bzl @@ -0,0 +1,58 @@ +"""Rule for generating Wails build assets.""" + +load(":private/common.bzl", "write_manifest") + +def _wails_build_assets_impl(ctx): + toolchain = ctx.toolchains["//wails:toolchain_type"].wails + out_dir = ctx.actions.declare_directory(ctx.label.name) + manifest = write_manifest(ctx, ctx.label.name + ".manifest", ctx.files.srcs, ctx.attr.strip_prefix) + + args = ctx.actions.args() + args.add("--app-name", ctx.attr.app_name) + args.add("--binary-name", ctx.attr.binary_name) + args.add("--config-file", ctx.attr.config_file) + args.add("--manifest", manifest.path) + args.add("--out", out_dir.path) + args.add("--wails", toolchain.executable.path) + if ctx.attr.macos_minimum_system_version: + args.add("--macos-minimum-system-version", ctx.attr.macos_minimum_system_version) + + ctx.actions.run( + executable = ctx.executable._tool, + arguments = [args], + inputs = depset(ctx.files.srcs + [manifest, toolchain.go_executable]), + outputs = [out_dir], + tools = [ + ctx.executable._tool, + toolchain.files_to_run, + ], + env = { + "GO_BIN": toolchain.go_executable.path, + }, + mnemonic = "WailsBuildAssets", + progress_message = "Generating Wails build assets for %s" % ctx.label, + ) + + return [DefaultInfo(files = depset([out_dir]))] + +wails_build_assets = rule( + implementation = _wails_build_assets_impl, + doc = "Runs `wails update build-assets` in a staged build directory.", + attrs = { + "app_name": attr.string(mandatory = True), + "binary_name": attr.string(mandatory = True), + "config_file": attr.string(default = "config.yml"), + "macos_minimum_system_version": attr.string(default = ""), + "srcs": attr.label_list( + mandatory = True, + allow_files = True, + ), + "strip_prefix": attr.string(default = ""), + "_tool": attr.label( + default = "//wails/tools:build_assets_action", + cfg = "exec", + executable = True, + ), + }, + toolchains = ["//wails:toolchain_type"], +) diff --git a/wails/private/common.bzl b/wails/private/common.bzl new file mode 100644 index 0000000..f0706ec --- /dev/null +++ b/wails/private/common.bzl @@ -0,0 +1,58 @@ +"""Shared helpers for Wails rules.""" + +def _normalize_prefix(prefix): + if not prefix or prefix == ".": + return "" + + normalized = prefix.strip("/") + if not normalized: + return "" + + return normalized + "/" + +def _manifest_rel_path(short_path, strip_prefix): + normalized_prefix = _normalize_prefix(strip_prefix) + rel_path = short_path + + if normalized_prefix and rel_path.startswith(normalized_prefix): + rel_path = rel_path[len(normalized_prefix):] + + return rel_path + +def write_manifest(ctx, name, files, strip_prefix = ""): + manifest = ctx.actions.declare_file(name) + lines = [] + + for src in files: + lines.append("%s\t%s" % (src.path, _manifest_rel_path(src.short_path, strip_prefix))) + + ctx.actions.write( + output = manifest, + content = "\n".join(lines) + "\n", + ) + + return manifest + +def bash_launcher(resolve_lines, command_lines): + return """#!/usr/bin/env bash +set -euo pipefail + +runfiles_dir="${{RUNFILES_DIR:-$0.runfiles}}" +export RUNFILES_DIR="${{runfiles_dir}}" +export RUNFILES="${{runfiles_dir}}" +resolve_runfile() {{ + local short_path="$1" + + if [[ "$short_path" == ../* ]]; then + echo "${{runfiles_dir}}/${{short_path#../}}" + else + echo "${{runfiles_dir}}/_main/${{short_path}}" + fi +}} + +{resolve_lines} +{command_lines} +""".format( + resolve_lines = resolve_lines.strip(), + command_lines = command_lines.strip(), + ) diff --git a/wails/private/generate_bindings.bzl b/wails/private/generate_bindings.bzl new file mode 100644 index 0000000..66ba334 --- /dev/null +++ b/wails/private/generate_bindings.bzl @@ -0,0 +1,205 @@ +"""Rules for Wails binding generation.""" + +load(":private/common.bzl", "bash_launcher", "write_manifest") + +def _normalize_relative_path(path): + if not path or path == ".": + return "" + + return path.strip("/") + +def _join_relative_path(base, child): + normalized_base = _normalize_relative_path(base) + normalized_child = _normalize_relative_path(child) + + if normalized_base and normalized_child: + return normalized_base + "/" + normalized_child + if normalized_base: + return normalized_base + return normalized_child + +def _package_glob_patterns(package_dir, out_dir): + normalized = package_dir.strip("./") + generated_dir = _join_relative_path(normalized, out_dir) + + exclude_patterns = ["**/node_modules/**", "**/dist/**"] + if generated_dir: + exclude_patterns.append(generated_dir + "/**") + + if not normalized: + return ("**", exclude_patterns) + + return ( + normalized + "/**", + exclude_patterns + [ + normalized + "/node_modules/**", + normalized + "/dist/**", + ], + ) + +def _workspace_package_dir(label_package, package_dir): + if label_package and package_dir and package_dir != ".": + return label_package + "/" + package_dir + if label_package: + return label_package + return package_dir + +def _wails_generate_bindings_impl(ctx): + toolchain = ctx.toolchains["//wails:toolchain_type"].wails + out_tree = ctx.actions.declare_directory(ctx.label.name + "_out") + workspace_package_dir = _workspace_package_dir(ctx.label.package, ctx.attr.package_dir or ".") + manifest = write_manifest( + ctx, + ctx.label.name + ".manifest", + ctx.files.srcs, + workspace_package_dir, + ) + + build_args = ctx.actions.args() + build_args.add("--clean=%s" % ("true" if ctx.attr.clean else "false")) + build_args.add("--manifest", manifest.path) + build_args.add("--mode", "action") + build_args.add("--out", out_tree.path) + build_args.add("--out-dir", ctx.attr.out_dir) + build_args.add("--ts=%s" % ("true" if ctx.attr.ts else "false")) + build_args.add("--wails", toolchain.executable.path) + for extra_arg in ctx.attr.extra_args: + build_args.add("--extra-arg", extra_arg) + + ctx.actions.run( + executable = ctx.executable._tool, + arguments = [build_args], + inputs = depset(ctx.files.srcs + [manifest, toolchain.go_executable]), + outputs = [out_tree], + tools = [ + ctx.executable._tool, + toolchain.files_to_run, + ], + env = { + "GO_BIN": toolchain.go_executable.path, + }, + mnemonic = "WailsGenerateBindings", + progress_message = "Generating Wails bindings for %s" % ctx.label, + ) + + launcher = ctx.actions.declare_file(ctx.label.name) + resolve_lines = """ +tool="$(resolve_runfile "{tool_short_path}")" +wails="$(resolve_runfile "{wails_short_path}")" +go_bin="$(resolve_runfile "{go_short_path}")" +workspace_root="${{BUILD_WORKSPACE_DIRECTORY:-}}" +if [[ -z "$workspace_root" ]]; then + echo "BUILD_WORKSPACE_DIRECTORY is required for bazel run bindings generation" >&2 + exit 1 +fi +workspace_package_dir="{workspace_package_dir}" +if [[ -n "$workspace_package_dir" && "$workspace_package_dir" != "." ]]; then + workspace_package_dir="${{workspace_root}}/${{workspace_package_dir}}" +else + workspace_package_dir="${{workspace_root}}" +fi +""".format( + tool_short_path = ctx.executable._tool.short_path, + wails_short_path = toolchain.executable.short_path, + go_short_path = toolchain.go_executable.short_path, + workspace_package_dir = workspace_package_dir or ".", + ) + + extra_args_lines = "\n".join([ + 'cmd+=(--extra-arg %s)' % _shell_quote(arg) + for arg in ctx.attr.extra_args + ]) + command_lines = """ +cmd=( + "$tool" + --clean={clean} + --mode workspace + --out-dir {out_dir} + --package-dir "$workspace_package_dir" + --ts={ts} + --wails "$wails" +) +{extra_args_lines} +export GO_BIN="$go_bin" +exec "${{cmd[@]}}" +""".format( + clean = "true" if ctx.attr.clean else "false", + out_dir = _shell_quote(ctx.attr.out_dir), + ts = "true" if ctx.attr.ts else "false", + extra_args_lines = extra_args_lines, + ) + + ctx.actions.write( + output = launcher, + is_executable = True, + content = bash_launcher(resolve_lines, command_lines), + ) + + runfiles = ctx.runfiles( + files = [ + ctx.executable._tool, + toolchain.executable, + toolchain.go_executable, + ], + transitive_files = depset(transitive = [ + ctx.attr._tool[DefaultInfo].default_runfiles.files, + toolchain.default_runfiles.files, + toolchain.go_default_runfiles.files, + ]), + ) + + return [ + DefaultInfo( + executable = launcher, + files = depset([out_tree]), + runfiles = runfiles, + ), + ] + +def _shell_quote(value): + return "'" + value.replace("'", "'\"'\"'") + "'" + +_wails_generate_bindings_rule = rule( + implementation = _wails_generate_bindings_impl, + attrs = { + "clean": attr.bool(default = True), + "extra_args": attr.string_list(), + "out_dir": attr.string(mandatory = True), + "package_dir": attr.string(default = "."), + "srcs": attr.label_list( + allow_files = True, + mandatory = True, + ), + "ts": attr.bool(default = True), + "_tool": attr.label( + default = "//wails/tools:generate_bindings_action", + cfg = "exec", + executable = True, + ), + }, + executable = True, + toolchains = ["//wails:toolchain_type"], +) + +def wails_generate_bindings( + name, + out_dir, + package_dir = ".", + clean = True, + ts = True, + extra_args = None, + tags = None, + visibility = None): + include_pattern, exclude_patterns = _package_glob_patterns(package_dir, out_dir) + + _wails_generate_bindings_rule( + name = name, + clean = clean, + extra_args = extra_args or [], + out_dir = out_dir, + package_dir = package_dir, + srcs = native.glob([include_pattern], exclude = exclude_patterns), + tags = tags, + visibility = visibility, + ts = ts, + ) diff --git a/wails/private/macros.bzl b/wails/private/macros.bzl new file mode 100644 index 0000000..a5143cc --- /dev/null +++ b/wails/private/macros.bzl @@ -0,0 +1,27 @@ +"""Convenience macros for Wails rules.""" + +load(":private/generate_bindings.bzl", "wails_generate_bindings") +load(":private/run.bzl", "wails_run") + +def wails_app(name, binary, build_assets, bindings = None, icon = None, visibility = None, tags = None): + wails_run( + name = name + "_run", + binary = binary, + build_assets = build_assets, + icon = icon, + tags = tags, + visibility = visibility, + ) + + if bindings: + wails_generate_bindings( + name = name + "_bindings", + out_dir = bindings["out_dir"], + package_dir = bindings.get("package_dir", "."), + clean = bindings.get("clean", True), + ts = bindings.get("ts", True), + extra_args = bindings.get("extra_args", []), + tags = tags, + visibility = visibility, + ) + diff --git a/wails/private/run.bzl b/wails/private/run.bzl new file mode 100644 index 0000000..51a52c0 --- /dev/null +++ b/wails/private/run.bzl @@ -0,0 +1,87 @@ +"""Runnable Wails app launcher rule.""" + +load(":private/common.bzl", "bash_launcher") + +def _wails_run_impl(ctx): + launcher = ctx.actions.declare_file(ctx.label.name) + + resolve_lines = """ +tool="$(resolve_runfile "{tool_short_path}")" +binary="$(resolve_runfile "{binary_short_path}")" +build_assets="$(resolve_runfile "{build_assets_short_path}")" +icon="" +if [[ -n "{icon_short_path}" ]]; then + icon="$(resolve_runfile "{icon_short_path}")" +fi +""".format( + tool_short_path = ctx.executable._tool.short_path, + binary_short_path = ctx.executable.binary.short_path, + build_assets_short_path = ctx.files.build_assets[0].short_path, + icon_short_path = ctx.file.icon.short_path if ctx.file.icon else "", + ) + + command_lines = """ +exec "$tool" \ + --binary "$binary" \ + --build-assets "$build_assets" \ + --frontend-url {frontend_url} \ + --icon "$icon" \ + --mode {mode} +""".format( + frontend_url = _shell_quote(ctx.attr.frontend_url), + mode = _shell_quote(ctx.attr.mode), + ) + + ctx.actions.write( + output = launcher, + is_executable = True, + content = bash_launcher(resolve_lines, command_lines), + ) + + transitive_files = [ + ctx.attr._tool[DefaultInfo].default_runfiles.files, + ctx.attr.binary[DefaultInfo].default_runfiles.files, + ] + + runfiles = ctx.runfiles( + files = [ + ctx.executable._tool, + ctx.executable.binary, + ] + ctx.files.build_assets + ([ctx.file.icon] if ctx.file.icon else []), + transitive_files = depset(transitive = transitive_files), + ) + + return [DefaultInfo(executable = launcher, runfiles = runfiles)] + +def _shell_quote(value): + return "'" + value.replace("'", "'\"'\"'") + "'" + +wails_run = rule( + implementation = _wails_run_impl, + doc = "Creates a runnable target that launches a Wails application.", + attrs = { + "binary": attr.label( + mandatory = True, + executable = True, + cfg = "target", + ), + "build_assets": attr.label( + mandatory = True, + allow_files = True, + ), + "frontend_url": attr.string(default = "http://127.0.0.1:9245"), + "icon": attr.label( + allow_single_file = True, + ), + "mode": attr.string( + default = "run", + values = ["dev", "run"], + ), + "_tool": attr.label( + default = "//wails/tools:launch_app", + cfg = "exec", + executable = True, + ), + }, + executable = True, +) diff --git a/wails/private/toolchain.bzl b/wails/private/toolchain.bzl new file mode 100644 index 0000000..9d454e7 --- /dev/null +++ b/wails/private/toolchain.bzl @@ -0,0 +1,48 @@ +"""Toolchain definitions for Wails.""" + +WailsToolchainInfo = provider( + doc = "Caller-supplied Wails executable, Go executable, and runfiles.", + fields = { + "default_runfiles": "Runfiles for the Wails executable.", + "go_default_runfiles": "Runfiles for the Go executable.", + "go_executable": "Executable file for the Go tool.", + "executable": "Executable file for the Wails tool.", + "files_to_run": "FilesToRunProvider for the Wails tool.", + }, +) + +def _wails_toolchain_impl(ctx): + wails_default_info = ctx.attr.wails[DefaultInfo] + go_default_info = ctx.attr.go[DefaultInfo] + + return [ + platform_common.ToolchainInfo( + wails = WailsToolchainInfo( + default_runfiles = wails_default_info.default_runfiles, + go_default_runfiles = go_default_info.default_runfiles, + go_executable = ctx.file.go, + executable = ctx.executable.wails, + files_to_run = wails_default_info.files_to_run, + ), + ), + ] + +wails_toolchain = rule( + implementation = _wails_toolchain_impl, + attrs = { + "go": attr.label( + mandatory = True, + cfg = "exec", + allow_single_file = True, + doc = "Executable file used for hermetic `go run` invocations inside the Wails tool.", + ), + "wails": attr.label( + mandatory = True, + cfg = "exec", + executable = True, + allow_files = True, + doc = "Executable target used to invoke Wails.", + ), + }, + doc = "Registers caller-supplied Wails and Go executables as a Bazel toolchain.", +) diff --git a/wails/tools/BUILD.bazel b/wails/tools/BUILD.bazel new file mode 100644 index 0000000..6bd484b --- /dev/null +++ b/wails/tools/BUILD.bazel @@ -0,0 +1,25 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary") + +go_binary( + name = "build_assets_action", + srcs = ["build_assets_action.go"], + importpath = "github.com/Eriyc/rules_wails/wails/tools/build_assets_action", + pure = "off", + visibility = ["//visibility:public"], +) + +go_binary( + name = "generate_bindings_action", + srcs = ["generate_bindings_action.go"], + importpath = "github.com/Eriyc/rules_wails/wails/tools/generate_bindings_action", + pure = "off", + visibility = ["//visibility:public"], +) + +go_binary( + name = "launch_app", + srcs = ["launch_app.go"], + importpath = "github.com/Eriyc/rules_wails/wails/tools/launch_app", + pure = "off", + visibility = ["//visibility:public"], +) diff --git a/wails/tools/build_assets_action.go b/wails/tools/build_assets_action.go new file mode 100644 index 0000000..e4fb2a6 --- /dev/null +++ b/wails/tools/build_assets_action.go @@ -0,0 +1,225 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" +) + +func main() { + var appName string + var binaryName string + var configFile string + var macOSMinimumSystemVersion string + var manifestPath string + var outDir string + var wailsPath string + + flag.StringVar(&appName, "app-name", "", "") + flag.StringVar(&binaryName, "binary-name", "", "") + flag.StringVar(&configFile, "config-file", "config.yml", "") + flag.StringVar(&macOSMinimumSystemVersion, "macos-minimum-system-version", "", "") + flag.StringVar(&manifestPath, "manifest", "", "") + flag.StringVar(&outDir, "out", "", "") + flag.StringVar(&wailsPath, "wails", "", "") + flag.Parse() + + require(manifestPath != "", "missing --manifest") + require(outDir != "", "missing --out") + require(wailsPath != "", "missing --wails") + require(appName != "", "missing --app-name") + require(binaryName != "", "missing --binary-name") + + var err error + wailsPath, err = filepath.Abs(wailsPath) + must(err) + must(resolveGoEnvToAbsolutePath()) + + tempRoot, err := os.MkdirTemp("", "rules-wails-build-assets-*") + must(err) + defer func() { + _ = os.Chmod(tempRoot, 0o755) + _ = os.RemoveAll(tempRoot) + }() + + workDir := filepath.Join(tempRoot, "work") + homeDir := filepath.Join(tempRoot, "home") + must(os.MkdirAll(workDir, 0o755)) + must(os.MkdirAll(homeDir, 0o755)) + must(stageManifest(manifestPath, workDir)) + + command := exec.Command( + wailsPath, + "update", + "build-assets", + "-name", appName, + "-binaryname", binaryName, + "-config", filepath.Join(workDir, configFile), + "-dir", workDir, + ) + command.Dir = workDir + command.Env = append(os.Environ(), + "HOME="+homeDir, + "LC_ALL=C", + "TZ=UTC", + ) + command.Stdout = os.Stdout + command.Stderr = os.Stderr + must(command.Run()) + + updateMacOSMinimumVersion(filepath.Join(workDir, "darwin", "Info.plist"), macOSMinimumSystemVersion) + updateMacOSMinimumVersion(filepath.Join(workDir, "darwin", "Info.dev.plist"), macOSMinimumSystemVersion) + + must(os.RemoveAll(outDir)) + must(os.MkdirAll(outDir, 0o755)) + must(copyTree(workDir, outDir)) +} + +func resolveGoEnvToAbsolutePath() error { + goBinary := os.Getenv("GO_BIN") + if goBinary == "" || filepath.IsAbs(goBinary) { + return nil + } + + absolutePath, err := filepath.Abs(goBinary) + if err != nil { + return err + } + + return os.Setenv("GO_BIN", absolutePath) +} + +func updateMacOSMinimumVersion(path string, minimumVersion string) { + if minimumVersion == "" { + return + } + if _, err := os.Stat(path); err != nil { + return + } + if _, err := os.Stat("/usr/libexec/PlistBuddy"); err != nil { + return + } + + setCommand := exec.Command("/usr/libexec/PlistBuddy", "-c", "Set :LSMinimumSystemVersion "+minimumVersion, path) + if err := setCommand.Run(); err == nil { + return + } + + addCommand := exec.Command("/usr/libexec/PlistBuddy", "-c", "Add :LSMinimumSystemVersion string "+minimumVersion, path) + _ = addCommand.Run() +} + +func stageManifest(manifestPath string, destinationRoot string) error { + entries, err := readManifest(manifestPath) + if err != nil { + return err + } + + for _, entry := range entries { + destinationPath := filepath.Join(destinationRoot, entry.relativePath) + if err := os.MkdirAll(filepath.Dir(destinationPath), 0o755); err != nil { + return err + } + if err := copyFile(entry.sourcePath, destinationPath); err != nil { + return err + } + } + + return nil +} + +func readManifest(path string) ([]manifestEntry, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + entries := make([]manifestEntry, 0) + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + parts := strings.SplitN(line, "\t", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid manifest line: %s", line) + } + entries = append(entries, manifestEntry{ + sourcePath: parts[0], + relativePath: parts[1], + }) + } + + return entries, scanner.Err() +} + +func copyTree(sourceRoot string, destinationRoot string) error { + return filepath.Walk(sourceRoot, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + relativePath, err := filepath.Rel(sourceRoot, path) + if err != nil { + return err + } + if relativePath == "." { + return nil + } + + destinationPath := filepath.Join(destinationRoot, relativePath) + if info.IsDir() { + return os.MkdirAll(destinationPath, 0o755) + } + + if err := os.MkdirAll(filepath.Dir(destinationPath), 0o755); err != nil { + return err + } + + return copyFile(path, destinationPath) + }) +} + +func copyFile(sourcePath string, destinationPath string) error { + sourceFile, err := os.Open(sourcePath) + if err != nil { + return err + } + defer sourceFile.Close() + + destinationFile, err := os.Create(destinationPath) + if err != nil { + return err + } + defer destinationFile.Close() + + if _, err := io.Copy(destinationFile, sourceFile); err != nil { + return err + } + + return destinationFile.Chmod(0o644) +} + +func must(err error) { + if err != nil { + panic(err) + } +} + +func require(condition bool, message string) { + if !condition { + panic(message) + } +} + +type manifestEntry struct { + sourcePath string + relativePath string +} diff --git a/wails/tools/generate_bindings_action.go b/wails/tools/generate_bindings_action.go new file mode 100644 index 0000000..b695469 --- /dev/null +++ b/wails/tools/generate_bindings_action.go @@ -0,0 +1,239 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" +) + +type repeatedFlag []string + +func (value *repeatedFlag) String() string { + return strings.Join(*value, ",") +} + +func (value *repeatedFlag) Set(input string) error { + *value = append(*value, input) + return nil +} + +func main() { + var clean bool + var extraArgs repeatedFlag + var manifestPath string + var mode string + var outDir string + var outputPath string + var packageDir string + var ts bool + var wailsPath string + + flag.BoolVar(&clean, "clean", true, "") + flag.Var(&extraArgs, "extra-arg", "") + flag.StringVar(&manifestPath, "manifest", "", "") + flag.StringVar(&mode, "mode", "", "") + flag.StringVar(&outDir, "out-dir", "", "") + flag.StringVar(&outputPath, "out", "", "") + flag.StringVar(&packageDir, "package-dir", "", "") + flag.BoolVar(&ts, "ts", true, "") + flag.StringVar(&wailsPath, "wails", "", "") + flag.Parse() + + require(mode != "", "missing --mode") + require(outDir != "", "missing --out-dir") + require(wailsPath != "", "missing --wails") + + var err error + wailsPath, err = filepath.Abs(wailsPath) + must(err) + must(resolveGoEnvToAbsolutePath()) + + switch mode { + case "action": + require(manifestPath != "", "missing --manifest") + require(outputPath != "", "missing --out") + must(runActionMode(manifestPath, outputPath, outDir, wailsPath, clean, ts, extraArgs)) + case "workspace": + require(packageDir != "", "missing --package-dir") + must(runWorkspaceMode(packageDir, outDir, wailsPath, clean, ts, extraArgs)) + default: + panic("unsupported --mode") + } +} + +func resolveGoEnvToAbsolutePath() error { + goBinary := os.Getenv("GO_BIN") + if goBinary == "" || filepath.IsAbs(goBinary) { + return nil + } + + absolutePath, err := filepath.Abs(goBinary) + if err != nil { + return err + } + + return os.Setenv("GO_BIN", absolutePath) +} + +func runActionMode(manifestPath string, outputPath string, outDir string, wailsPath string, clean bool, ts bool, extraArgs []string) error { + tempRoot, err := os.MkdirTemp("", "rules-wails-bindings-*") + if err != nil { + return err + } + defer os.RemoveAll(tempRoot) + + workDir := filepath.Join(tempRoot, "work") + if err := os.MkdirAll(workDir, 0o755); err != nil { + return err + } + if err := stageManifest(manifestPath, workDir); err != nil { + return err + } + if err := runBindings(workDir, outDir, wailsPath, clean, ts, extraArgs); err != nil { + return err + } + + if err := os.RemoveAll(outputPath); err != nil { + return err + } + if err := os.MkdirAll(outputPath, 0o755); err != nil { + return err + } + return copyTree(filepath.Join(workDir, outDir), outputPath) +} + +func runWorkspaceMode(packageDir string, outDir string, wailsPath string, clean bool, ts bool, extraArgs []string) error { + return runBindings(packageDir, outDir, wailsPath, clean, ts, extraArgs) +} + +func runBindings(cwd string, outDir string, wailsPath string, clean bool, ts bool, extraArgs []string) error { + commandArgs := []string{"generate", "bindings", "-d", outDir} + if clean { + commandArgs = append(commandArgs, "-clean") + } + if ts { + commandArgs = append(commandArgs, "-ts") + } + commandArgs = append(commandArgs, extraArgs...) + + command := exec.Command(wailsPath, commandArgs...) + command.Dir = cwd + command.Stdout = os.Stdout + command.Stderr = os.Stderr + return command.Run() +} + +func stageManifest(manifestPath string, destinationRoot string) error { + entries, err := readManifest(manifestPath) + if err != nil { + return err + } + + for _, entry := range entries { + destinationPath := filepath.Join(destinationRoot, entry.relativePath) + if err := os.MkdirAll(filepath.Dir(destinationPath), 0o755); err != nil { + return err + } + if err := copyFile(entry.sourcePath, destinationPath); err != nil { + return err + } + } + + return nil +} + +func readManifest(path string) ([]manifestEntry, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + entries := make([]manifestEntry, 0) + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + parts := strings.SplitN(line, "\t", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid manifest line: %s", line) + } + entries = append(entries, manifestEntry{ + sourcePath: parts[0], + relativePath: parts[1], + }) + } + + return entries, scanner.Err() +} + +func copyTree(sourceRoot string, destinationRoot string) error { + return filepath.Walk(sourceRoot, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + relativePath, err := filepath.Rel(sourceRoot, path) + if err != nil { + return err + } + if relativePath == "." { + return nil + } + + destinationPath := filepath.Join(destinationRoot, relativePath) + if info.IsDir() { + return os.MkdirAll(destinationPath, 0o755) + } + + if err := os.MkdirAll(filepath.Dir(destinationPath), 0o755); err != nil { + return err + } + + return copyFile(path, destinationPath) + }) +} + +func copyFile(sourcePath string, destinationPath string) error { + sourceFile, err := os.Open(sourcePath) + if err != nil { + return err + } + defer sourceFile.Close() + + destinationFile, err := os.Create(destinationPath) + if err != nil { + return err + } + defer destinationFile.Close() + + if _, err := io.Copy(destinationFile, sourceFile); err != nil { + return err + } + + return destinationFile.Chmod(0o644) +} + +func must(err error) { + if err != nil { + panic(err) + } +} + +func require(condition bool, message string) { + if !condition { + panic(message) + } +} + +type manifestEntry struct { + sourcePath string + relativePath string +} diff --git a/wails/tools/launch_app.go b/wails/tools/launch_app.go new file mode 100644 index 0000000..b069ea7 --- /dev/null +++ b/wails/tools/launch_app.go @@ -0,0 +1,139 @@ +package main + +import ( + "flag" + "io" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +func main() { + var binaryPath string + var buildAssetsPath string + var frontendURL string + var iconPath string + var mode string + + flag.StringVar(&binaryPath, "binary", "", "") + flag.StringVar(&buildAssetsPath, "build-assets", "", "") + flag.StringVar(&frontendURL, "frontend-url", "http://127.0.0.1:9245", "") + flag.StringVar(&iconPath, "icon", "", "") + flag.StringVar(&mode, "mode", "run", "") + flag.Parse() + + require(binaryPath != "", "missing --binary") + require(buildAssetsPath != "", "missing --build-assets") + + environment := os.Environ() + if mode == "dev" && os.Getenv("FRONTEND_DEVSERVER_URL") == "" { + environment = append(environment, "FRONTEND_DEVSERVER_URL="+frontendURL) + } + + if runtime.GOOS == "darwin" { + os.Exit(runDarwin(binaryPath, buildAssetsPath, iconPath, mode, environment)) + } + + command := exec.Command(binaryPath) + command.Stdout = os.Stdout + command.Stderr = os.Stderr + command.Stdin = os.Stdin + command.Env = environment + if err := command.Run(); err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + os.Exit(exitError.ExitCode()) + } + panic(err) + } +} + +func runDarwin(binaryPath string, buildAssetsPath string, iconPath string, mode string, environment []string) int { + appName := strings.TrimSuffix(filepath.Base(binaryPath), filepath.Ext(binaryPath)) + if appName == "" { + appName = "wails-app" + } + + appDir := filepath.Join(os.TempDir(), appName+"-bazel-"+mode) + defer os.RemoveAll(appDir) + + appContents := filepath.Join(appDir, "Contents") + appMacOS := filepath.Join(appContents, "MacOS") + appResources := filepath.Join(appContents, "Resources") + appBinary := filepath.Join(appMacOS, appName) + + must(os.MkdirAll(appMacOS, 0o755)) + must(os.MkdirAll(appResources, 0o755)) + must(copyFile(binaryPath, appBinary, 0o755)) + + for _, candidate := range []string{ + filepath.Join(buildAssetsPath, "darwin", "Info.dev.plist"), + filepath.Join(buildAssetsPath, "darwin", "Info.plist"), + } { + if mode != "dev" && strings.HasSuffix(candidate, "Info.dev.plist") { + continue + } + if _, err := os.Stat(candidate); err == nil { + must(copyFile(candidate, filepath.Join(appContents, "Info.plist"), 0o644)) + break + } + } + + if iconPath != "" { + if _, err := os.Stat(iconPath); err == nil { + must(copyFile(iconPath, filepath.Join(appResources, filepath.Base(iconPath)), 0o644)) + } + } + + if _, err := os.Stat("/usr/bin/codesign"); err == nil { + codesign := exec.Command("/usr/bin/codesign", "--force", "--deep", "--sign", "-", appDir) + _ = codesign.Run() + } + + command := exec.Command(appBinary) + command.Stdout = os.Stdout + command.Stderr = os.Stderr + command.Stdin = os.Stdin + command.Env = environment + if err := command.Run(); err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + return exitError.ExitCode() + } + panic(err) + } + + return 0 +} + +func copyFile(sourcePath string, destinationPath string, mode os.FileMode) error { + sourceFile, err := os.Open(sourcePath) + if err != nil { + return err + } + defer sourceFile.Close() + + destinationFile, err := os.Create(destinationPath) + if err != nil { + return err + } + defer destinationFile.Close() + + if _, err := io.Copy(destinationFile, sourceFile); err != nil { + return err + } + + return destinationFile.Chmod(mode) +} + +func must(err error) { + if err != nil { + panic(err) + } +} + +func require(condition bool, message string) { + if !condition { + panic(message) + } +} diff --git a/wails_bun/BUILD.bazel b/wails_bun/BUILD.bazel new file mode 100644 index 0000000..f0b9192 --- /dev/null +++ b/wails_bun/BUILD.bazel @@ -0,0 +1,5 @@ +exports_files([ + "defs.bzl", + "README.md", +]) + diff --git a/wails_bun/README.md b/wails_bun/README.md new file mode 100644 index 0000000..2760a0c --- /dev/null +++ b/wails_bun/README.md @@ -0,0 +1,23 @@ +# rules_wails Bun integration + +Optional `rules_bun` integration for Wails projects that build their frontend +with Bun and Vite-style package scripts. + +Public API: + +- `wails_bun_frontend_dist` +- `wails_bun_frontend_dev` +- `wails_bun_dev_session` +- `wails_bun_app` + +Important contract: + +- `ts_library` is not enough for frontend package staging. +- Package-source targets passed to `wails_bun_frontend_dist` should include both the package sources and `package.json`. +- Consumers should provide package-source targets that include both application sources and `package.json`. + +Load with: + +```starlark +load("@rules_wails//wails_bun:defs.bzl", "wails_bun_dev_session", "wails_bun_frontend_dev", "wails_bun_frontend_dist") +``` diff --git a/wails_bun/defs.bzl b/wails_bun/defs.bzl new file mode 100644 index 0000000..c0dd8a4 --- /dev/null +++ b/wails_bun/defs.bzl @@ -0,0 +1,13 @@ +"""Public API for Bun-backed Wails helpers.""" + +load(":private/dev_session.bzl", _wails_bun_dev_session = "wails_bun_dev_session") +load(":private/frontend_dist.bzl", _wails_bun_frontend_dev = "wails_bun_frontend_dev", _wails_bun_frontend_dist = "wails_bun_frontend_dist") +load(":private/macros.bzl", _wails_bun_app = "wails_bun_app") + +visibility("public") + +wails_bun_frontend_dist = _wails_bun_frontend_dist +wails_bun_frontend_dev = _wails_bun_frontend_dev +wails_bun_dev_session = _wails_bun_dev_session +wails_bun_app = _wails_bun_app + diff --git a/wails_bun/private/dev_session.bzl b/wails_bun/private/dev_session.bzl new file mode 100644 index 0000000..9f526cc --- /dev/null +++ b/wails_bun/private/dev_session.bzl @@ -0,0 +1,198 @@ +"""Dev-session rules for Bun-backed Wails apps.""" + +load("//wails:private/common.bzl", "bash_launcher") + +def _shell_quote(value): + return "'" + value.replace("'", "'\"'\"'") + "'" + +def _absolute_label(label_package, name): + if label_package: + return "//%s:%s" % (label_package, name) + return "//:%s" % name + +def _wails_bun_watch_run_impl(ctx): + launcher = ctx.actions.declare_file(ctx.label.name) + + resolve_lines = """ +tool="$(resolve_runfile "{tool_short_path}")" +binary="$(resolve_runfile "{binary_short_path}")" +build_assets="$(resolve_runfile "{build_assets_short_path}")" +bindings="" +if [[ -n "{bindings_short_path}" ]]; then + bindings="$(resolve_runfile "{bindings_short_path}")" +fi +icon="" +if [[ -n "{icon_short_path}" ]]; then + icon="$(resolve_runfile "{icon_short_path}")" +fi +""".format( + tool_short_path = ctx.executable._tool.short_path, + binary_short_path = ctx.executable.binary.short_path, + build_assets_short_path = ctx.files.build_assets[0].short_path, + bindings_short_path = ctx.executable.bindings.short_path if ctx.executable.bindings else "", + icon_short_path = ctx.file.icon.short_path if ctx.file.icon else "", + ) + + binding_command = "" + if ctx.executable.bindings: + binding_command = "\"$bindings\"\n" + + command_lines = """ +{binding_command}exec "$tool" \ + --binary "$binary" \ + --build-assets "$build_assets" \ + --frontend-url {frontend_url} \ + --icon "$icon" \ + --mode 'dev' +""".format( + binding_command = binding_command, + frontend_url = _shell_quote(ctx.attr.frontend_url), + ) + + ctx.actions.write( + output = launcher, + is_executable = True, + content = bash_launcher(resolve_lines, command_lines), + ) + + transitive_files = [ + ctx.attr._tool[DefaultInfo].default_runfiles.files, + ctx.attr.binary[DefaultInfo].default_runfiles.files, + ] + files = [ + ctx.executable._tool, + ctx.executable.binary, + ] + ctx.files.build_assets + ([ctx.file.icon] if ctx.file.icon else []) + + if ctx.attr.bindings: + transitive_files.append(ctx.attr.bindings[DefaultInfo].default_runfiles.files) + files.append(ctx.executable.bindings) + + runfiles = ctx.runfiles( + files = files, + transitive_files = depset(transitive = transitive_files), + ) + + return [DefaultInfo(executable = launcher, runfiles = runfiles)] + +_wails_bun_watch_run = rule( + implementation = _wails_bun_watch_run_impl, + attrs = { + "binary": attr.label( + mandatory = True, + executable = True, + cfg = "target", + ), + "bindings": attr.label( + executable = True, + cfg = "target", + ), + "build_assets": attr.label( + mandatory = True, + allow_files = True, + ), + "frontend_url": attr.string(default = "http://127.0.0.1:9245"), + "icon": attr.label(allow_single_file = True), + "_tool": attr.label( + default = "//wails/tools:launch_app", + cfg = "exec", + executable = True, + ), + }, + executable = True, +) + +def _wails_bun_dev_session_impl(ctx): + launcher = ctx.actions.declare_file(ctx.label.name) + resolve_lines = """ +tool="$(resolve_runfile "{tool_short_path}")" +frontend_dev="$(resolve_runfile "{frontend_dev_short_path}")" +""".format( + tool_short_path = ctx.executable._tool.short_path, + frontend_dev_short_path = ctx.executable.frontend_dev.short_path, + ) + + command_lines = """ +exec "$tool" \ + --frontend-dev "$frontend_dev" \ + --frontend-url {frontend_url} \ + --watch-target {watch_target} \ + --workspace-dir {workspace_dir} +""".format( + frontend_url = _shell_quote(ctx.attr.frontend_url), + watch_target = _shell_quote(ctx.attr.watch_target), + workspace_dir = _shell_quote(ctx.attr.workspace_dir), + ) + + ctx.actions.write( + output = launcher, + is_executable = True, + content = bash_launcher(resolve_lines, command_lines), + ) + + runfiles = ctx.runfiles( + files = [ + ctx.executable._tool, + ctx.executable.frontend_dev, + ], + transitive_files = depset(transitive = [ + ctx.attr._tool[DefaultInfo].default_runfiles.files, + ctx.attr.frontend_dev[DefaultInfo].default_runfiles.files, + ]), + ) + + return [DefaultInfo(executable = launcher, runfiles = runfiles)] + +_wails_bun_dev_session_rule = rule( + implementation = _wails_bun_dev_session_impl, + attrs = { + "frontend_dev": attr.label( + mandatory = True, + executable = True, + cfg = "target", + ), + "frontend_url": attr.string(default = "http://127.0.0.1:9245"), + "watch_target": attr.string(mandatory = True), + "workspace_dir": attr.string(default = "."), + "_tool": attr.label( + default = "//wails_bun/tools:bun_dev_session", + cfg = "exec", + executable = True, + ), + }, + executable = True, +) + +def wails_bun_dev_session( + name, + workspace_dir, + frontend_dev, + app_binary, + build_assets, + bindings_target = None, + icon = None, + frontend_url = "http://127.0.0.1:9245", + tags = None, + visibility = None): + watch_name = name + "_watch" + + _wails_bun_watch_run( + name = watch_name, + binary = app_binary, + bindings = bindings_target, + build_assets = build_assets, + frontend_url = frontend_url, + icon = icon, + tags = ["manual"], + visibility = ["//visibility:private"], + ) + + _wails_bun_dev_session_rule( + name = name, + frontend_dev = frontend_dev, + frontend_url = frontend_url, + tags = tags, + visibility = visibility, + watch_target = _absolute_label(native.package_name(), watch_name), + workspace_dir = workspace_dir, + ) diff --git a/wails_bun/private/frontend_dist.bzl b/wails_bun/private/frontend_dist.bzl new file mode 100644 index 0000000..48e4e66 --- /dev/null +++ b/wails_bun/private/frontend_dist.bzl @@ -0,0 +1,274 @@ +"""Bun frontend build helpers for Wails.""" + +load("//wails:private/common.bzl", "bash_launcher", "write_manifest") + +def _node_modules_root_from_path(path): + marker = "/node_modules/" + marker_index = path.find(marker) + if marker_index < 0: + return None + return path[:marker_index + len("/node_modules")] + +def _dirname(path): + index = path.rfind("/") + if index < 0: + return "." + return path[:index] or "." + +def _wails_bun_frontend_dist_impl(ctx): + out_dir = ctx.actions.declare_directory(ctx.label.name) + + manifest_srcs = list(ctx.files.srcs) + if ctx.file.package_json.path not in [src.path for src in manifest_srcs]: + manifest_srcs.append(ctx.file.package_json) + + manifest = write_manifest( + ctx, + ctx.label.name + ".manifest", + manifest_srcs, + ctx.attr.strip_prefix, + ) + + if not ctx.files.node_modules: + fail("wails_bun_frontend_dist requires a non-empty node_modules tree") + + workspace_marker = "" + if ctx.attr.workspace_dir: + workspace_marker = "/%s/node_modules/" % ctx.attr.workspace_dir.strip("/") + + shortest_path = None + shared_node_modules_root = None + workspace_node_modules_root = None + + for src in ctx.files.node_modules: + if shortest_path == None or len(src.path) < len(shortest_path): + shortest_path = src.path + if workspace_marker and workspace_marker in src.path and workspace_node_modules_root == None: + workspace_node_modules_root = _node_modules_root_from_path(src.path) + + if shortest_path: + shared_node_modules_root = _node_modules_root_from_path(shortest_path) + + node_modules_root = workspace_node_modules_root or shared_node_modules_root + if node_modules_root == None or shared_node_modules_root == None: + fail("unable to determine node_modules roots from node_modules inputs") + + package_json_rel = ctx.file.package_json.short_path + if ctx.attr.strip_prefix and package_json_rel.startswith(ctx.attr.strip_prefix): + package_json_rel = package_json_rel[len(ctx.attr.strip_prefix):] + package_dir = _dirname(package_json_rel) + + args = ctx.actions.args() + args.add("--build-script", ctx.attr.build_script) + args.add("--manifest", manifest.path) + args.add("--node-modules-root", node_modules_root) + args.add("--out", out_dir.path) + args.add("--package-dir", package_dir) + args.add("--shared-node-modules-root", shared_node_modules_root) + args.add("--bun-darwin-aarch64", ctx.file._bun_darwin_aarch64.path) + args.add("--bun-darwin-x64", ctx.file._bun_darwin_x64.path) + args.add("--bun-linux-aarch64", ctx.file._bun_linux_aarch64.path) + args.add("--bun-linux-x64", ctx.file._bun_linux_x64.path) + args.add("--bun-windows-x64", ctx.file._bun_windows_x64.path) + if ctx.attr.workspace_dir: + args.add("--workspace-dir", ctx.attr.workspace_dir) + + ctx.actions.run( + executable = ctx.executable._tool, + arguments = [args], + inputs = depset( + manifest_srcs + + ctx.files.node_modules + [ + manifest, + ctx.file._bun_darwin_aarch64, + ctx.file._bun_darwin_x64, + ctx.file._bun_linux_aarch64, + ctx.file._bun_linux_x64, + ctx.file._bun_windows_x64, + ], + ), + outputs = [out_dir], + tools = [ctx.attr._tool[DefaultInfo].files_to_run], + mnemonic = "WailsBunFrontendDist", + progress_message = "Building Bun frontend dist for %s" % ctx.label, + ) + + return [DefaultInfo(files = depset([out_dir]))] + +def _wails_bun_frontend_dev_impl(ctx): + launcher = ctx.actions.declare_file(ctx.label.name) + package_dir = _dirname(ctx.file.package_json.short_path) + + resolve_lines = """ +tool="$(resolve_runfile "{tool_short_path}")" +bun_darwin_aarch64="$(resolve_runfile "{bun_darwin_aarch64_short_path}")" +bun_darwin_x64="$(resolve_runfile "{bun_darwin_x64_short_path}")" +bun_linux_aarch64="$(resolve_runfile "{bun_linux_aarch64_short_path}")" +bun_linux_x64="$(resolve_runfile "{bun_linux_x64_short_path}")" +bun_windows_x64="$(resolve_runfile "{bun_windows_x64_short_path}")" +""".format( + tool_short_path = ctx.executable._tool.short_path, + bun_darwin_aarch64_short_path = ctx.file._bun_darwin_aarch64.short_path, + bun_darwin_x64_short_path = ctx.file._bun_darwin_x64.short_path, + bun_linux_aarch64_short_path = ctx.file._bun_linux_aarch64.short_path, + bun_linux_x64_short_path = ctx.file._bun_linux_x64.short_path, + bun_windows_x64_short_path = ctx.file._bun_windows_x64.short_path, + ) + + command_lines = """ +exec "$tool" \ + --bun-darwin-aarch64 "$bun_darwin_aarch64" \ + --bun-darwin-x64 "$bun_darwin_x64" \ + --bun-linux-aarch64 "$bun_linux_aarch64" \ + --bun-linux-x64 "$bun_linux_x64" \ + --bun-windows-x64 "$bun_windows_x64" \ + --package-dir {package_dir} \ + --script {script} +""".format( + package_dir = _shell_quote(ctx.attr.workspace_dir or package_dir), + script = _shell_quote(ctx.attr.script), + ) + + ctx.actions.write( + output = launcher, + is_executable = True, + content = bash_launcher(resolve_lines, command_lines), + ) + + runfiles = ctx.runfiles( + files = [ + ctx.executable._tool, + ctx.file._bun_darwin_aarch64, + ctx.file._bun_darwin_x64, + ctx.file._bun_linux_aarch64, + ctx.file._bun_linux_x64, + ctx.file._bun_windows_x64, + ] + ([ctx.file.package_json] if ctx.file.package_json else []), + transitive_files = depset(transitive = [ + ctx.attr._tool[DefaultInfo].default_runfiles.files, + ]), + ) + + return [DefaultInfo(executable = launcher, runfiles = runfiles)] + +def _shell_quote(value): + return "'" + value.replace("'", "'\"'\"'") + "'" + +wails_bun_frontend_dist = rule( + implementation = _wails_bun_frontend_dist_impl, + attrs = { + "build_script": attr.string(default = "build"), + "node_modules": attr.label( + mandatory = True, + allow_files = True, + ), + "package_json": attr.label( + mandatory = True, + allow_single_file = True, + ), + "srcs": attr.label_list( + mandatory = True, + allow_files = True, + ), + "strip_prefix": attr.string(default = ""), + "workspace_dir": attr.string(default = ""), + "_tool": attr.label( + default = "//wails_bun/tools:frontend_dist_action", + cfg = "exec", + executable = True, + ), + "_bun_darwin_aarch64": attr.label( + default = "@bun_darwin_aarch64//:bun", + cfg = "exec", + allow_single_file = True, + ), + "_bun_darwin_x64": attr.label( + default = "@bun_darwin_x64//:bun", + cfg = "exec", + allow_single_file = True, + ), + "_bun_linux_aarch64": attr.label( + default = "@bun_linux_aarch64//:bun", + cfg = "exec", + allow_single_file = True, + ), + "_bun_linux_x64": attr.label( + default = "@bun_linux_x64//:bun", + cfg = "exec", + allow_single_file = True, + ), + "_bun_windows_x64": attr.label( + default = "@bun_windows_x64//:bun", + cfg = "exec", + allow_single_file = True, + ), + }, +) + +_wails_bun_frontend_dev = rule( + implementation = _wails_bun_frontend_dev_impl, + attrs = { + "data": attr.label_list(allow_files = True), + "node_modules": attr.label( + mandatory = True, + allow_files = True, + ), + "package_json": attr.label( + mandatory = True, + allow_single_file = True, + ), + "script": attr.string(default = "dev"), + "workspace_dir": attr.string(default = ""), + "_tool": attr.label( + default = "//wails_bun/tools:frontend_dev_server", + cfg = "exec", + executable = True, + ), + "_bun_darwin_aarch64": attr.label( + default = "@bun_darwin_aarch64//:bun", + cfg = "exec", + allow_single_file = True, + ), + "_bun_darwin_x64": attr.label( + default = "@bun_darwin_x64//:bun", + cfg = "exec", + allow_single_file = True, + ), + "_bun_linux_aarch64": attr.label( + default = "@bun_linux_aarch64//:bun", + cfg = "exec", + allow_single_file = True, + ), + "_bun_linux_x64": attr.label( + default = "@bun_linux_x64//:bun", + cfg = "exec", + allow_single_file = True, + ), + "_bun_windows_x64": attr.label( + default = "@bun_windows_x64//:bun", + cfg = "exec", + allow_single_file = True, + ), + }, + executable = True, +) + +def wails_bun_frontend_dev( + name, + data, + node_modules, + package_json, + script = "dev", + workspace_dir = "", + tags = None, + visibility = None): + _wails_bun_frontend_dev( + name = name, + data = data, + node_modules = node_modules, + package_json = package_json, + script = script, + tags = tags, + visibility = visibility, + workspace_dir = workspace_dir, + ) diff --git a/wails_bun/private/macros.bzl b/wails_bun/private/macros.bzl new file mode 100644 index 0000000..f19ebf8 --- /dev/null +++ b/wails_bun/private/macros.bzl @@ -0,0 +1,71 @@ +"""Macros for Bun-backed Wails applications.""" + +load("//wails:defs.bzl", "wails_build_assets", "wails_generate_bindings", "wails_run") +load(":private/dev_session.bzl", "wails_bun_dev_session") +load(":private/frontend_dist.bzl", "wails_bun_frontend_dev", "wails_bun_frontend_dist") + +def wails_bun_app( + name, + app_binary, + frontend_srcs, + package_json, + node_modules, + build_asset_srcs, + app_name, + binary_name, + icon = None, + bindings_package_dir = ".", + frontend_strip_prefix = "", + build_strip_prefix = "", + visibility = None): + wails_bun_frontend_dist( + name = name + "_frontend_dist", + srcs = frontend_srcs, + node_modules = node_modules, + package_json = package_json, + strip_prefix = frontend_strip_prefix, + visibility = visibility, + ) + + wails_build_assets( + name = name + "_build_assets", + srcs = build_asset_srcs, + app_name = app_name, + binary_name = binary_name, + strip_prefix = build_strip_prefix, + visibility = visibility, + ) + + wails_generate_bindings( + name = name + "_bindings", + out_dir = "frontend/src/lib/bindings", + package_dir = bindings_package_dir, + visibility = visibility, + ) + + wails_bun_frontend_dev( + name = name + "_frontend_dev", + data = frontend_srcs, + node_modules = node_modules, + package_json = package_json, + visibility = visibility, + ) + + wails_run( + name = name + "_run", + binary = app_binary, + build_assets = name + "_build_assets", + icon = icon, + visibility = visibility, + ) + + wails_bun_dev_session( + name = name + "_dev", + workspace_dir = native.package_name() or ".", + frontend_dev = name + "_frontend_dev", + app_binary = app_binary, + build_assets = name + "_build_assets", + bindings_target = name + "_bindings", + icon = icon, + visibility = visibility, + ) diff --git a/wails_bun/tools/BUILD.bazel b/wails_bun/tools/BUILD.bazel new file mode 100644 index 0000000..76dfa18 --- /dev/null +++ b/wails_bun/tools/BUILD.bazel @@ -0,0 +1,25 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary") + +go_binary( + name = "frontend_dist_action", + srcs = ["frontend_dist_action.go"], + importpath = "github.com/Eriyc/rules_wails/wails_bun/tools/frontend_dist_action", + pure = "off", + visibility = ["//visibility:public"], +) + +go_binary( + name = "bun_dev_session", + srcs = ["bun_dev_session.go"], + importpath = "github.com/Eriyc/rules_wails/wails_bun/tools/bun_dev_session", + pure = "off", + visibility = ["//visibility:public"], +) + +go_binary( + name = "frontend_dev_server", + srcs = ["frontend_dev_server.go"], + importpath = "github.com/Eriyc/rules_wails/wails_bun/tools/frontend_dev_server", + pure = "off", + visibility = ["//visibility:public"], +) diff --git a/wails_bun/tools/bun_dev_session.go b/wails_bun/tools/bun_dev_session.go new file mode 100644 index 0000000..f4da6ba --- /dev/null +++ b/wails_bun/tools/bun_dev_session.go @@ -0,0 +1,177 @@ +package main + +import ( + "flag" + "fmt" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" + "time" +) + +func main() { + var frontendDev string + var frontendURL string + var readyTimeout time.Duration + var watchTarget string + var workspaceDir string + + flag.StringVar(&frontendDev, "frontend-dev", "", "") + flag.StringVar(&frontendURL, "frontend-url", "http://127.0.0.1:9245", "") + flag.DurationVar(&readyTimeout, "ready-timeout", 2*time.Minute, "") + flag.StringVar(&watchTarget, "watch-target", "", "") + flag.StringVar(&workspaceDir, "workspace-dir", ".", "") + flag.Parse() + + require(frontendDev != "", "missing --frontend-dev") + require(watchTarget != "", "missing --watch-target") + + workspaceRoot := os.Getenv("BUILD_WORKSPACE_DIRECTORY") + if workspaceRoot == "" { + workspaceRoot = "." + } + runfilesDir := resolveRunfilesDir() + + frontendCommand := exec.Command(frontendDev) + frontendCommand.Dir = workspaceRoot + frontendCommand.Env = withRunfilesEnv(os.Environ(), runfilesDir) + frontendCommand.Stdout = os.Stdout + frontendCommand.Stderr = os.Stderr + + must(frontendCommand.Start()) + waitCh := make(chan error, 1) + go func() { + waitCh <- frontendCommand.Wait() + }() + defer terminate(frontendCommand, waitCh) + + must(waitForURL(frontendURL, readyTimeout, waitCh)) + + watchCommand, err := resolveWatchCommand(watchTarget) + must(err) + watchCommand.Dir = workspaceRoot + watchCommand.Env = os.Environ() + watchCommand.Stdout = os.Stdout + watchCommand.Stderr = os.Stderr + watchCommand.Stdin = os.Stdin + + if err := watchCommand.Run(); err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + os.Exit(exitError.ExitCode()) + } + panic(err) + } + + _ = workspaceDir +} + +func resolveRunfilesDir() string { + for _, candidate := range []string{ + os.Getenv("RUNFILES_DIR"), + os.Getenv("RUNFILES"), + } { + if candidate != "" { + return candidate + } + } + + executablePath, err := os.Executable() + if err != nil { + return "" + } + + normalizedPath := filepath.Clean(executablePath) + marker := ".runfiles" + if index := strings.Index(normalizedPath, marker+string(os.PathSeparator)); index >= 0 { + return normalizedPath[:index+len(marker)] + } + if strings.HasSuffix(normalizedPath, marker) { + return normalizedPath + } + + return "" +} + +func withRunfilesEnv(environment []string, runfilesDir string) []string { + if runfilesDir == "" { + return environment + } + + environment = setEnv(environment, "RUNFILES_DIR", runfilesDir) + environment = setEnv(environment, "RUNFILES", runfilesDir) + return environment +} + +func setEnv(environment []string, key string, value string) []string { + prefix := key + "=" + for index, entry := range environment { + if strings.HasPrefix(entry, prefix) { + environment[index] = prefix + value + return environment + } + } + return append(environment, prefix+value) +} + +func waitForURL(url string, timeout time.Duration, waitCh <-chan error) error { + client := http.Client{Timeout: 2 * time.Second} + deadline := time.Now().Add(timeout) + + for time.Now().Before(deadline) { + select { + case err := <-waitCh: + if err == nil { + return fmt.Errorf("frontend dev server exited before becoming ready") + } + return fmt.Errorf("frontend dev server exited before becoming ready: %w", err) + default: + } + + response, err := client.Get(url) + if err == nil { + response.Body.Close() + if response.StatusCode >= 200 && response.StatusCode < 400 { + return nil + } + } + + time.Sleep(250 * time.Millisecond) + } + + return fmt.Errorf("frontend dev server did not become ready at %s", url) +} + +func resolveWatchCommand(target string) (*exec.Cmd, error) { + for _, candidate := range []string{"ibazel", "bazelisk", "bazel"} { + if _, err := exec.LookPath(candidate); err == nil { + return exec.Command(candidate, "run", target), nil + } + } + + return nil, fmt.Errorf("neither ibazel, bazelisk, nor bazel is available in PATH") +} + +func terminate(command *exec.Cmd, waitCh <-chan error) { + if command.Process == nil { + return + } + _ = command.Process.Kill() + select { + case <-waitCh: + case <-time.After(2 * time.Second): + } +} + +func must(err error) { + if err != nil { + panic(err) + } +} + +func require(condition bool, message string) { + if !condition { + panic(message) + } +} diff --git a/wails_bun/tools/frontend_dev_server.go b/wails_bun/tools/frontend_dev_server.go new file mode 100644 index 0000000..8697d22 --- /dev/null +++ b/wails_bun/tools/frontend_dev_server.go @@ -0,0 +1,139 @@ +package main + +import ( + "flag" + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +func main() { + var bunDarwinAarch64 string + var bunDarwinX64 string + var bunLinuxAarch64 string + var bunLinuxX64 string + var bunWindowsX64 string + var packageDir string + var script string + + flag.StringVar(&bunDarwinAarch64, "bun-darwin-aarch64", "", "") + flag.StringVar(&bunDarwinX64, "bun-darwin-x64", "", "") + flag.StringVar(&bunLinuxAarch64, "bun-linux-aarch64", "", "") + flag.StringVar(&bunLinuxX64, "bun-linux-x64", "", "") + flag.StringVar(&bunWindowsX64, "bun-windows-x64", "", "") + flag.StringVar(&packageDir, "package-dir", "", "") + flag.StringVar(&script, "script", "dev", "") + flag.Parse() + + require(packageDir != "", "missing --package-dir") + + bunPath, err := resolveBunBinary(bunDarwinAarch64, bunDarwinX64, bunLinuxAarch64, bunLinuxX64, bunWindowsX64) + must(err) + must(makeAbsolute(&bunPath)) + + workspaceRoot := os.Getenv("BUILD_WORKSPACE_DIRECTORY") + if workspaceRoot == "" { + returnError("BUILD_WORKSPACE_DIRECTORY is required for frontend dev sessions") + } + + packageRoot := filepath.Join(workspaceRoot, filepath.FromSlash(packageDir)) + if _, err := os.Stat(filepath.Join(packageRoot, "package.json")); err != nil { + returnError("frontend package.json not found in workspace package dir: " + packageRoot) + } + + environment := append([]string{}, os.Environ()...) + environment = setPath(environment, buildPath(workspaceRoot, packageRoot)) + + command := exec.Command(bunPath, "--bun", "run", script) + command.Dir = packageRoot + command.Env = environment + command.Stdout = os.Stdout + command.Stderr = os.Stderr + command.Stdin = os.Stdin + + if err := command.Run(); err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + os.Exit(exitError.ExitCode()) + } + panic(err) + } +} + +func resolveBunBinary(darwinAarch64 string, darwinX64 string, linuxAarch64 string, linuxX64 string, windowsX64 string) (string, error) { + switch runtime.GOOS + "/" + runtime.GOARCH { + case "darwin/arm64": + return darwinAarch64, nil + case "darwin/amd64": + return darwinX64, nil + case "linux/arm64": + return linuxAarch64, nil + case "linux/amd64": + return linuxX64, nil + case "windows/amd64": + return windowsX64, nil + default: + return "", fmt.Errorf("unsupported Bun exec platform: %s/%s", runtime.GOOS, runtime.GOARCH) + } +} + +func buildPath(workspaceRoot string, packageRoot string) string { + entries := make([]string, 0, 4) + for _, candidate := range []string{ + filepath.Join(packageRoot, "node_modules", ".bin"), + filepath.Join(workspaceRoot, "node_modules", ".bin"), + os.Getenv("PATH"), + } { + if candidate != "" { + entries = append(entries, candidate) + } + } + return strings.Join(entries, string(os.PathListSeparator)) +} + +func setPath(environment []string, pathValue string) []string { + return setEnv(environment, "PATH", pathValue) +} + +func setEnv(environment []string, key string, value string) []string { + prefix := key + "=" + for index, entry := range environment { + if strings.HasPrefix(entry, prefix) { + environment[index] = prefix + value + return environment + } + } + return append(environment, prefix+value) +} + +func makeAbsolute(path *string) error { + if filepath.IsAbs(*path) { + return nil + } + + absolutePath, err := filepath.Abs(*path) + if err != nil { + return err + } + *path = absolutePath + return nil +} + +func returnError(message string) { + fmt.Fprintln(os.Stderr, message) + os.Exit(1) +} + +func must(err error) { + if err != nil { + panic(err) + } +} + +func require(condition bool, message string) { + if !condition { + panic(message) + } +} diff --git a/wails_bun/tools/frontend_dist_action.go b/wails_bun/tools/frontend_dist_action.go new file mode 100644 index 0000000..a189168 --- /dev/null +++ b/wails_bun/tools/frontend_dist_action.go @@ -0,0 +1,423 @@ +package main + +import ( + "bufio" + "encoding/json" + "flag" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +type manifestEntry struct { + sourcePath string + relativePath string +} + +func main() { + var buildScript string + var bunDarwinAarch64 string + var bunDarwinX64 string + var bunLinuxAarch64 string + var bunLinuxX64 string + var bunWindowsX64 string + var manifestPath string + var nodeModulesRoot string + var outDir string + var packageDir string + var sharedNodeModulesRoot string + var workspaceDir string + + flag.StringVar(&buildScript, "build-script", "build", "") + flag.StringVar(&bunDarwinAarch64, "bun-darwin-aarch64", "", "") + flag.StringVar(&bunDarwinX64, "bun-darwin-x64", "", "") + flag.StringVar(&bunLinuxAarch64, "bun-linux-aarch64", "", "") + flag.StringVar(&bunLinuxX64, "bun-linux-x64", "", "") + flag.StringVar(&bunWindowsX64, "bun-windows-x64", "", "") + flag.StringVar(&manifestPath, "manifest", "", "") + flag.StringVar(&nodeModulesRoot, "node-modules-root", "", "") + flag.StringVar(&outDir, "out", "", "") + flag.StringVar(&packageDir, "package-dir", ".", "") + flag.StringVar(&sharedNodeModulesRoot, "shared-node-modules-root", "", "") + flag.StringVar(&workspaceDir, "workspace-dir", "", "") + flag.Parse() + + require(manifestPath != "", "missing --manifest") + require(nodeModulesRoot != "", "missing --node-modules-root") + require(outDir != "", "missing --out") + require(packageDir != "", "missing --package-dir") + require(sharedNodeModulesRoot != "", "missing --shared-node-modules-root") + + bunPath, err := resolveBunBinary(bunDarwinAarch64, bunDarwinX64, bunLinuxAarch64, bunLinuxX64, bunWindowsX64) + must(err) + must(makeAbsolute(&bunPath)) + must(makeAbsolute(&manifestPath)) + must(makeAbsolute(&nodeModulesRoot)) + must(makeAbsolute(&outDir)) + must(makeAbsolute(&sharedNodeModulesRoot)) + + tempRoot, err := os.MkdirTemp("", "rules-wails-frontend-*") + must(err) + defer os.RemoveAll(tempRoot) + + stageRoot := filepath.Join(tempRoot, "workspace") + homeDir := filepath.Join(tempRoot, "home") + must(os.MkdirAll(stageRoot, 0o755)) + must(os.MkdirAll(homeDir, 0o755)) + + must(stageManifest(manifestPath, stageRoot)) + must(os.MkdirAll(filepath.Join(stageRoot, "node_modules"), 0o755)) + must(copyDirectoryContents(nodeModulesRoot, filepath.Join(stageRoot, "node_modules"), true)) + + sharedBunStore := filepath.Join(sharedNodeModulesRoot, ".bun") + if pathExists(sharedBunStore) { + stageBunStore := filepath.Join(stageRoot, "node_modules", ".bun") + _ = os.RemoveAll(stageBunStore) + must(os.Symlink(sharedBunStore, stageBunStore)) + } + + must(restoreBunLinks(stageRoot)) + must(overlayWorkspacePackages(stageRoot)) + must(installWorkspaceAlias(stageRoot, packageDir, workspaceDir)) + must(linkPackageNodeModules(stageRoot, packageDir)) + + packageRoot := stageRoot + if packageDir != "." { + packageRoot = filepath.Join(stageRoot, filepath.FromSlash(packageDir)) + } + + command := exec.Command(bunPath, "--bun", "run", buildScript) + command.Dir = packageRoot + command.Env = append(os.Environ(), + "HOME="+homeDir, + "PATH="+buildPath(stageRoot, packageRoot), + ) + command.Stdout = os.Stdout + command.Stderr = os.Stderr + command.Stdin = os.Stdin + must(command.Run()) + + must(os.RemoveAll(outDir)) + must(os.MkdirAll(outDir, 0o755)) + must(copyDirectoryContents(filepath.Join(packageRoot, "dist"), outDir, false)) +} + +func resolveBunBinary(darwinAarch64 string, darwinX64 string, linuxAarch64 string, linuxX64 string, windowsX64 string) (string, error) { + switch runtime.GOOS + "/" + runtime.GOARCH { + case "darwin/arm64": + return darwinAarch64, nil + case "darwin/amd64": + return darwinX64, nil + case "linux/arm64": + return linuxAarch64, nil + case "linux/amd64": + return linuxX64, nil + case "windows/amd64": + return windowsX64, nil + default: + return "", fmt.Errorf("unsupported Bun exec platform: %s/%s", runtime.GOOS, runtime.GOARCH) + } +} + +func overlayWorkspacePackages(stageRoot string) error { + packageRoots := make([]string, 0) + + err := filepath.Walk(stageRoot, func(path string, info os.FileInfo, walkErr error) error { + if walkErr != nil { + return walkErr + } + if info.IsDir() && info.Name() == "node_modules" { + return filepath.SkipDir + } + if !info.IsDir() && info.Name() == "package.json" { + packageRoots = append(packageRoots, filepath.Dir(path)) + } + return nil + }) + if err != nil { + return err + } + + for _, packageRoot := range packageRoots { + packageName, err := readPackageName(filepath.Join(packageRoot, "package.json")) + if err != nil || packageName == "" { + continue + } + + nodePackageDir := filepath.Join(append([]string{stageRoot, "node_modules"}, strings.Split(packageName, "/")...)...) + if !pathExists(nodePackageDir) { + continue + } + + entries, err := os.ReadDir(packageRoot) + if err != nil { + return err + } + + for _, entry := range entries { + entryPath := filepath.Join(packageRoot, entry.Name()) + destinationPath := filepath.Join(nodePackageDir, entry.Name()) + _ = os.RemoveAll(destinationPath) + if err := os.Symlink(entryPath, destinationPath); err != nil { + return err + } + } + } + + return nil +} + +func restoreBunLinks(stageRoot string) error { + bunPackagesRoot := filepath.Join(stageRoot, "node_modules", ".bun", "node_modules") + if !pathExists(bunPackagesRoot) { + return nil + } + + entries, err := os.ReadDir(bunPackagesRoot) + if err != nil { + return err + } + + for _, entry := range entries { + entryPath := filepath.Join(bunPackagesRoot, entry.Name()) + if strings.HasPrefix(entry.Name(), "@") { + scopeRoot := filepath.Join(stageRoot, "node_modules", entry.Name()) + if err := os.MkdirAll(scopeRoot, 0o755); err != nil { + return err + } + + scopedEntries, err := os.ReadDir(entryPath) + if err != nil { + return err + } + + for _, scopedEntry := range scopedEntries { + scopedPath := filepath.Join(entryPath, scopedEntry.Name()) + destinationPath := filepath.Join(scopeRoot, scopedEntry.Name()) + _ = os.RemoveAll(destinationPath) + if err := os.Symlink(scopedPath, destinationPath); err != nil { + return err + } + } + + continue + } + + destinationPath := filepath.Join(stageRoot, "node_modules", entry.Name()) + _ = os.RemoveAll(destinationPath) + if err := os.Symlink(entryPath, destinationPath); err != nil { + return err + } + } + + return nil +} + +func installWorkspaceAlias(stageRoot string, packageDir string, workspaceDir string) error { + if workspaceDir == "" || workspaceDir == "." || workspaceDir == packageDir { + return nil + } + + targetPath := stageRoot + if packageDir != "." { + targetPath = filepath.Join(stageRoot, filepath.FromSlash(packageDir)) + } + aliasPath := filepath.Join(stageRoot, filepath.FromSlash(workspaceDir)) + if err := os.MkdirAll(filepath.Dir(aliasPath), 0o755); err != nil { + return err + } + _ = os.RemoveAll(aliasPath) + return os.Symlink(targetPath, aliasPath) +} + +func linkPackageNodeModules(stageRoot string, packageDir string) error { + if packageDir == "." { + return nil + } + + packageNodeModules := filepath.Join(stageRoot, filepath.FromSlash(packageDir), "node_modules") + if pathExists(packageNodeModules) { + return nil + } + if err := os.MkdirAll(filepath.Dir(packageNodeModules), 0o755); err != nil { + return err + } + return os.Symlink(filepath.Join(stageRoot, "node_modules"), packageNodeModules) +} + +func buildPath(stageRoot string, packageRoot string) string { + entries := make([]string, 0, 3) + for _, candidate := range []string{ + filepath.Join(packageRoot, "node_modules", ".bin"), + filepath.Join(stageRoot, "node_modules", ".bin"), + os.Getenv("PATH"), + } { + if candidate != "" { + entries = append(entries, candidate) + } + } + return strings.Join(entries, string(os.PathListSeparator)) +} + +func stageManifest(manifestPath string, destinationRoot string) error { + entries, err := readManifest(manifestPath) + if err != nil { + return err + } + + for _, entry := range entries { + destinationPath := filepath.Join(destinationRoot, filepath.FromSlash(entry.relativePath)) + if err := os.MkdirAll(filepath.Dir(destinationPath), 0o755); err != nil { + return err + } + if err := copyFile(entry.sourcePath, destinationPath); err != nil { + return err + } + } + + return nil +} + +func readManifest(path string) ([]manifestEntry, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + entries := make([]manifestEntry, 0) + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + parts := strings.SplitN(line, "\t", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid manifest line: %s", line) + } + entries = append(entries, manifestEntry{ + sourcePath: parts[0], + relativePath: parts[1], + }) + } + + return entries, scanner.Err() +} + +func readPackageName(path string) (string, error) { + file, err := os.Open(path) + if err != nil { + return "", err + } + defer file.Close() + + var payload struct { + Name string `json:"name"` + } + if err := json.NewDecoder(file).Decode(&payload); err != nil { + return "", err + } + return payload.Name, nil +} + +func copyDirectoryContents(sourceRoot string, destinationRoot string, preserveSymlinks bool) error { + if preserveSymlinks { + copyBinary, err := resolveCopyBinary() + if err != nil { + return err + } + command := exec.Command(copyBinary, "-R", sourceRoot+"/.", destinationRoot+"/") + command.Stdout = os.Stdout + command.Stderr = os.Stderr + return command.Run() + } + + return filepath.Walk(sourceRoot, func(path string, info os.FileInfo, walkErr error) error { + if walkErr != nil { + return walkErr + } + + relativePath, err := filepath.Rel(sourceRoot, path) + if err != nil { + return err + } + if relativePath == "." { + return nil + } + + destinationPath := filepath.Join(destinationRoot, relativePath) + if info.IsDir() { + return os.MkdirAll(destinationPath, 0o755) + } + + if err := os.MkdirAll(filepath.Dir(destinationPath), 0o755); err != nil { + return err + } + + return copyFile(path, destinationPath) + }) +} + +func resolveCopyBinary() (string, error) { + for _, candidate := range []string{"/bin/cp", "/usr/bin/cp"} { + if pathExists(candidate) { + return candidate, nil + } + } + return "", fmt.Errorf("unable to locate cp binary for %s/%s", runtime.GOOS, runtime.GOARCH) +} + +func copyFile(sourcePath string, destinationPath string) error { + sourceFile, err := os.Open(sourcePath) + if err != nil { + return err + } + defer sourceFile.Close() + + destinationFile, err := os.Create(destinationPath) + if err != nil { + return err + } + defer destinationFile.Close() + + if _, err := io.Copy(destinationFile, sourceFile); err != nil { + return err + } + + return destinationFile.Chmod(0o644) +} + +func makeAbsolute(path *string) error { + if filepath.IsAbs(*path) { + return nil + } + + absolutePath, err := filepath.Abs(*path) + if err != nil { + return err + } + *path = absolutePath + return nil +} + +func pathExists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +func must(err error) { + if err != nil { + panic(err) + } +} + +func require(condition bool, message string) { + if !condition { + panic(message) + } +}