diff --git a/.github/workflows/cliff.toml b/.github/workflows/cliff.toml index 9a63d04..7009d49 100644 --- a/.github/workflows/cliff.toml +++ b/.github/workflows/cliff.toml @@ -6,7 +6,7 @@ header = """ # template for the changelog body # https://tera.netlify.app/docs/#introduction body = """ -{%- set repo = "https://github.com/tami5/xbase" -%}\ +{%- set repo = "https://github.com/xbase-lab/xbase" -%}\ {% if version %}\ ## 🎉 [{{ version }}]({{ repo }}/tree/{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index bc8587e..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,69 +0,0 @@ -# Contributing - -Please checkout [milestones](https://github.com/tami5/xbase/milestones) for planned future releases and features. - -- [Project Organization](#project-organization) -- [Development Setup](#development-setup) - -## Project Organization - -Here's an overview of the project architecture to help you contribute. - -### [proto] crate - -Client/Daemon requests and types definitions. - -### [daemon] crate - -The main product of [xbase] reside, it handle requests and data defined in [proto] crate. - -- [build.rs](./daemon/src/build.rs): build request handler definition. -- [run.rs](./daemon/src/run.rs): run request handler definition. -- [drop.rs](./daemon/src/drop.rs): drop request handler definition. -- [register.rs](./daemon/src/register.rs): register request handler definition. -- [nvim.rs](./daemon/src/nvim.rs): helper methods to interact with running nvim instance. -- [project.rs](./daemon/src/project/mod.rs): traits to be implement for each supported project setup. - - [xcodegen.rs](./daemon/src/project/xcodegen.rs): implantation of project traits for xcodegen projects. - - [tuist.rs](./daemon/src/project/tuist.rs): implantation of project traits for tuist projects. - - [barebone.rs](./daemon/src/project/barebone.rs): implantation of project traits for barebone (i.e. no generators) projects. - - [swift.rs](./daemon/src/project/swift.rs): implantation of project traits for swift (i.e. package.swift) projects. - -### [sourcekit] crate - -The Helper build server implementing [BSP] protocol required because [sourcekit-lsp] can't define compile arguments required to _jump to definition_ and _symbol definition_. - -### [client] crate - -The neovim editor library. provide convenient and backed function to run in neovim runtime - -[sourcekit]: ./sourcekit/ -[daemon]: ./daemon/ -[client]: ./client/ -[proto]: ./proto/ -[xbase]: https://github.com/tami5/xbase -[BSP]: https://build-server-protocol.github.io -[sourcekit-lsp]: https://github.com/apple/sourcekit-lsp - -## Development Setup - -#### Clone the repo - -```sh -git clone https://github.com/tami5/xbase -``` - -#### Install in debug mode - -Do all the setup required to watch and develop - -```sh -make install_debug -``` - -#### Start watchers - -Watch all the products and trigger recompile when the source code changes. - -```sh -make watch -``` diff --git a/Cargo.lock b/Cargo.lock index f95b55b..fa8e368 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,7 +17,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -64,27 +64,12 @@ dependencies = [ "syn 1.0.98", ] -[[package]] -name = "async_once" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ce4f10ea3abcd6617873bae9f91d1c5332b4a778bd9ce34d0cd517474c1de82" - [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bindgen" version = "0.59.2" @@ -179,12 +164,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "bumpalo" -version = "3.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" - [[package]] name = "byte-tools" version = "0.3.1" @@ -218,12 +197,6 @@ dependencies = [ "nom", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -267,7 +240,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", ] @@ -277,7 +250,7 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "once_cell", ] @@ -340,7 +313,7 @@ checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -407,15 +380,6 @@ dependencies = [ "syn 1.0.98", ] -[[package]] -name = "erased-serde" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d013529d5574a60caeda29e179e695125448e5de52e3874f7b4c1d7360e18e" -dependencies = [ - "serde", -] - [[package]] name = "errno" version = "0.2.8" @@ -424,7 +388,7 @@ checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ "errno-dragonfly", "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -449,18 +413,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall", - "winapi 0.3.9", + "winapi", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "form_urlencoded" version = "1.0.1" @@ -471,25 +429,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fsevent" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" -dependencies = [ - "bitflags", - "fsevent-sys 2.0.1", -] - -[[package]] -name = "fsevent-sys" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" -dependencies = [ - "libc", -] - [[package]] name = "fsevent-sys" version = "4.1.0" @@ -499,22 +438,6 @@ dependencies = [ "libc", ] -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - [[package]] name = "futures" version = "0.3.21" @@ -599,7 +522,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.9", + "pin-project-lite", "pin-utils", "slab", ] @@ -619,7 +542,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "wasi 0.9.0+wasi-snapshot-preview1", ] @@ -630,7 +553,7 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "wasi 0.11.0+wasi-snapshot-preview1", ] @@ -641,12 +564,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -[[package]] -name = "hashbrown" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" - [[package]] name = "heck" version = "0.3.3" @@ -671,12 +588,6 @@ dependencies = [ "libc", ] -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "idna" version = "0.2.3" @@ -694,27 +605,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cfe9645a18782869361d9c8732246be7b410ad4e919d3609ebabdac00ba12c3" -[[package]] -name = "indexmap" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "inotify" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" -dependencies = [ - "bitflags", - "inotify-sys", - "libc", -] - [[package]] name = "inotify" version = "0.9.6" @@ -735,15 +625,6 @@ dependencies = [ "libc", ] -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - [[package]] name = "itertools" version = "0.10.3" @@ -765,25 +646,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5" -[[package]] -name = "js-sys" -version = "0.3.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "kqueue" version = "1.0.6" @@ -851,8 +713,8 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" dependencies = [ - "cfg-if 1.0.0", - "winapi 0.3.9", + "cfg-if", + "winapi", ] [[package]] @@ -866,24 +728,6 @@ dependencies = [ "libc", ] -[[package]] -name = "linemux" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faffd44046d5dcc8b31e76840fa2a9e975b3b6e6ad2db576cf71dca8c27945b9" -dependencies = [ - "futures-util", - "notify 5.0.0-pre.15", - "pin-project-lite 0.1.12", - "tokio", -] - -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "lock_api" version = "0.4.7" @@ -894,24 +738,13 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "log" -version = "0.1.0" -dependencies = [ - "thiserror", - "tracing", - "tracing-appender", - "tracing-attributes", - "tracing-subscriber", -] - [[package]] name = "log" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -927,24 +760,6 @@ dependencies = [ "url", ] -[[package]] -name = "lua-src" -version = "544.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708ba3c844d5e9d38def4a09dd871c17c370f519b3c4b7261fbabe4a613a814c" -dependencies = [ - "cc", -] - -[[package]] -name = "luajit-src" -version = "210.4.0+resty124ff8d" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76fb2e2c0c7192e18719d321c9a148f7625c4dcbe3df5f4c19e685e4c286f6c" -dependencies = [ - "cc", -] - [[package]] name = "maplit" version = "1.0.2" @@ -978,25 +793,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "mio" -version = "0.6.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" -dependencies = [ - "cfg-if 0.1.10", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log 0.4.17", - "miow", - "net2", - "slab", - "winapi 0.2.8", -] - [[package]] name = "mio" version = "0.8.4" @@ -1004,83 +800,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", - "log 0.4.17", + "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys", ] -[[package]] -name = "mio-extras" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" -dependencies = [ - "lazycell", - "log 0.4.17", - "mio 0.6.23", - "slab", -] - -[[package]] -name = "miow" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", -] - -[[package]] -name = "mlua" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75eb178f8a9d6654ef31f094fb3da6d1e81e714f27007e92a3094ddd6587aa2" -dependencies = [ - "bstr", - "cc", - "erased-serde", - "futures-core", - "futures-task", - "futures-util", - "lua-src", - "luajit-src", - "mlua_derive", - "num-traits", - "once_cell", - "pkg-config", - "rustc-hash", - "serde", -] - -[[package]] -name = "mlua_derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9214e60d3cf1643013b107330fcd374ccec1e4ba1eef76e7e5da5e8202e71c0" -dependencies = [ - "itertools", - "once_cell", - "proc-macro-error", - "proc-macro2", - "quote 1.0.20", - "regex", - "syn 1.0.98", -] - -[[package]] -name = "net2" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.9", -] - [[package]] name = "nom" version = "7.1.1" @@ -1104,24 +828,6 @@ dependencies = [ "nom", ] -[[package]] -name = "notify" -version = "4.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257" -dependencies = [ - "bitflags", - "filetime", - "fsevent", - "fsevent-sys 2.0.1", - "inotify 0.7.1", - "libc", - "mio 0.6.23", - "mio-extras", - "walkdir", - "winapi 0.3.9", -] - [[package]] name = "notify" version = "5.0.0-pre.15" @@ -1131,13 +837,13 @@ dependencies = [ "bitflags", "crossbeam-channel", "filetime", - "fsevent-sys 4.1.0", - "inotify 0.9.6", + "fsevent-sys", + "inotify", "kqueue", "libc", - "mio 0.8.4", + "mio", "walkdir", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1201,25 +907,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" -[[package]] -name = "opentelemetry" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6105e89802af13fdf48c49d7646d3b533a70e536d818aae7e78ba0433d01acb8" -dependencies = [ - "async-trait", - "crossbeam-channel", - "futures-channel", - "futures-executor", - "futures-util", - "js-sys", - "lazy_static", - "percent-encoding", - "pin-project", - "rand 0.8.5", - "thiserror", -] - [[package]] name = "os_pipe" version = "1.0.1" @@ -1227,7 +914,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c92f2b54f081d635c77e7120862d48db8e91f7f21cef23ab1b4fe9971c59f55" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1238,10 +925,10 @@ checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" dependencies = [ "futures", "libc", - "log 0.4.17", + "log", "rand 0.7.3", "tokio", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1260,7 +947,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall", "smallvec", @@ -1392,30 +1079,24 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" dependencies = [ "proc-macro2", "quote 1.0.20", "syn 1.0.98", ] -[[package]] -name = "pin-project-lite" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" - [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1428,12 +1109,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" - [[package]] name = "pori" version = "0.0.0" @@ -1449,30 +1124,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote 1.0.20", - "syn 1.0.98", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote 1.0.20", - "version_check", -] - [[package]] name = "proc-macro2" version = "1.0.40" @@ -1673,9 +1324,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c" +checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" [[package]] name = "serde" @@ -1719,18 +1370,6 @@ dependencies = [ "syn 1.0.98", ] -[[package]] -name = "serde_yaml" -version = "0.8.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc" -dependencies = [ - "indexmap", - "ryu", - "serde", - "yaml-rust", -] - [[package]] name = "sha-1" version = "0.8.2" @@ -1764,6 +1403,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +[[package]] +name = "signal-hook" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -1773,6 +1422,18 @@ dependencies = [ "libc", ] +[[package]] +name = "signal-hook-tokio" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213241f76fb1e37e27de3b6aa1b068a2c333233b59cca6634f634b80a27ecf1e" +dependencies = [ + "futures-core", + "libc", + "signal-hook", + "tokio", +] + [[package]] name = "simctl" version = "0.1.1" @@ -1807,15 +1468,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strum" version = "0.24.1" @@ -1875,41 +1530,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "tarpc" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38a012bed6fb9681d3bf71ffaa4f88f3b4b9ed3198cda6e4c8462d24d4bb80" -dependencies = [ - "anyhow", - "fnv", - "futures", - "humantime", - "opentelemetry", - "pin-project", - "rand 0.8.5", - "serde", - "static_assertions", - "tarpc-plugins", - "thiserror", - "tokio", - "tokio-serde", - "tokio-util", - "tracing", - "tracing-opentelemetry", -] - -[[package]] -name = "tarpc-plugins" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee42b4e559f17bce0385ebf511a7beb67d5cc33c12c96b7f4e9789919d9c10f" -dependencies = [ - "proc-macro2", - "quote 1.0.20", - "syn 1.0.98", -] - [[package]] name = "thiserror" version = "1.0.31" @@ -1974,15 +1594,15 @@ dependencies = [ "bytes", "libc", "memchr", - "mio 0.8.4", + "mio", "num_cpus", "once_cell", "parking_lot", - "pin-project-lite 0.2.9", + "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2002,7 +1622,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "911a61637386b789af998ee23f50aa30d5fd7edcec8d6d3dedae5e5815205466" dependencies = [ - "bincode", "bytes", "educe", "futures-core", @@ -2019,23 +1638,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" dependencies = [ "futures-core", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", ] [[package]] name = "tokio-util" -version = "0.6.10" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" dependencies = [ "bytes", "futures-core", "futures-sink", - "log 0.4.17", - "pin-project-lite 0.2.9", - "slab", + "pin-project-lite", "tokio", + "tracing", ] [[package]] @@ -2044,9 +1662,8 @@ version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ - "cfg-if 1.0.0", - "log 0.4.17", - "pin-project-lite 0.2.9", + "cfg-if", + "pin-project-lite", "tracing-attributes", "tracing-core", ] @@ -2090,23 +1707,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "lazy_static", - "log 0.4.17", + "log", "tracing-core", ] -[[package]] -name = "tracing-opentelemetry" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93600c803bb15e2a32bd376001b8625587f268fe887669b5ac86af524637c242" -dependencies = [ - "once_cell", - "opentelemetry", - "tracing", - "tracing-core", - "tracing-subscriber", -] - [[package]] name = "tracing-subscriber" version = "0.3.11" @@ -2174,9 +1778,9 @@ checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "unicode-normalization" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dee68f85cab8cf68dec42158baf3a79a1cdc065a8b103025965d6ccb7f6cbd" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" dependencies = [ "tinyvec", ] @@ -2218,12 +1822,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "walkdir" version = "2.3.2" @@ -2231,7 +1829,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", - "winapi 0.3.9", + "winapi", "winapi-util", ] @@ -2247,60 +1845,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" -dependencies = [ - "cfg-if 1.0.0", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" -dependencies = [ - "bumpalo", - "lazy_static", - "log 0.4.17", - "proc-macro2", - "quote 1.0.20", - "syn 1.0.98", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" -dependencies = [ - "quote 1.0.20", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" -dependencies = [ - "proc-macro2", - "quote 1.0.20", - "syn 1.0.98", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" - [[package]] name = "wax" version = "0.5.0" @@ -2330,12 +1874,6 @@ dependencies = [ "libc", ] -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - [[package]] name = "winapi" version = "0.3.9" @@ -2346,12 +1884,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -2364,7 +1896,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2416,16 +1948,6 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "xbase" version = "0.2.0" @@ -2436,12 +1958,9 @@ dependencies = [ "derive-deref-rs", "dirs", "futures", - "lazy_static", "libc", "libproc", - "linemux", - "log 0.1.0", - "notify 5.0.0-pre.15", + "notify", "once_cell", "os_pipe", "parity-tokio-ipc", @@ -2449,72 +1968,28 @@ dependencies = [ "process-stream", "serde", "serde_json", - "serde_yaml", + "serde_repr", "shell-words", + "signal-hook", + "signal-hook-tokio", "simctl", "strum", "tap", - "tarpc", "thiserror", "tokio", + "tokio-serde", + "tokio-util", + "tracing", + "tracing-appender", + "tracing-attributes", + "tracing-subscriber", "tracing-test", "wax", "which", - "xbase-proto", "xclog", "xcodeproj", ] -[[package]] -name = "xbase-client" -version = "0.2.0" -dependencies = [ - "async-trait", - "async_once", - "derive-deref-rs", - "lazy_static", - "linemux", - "mlua", - "once_cell", - "os_pipe", - "paste", - "process-stream", - "serde", - "serde_json", - "tap", - "tarpc", - "tokio", - "wax", - "xbase-proto", - "xcodeproj", -] - -[[package]] -name = "xbase-proto" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-trait", - "futures", - "linemux", - "log 0.1.0", - "mlua", - "notify 4.0.17", - "paste", - "process-stream", - "serde", - "serde_json", - "simctl", - "strum", - "tarpc", - "thiserror", - "tokio", - "tokio-serde", - "tokio-util", - "which", - "xcodeproj", -] - [[package]] name = "xbase-sourcekit-helper" version = "0.2.0" @@ -2522,11 +1997,14 @@ dependencies = [ "anyhow", "bsp-server", "dirs", - "log 0.1.0", "once_cell", "serde", "serde_json", "tap", + "tracing", + "tracing-appender", + "tracing-attributes", + "tracing-subscriber", "xclog", ] @@ -2585,12 +2063,3 @@ dependencies = [ "tracing-subscriber", "wax", ] - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] diff --git a/Cargo.toml b/Cargo.toml index 2399cc1..fd1b8a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,64 @@ +[package] +name = "xbase" +version = "0.2.0" +edition = "2021" + +[dependencies] + +# Logging +tracing = { version = "0.1.32" } +tracing-attributes = { version = "0.1.21" } +tracing-appender = { version = "0.2.1" } +tracing-subscriber = { version = "0.3.9", features = ["env-filter"] } + +# Error Handling +anyhow = { version = "^1.0.58" } +thiserror = { version = "1.0.31" } + +# Serialization Feature +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0.79" } +serde_repr = { version = "0.1.8" } +strum = { version = "0.24.0", features = ["derive"] } + +# Async Runtime Feature +tokio = { version = "1.19.2", features = ["full"] } +tokio-util = { version = "0.7.3", features = ["codec"] } +tokio-serde = { version = "0.8.0", features = ["json"] } +futures = { version = "0.3.21" } +async-trait = { version = "0.1.52" } +async-stream = { version = "0.3.3" } +parity-tokio-ipc = { version = "0.9.0" } +process-stream = { version = "0.4.*", features = ["serde"] } +once_cell = { version = "1.12.0" } + +# Filesystem watcher +notify = { version = "5.0.0-pre.13" } +dirs = { version = "4.0" } +wax = { version = "0.5.0" } + +# Other +tap = { version = "1.0.1" } +xclog = { version = "0.3.*", features = ["with_tracing"] } +simctl = { git = "https://github.com/xbase-lab/simctl" } +derive-deref-rs = { version = "0.1.1"} +shell-words = { version = "1.1.0" } +xcodeproj = { version = "0.2.*" } + +# Unix Api +libc = { version = "0.2.126" } +libproc = { version = "0.12.0" } +paste = { version = "1.0.7" } +which = { version = "4.2.5" } +os_pipe = { version = "1.0.1" } +signal-hook = { version = "0.3.14" } +signal-hook-tokio = { version = "0.3.1", features = ["futures-v0_3"] } + [workspace] -members = [ - # Shared protocol - "proto", - # sourcekit build server helper - "sourcekit", - # Main xbase daemon - "daemon", - # library to communicate with the daemon - "client", -] +members = [ "crates/*" ] + +[dev-dependencies] +tracing-test = "0.2.2" [profile.dev] debug = 0 diff --git a/LICENSE b/LICENSE index a96ca21..80da097 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 tami5 +Copyright (c) 2022 kkharji Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index b376f85..25844a0 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,11 @@ $(VERBOSE).SILENT: .PHONY: test default: test -RELEASE_ROOT:target/release + +DEBUG_ROOT=target/release +RELEASE_ROOT=target/release +XBASE_LOCAL_ROOT=~/.local/share/xbase +ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) test: cargo test --workspace @@ -11,27 +15,25 @@ lint: nix-shell -p lua51Packages.luacheck --command 'luacheck lua/xbase && exit 0 || exit 1' watch: - cargo watch -x 'build -p xbase-sourcekit-helper -p xbase-client' -x 'run -p xbase' -w 'sourcekit' -w 'daemon' -w 'proto' -w 'client' -c + cargo watch -x 'build -p xbase-sourcekit-helper' -x 'run xbase' -w 'crates' -w 'src' -c clean: - rm -rf bin; - rm -rf lua/xbase_client.so + rm -rf $(XBASE_LOCAL_ROOT) bin install: clean killall xbase xbase-sourcekit-helper || true - mkdir bin - cargo build --release - mv target/release/xbase ./bin/xbase - mv target/release/xbase-sourcekit-helper ./bin/xbase-sourcekit-helper - mv target/release/libxbase_client.dylib ./lua/xbase_client.so + mkdir $(XBASE_LOCAL_ROOT) + cargo build -p xbase -p xbase-sourcekit-helper --release + mv target/release/xbase $(XBASE_LOCAL_ROOT)/xbase + mv target/release/xbase-sourcekit-helper $(XBASE_LOCAL_ROOT)/xbase-sourcekit-helper echo "DONE" install_debug: clean - mkdir bin - cargo build - ln -sf ../target/debug/xbase ./bin/xbase - ln -sf ../target/debug/xbase-sourcekit-helper ./bin/xbase-sourcekit-helper - ln -sf ../target/debug/libxbase_client.dylib ./lua/xbase_client.so + killall xbase xbase-sourcekit-helper || true + mkdir $(XBASE_LOCAL_ROOT) + cargo build -p xbase -p xbase-sourcekit-helper + ln -sf $(ROOT_DIR)/target/debug/xbase $(XBASE_LOCAL_ROOT)/xbase + ln -sf $(ROOT_DIR)/target/debug/xbase-sourcekit-helper $(XBASE_LOCAL_ROOT)/xbase-sourcekit-helper echo "DONE" free_space: diff --git a/README.md b/README.md index e7d5d8e..96237b0 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,6 @@ An XCode replacement-ish *development environment* that aims to be your reliable Furthermore, [XBase] has built-in support for a variety of XCode project generators, which allow you to avoid launching XCode or manually editing '*.xcodeproj' anytime you add or remove files. We strongly advise you to use one ... at least till [XBase] supports adding/removing files and folders, along with other requirements. ([💆 Generators](#-generators)) -- Watch [XBase] repo to remain up to date on fresh improvements and exciting new features. -- Checkout [Milestones](https://github.com/tami5/xbase/milestones) for planned future features and releases. -- Visit [CONTRIBUTING.md] to have your setup to start contributing and support the project. - Please be aware that [XBase] is still **WIP**, so don't hesitate to report bugs, ask questions or suggest new exciting features. @@ -98,15 +94,13 @@ issue ## 🦾 Installation To install [XBase] on your system you need run `make install`. This will run `cargo build ---release` on all the required binaries in addition to a lua library. The binaries will be -moved to `path/to/repo/bin` and the lua library will be moved to -`path/to/repo/lua/xbase_editor_lib.so`. +--release` and resulting binrary to `~/.local/share/xbase/`. #### With [packer] ```lua use { - 'tami5/xbase', + 'xbase-lab/xbase', run = 'make install', -- make free_space (not recommended, longer build time) requires = { "nvim-lua/plenary.nvim", @@ -124,7 +118,7 @@ use { Plug 'nvim-lua/plenary.nvim' Plug 'nvim-telescope/telescope.nvim' Plug 'neovim/nvim-lspconfig' -Plug 'tami5/xbase', { 'do': 'make install' } +Plug 'xbase-lab/xbase', { 'do': 'make install' } lua require'xbase'.setup() ``` @@ -133,7 +127,7 @@ lua require'xbase'.setup() call dein#add('nvim-lua/plenary.nvim') call dein#add('nvim-telescope/telescope.nvim') call dein#add('neovim/nvim-lspconfig') -call dein#add('tami5/xbase', { 'build': 'make install' }) +call dein#add('xbase-lab/xbase', { 'build': 'make install' }) lua require'xbase'.setup() ``` @@ -226,9 +220,9 @@ to check logs. The following is how you can have a stream of logs in your termin ```bash # Daemon logs -tail -f /tmp/xbase-daemon.log +tail -f /tmp/xbase.log # Build Server logs -tail -f /tmp/xbase-server.log +tail -f /tmp/xbase-build-server.log ``` In case, you need to manually stop servers: @@ -248,8 +242,8 @@ statusline get updated. [xcodegen]: https://github.com/yonaskolb/XcodeGen [sourcekit-lsp]: https://github.com/apple/sourcekit-lsp -[XBase]: https://github.com/tami5/xbase -[xcodebuild]: https://github.com/tami5/xcodebuild +[XBase]: https://github.com/xbase-lab/xbase +[xcodebuild]: https://github.com/xbase-lab/xcodebuild [feline]: https://github.com/feline-nvim/feline.nvim [XVim2]: https://github.com/XVimProject/XVim2 [rust]: https://www.rust-lang.org @@ -262,7 +256,7 @@ statusline get updated. [plenary.nvim]: https://github.com/nvim-lua/plenary.nvim [neovim]: https://github.com/neovim/neovim [tuist]: https://github.com/tuist/tuist -[CONTRIBUTING.md]: ./CONTRIBUTING.md +[dev.md]: ./dev.md [lspconfig]: https://github.com/neovim/nvim-lspconfig [sourcekit-setup]: https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#sourcekit [apple]: https://github.com/apple diff --git a/client/Cargo.toml b/client/Cargo.toml deleted file mode 100644 index a185097..0000000 --- a/client/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "xbase-client" -version = "0.2.0" -edition = "2021" -description = "XBase Editor editor library" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -# Libraries -tap = "1.0.1" -once_cell = "1.12.0" -serde_json = "1.0.81" - -# RPC & Runtime -tokio = { version = "1.19.2", features = ["net", "rt-multi-thread", "sync", "parking_lot"] } -tarpc = { version = "0.29.0" } -xbase-proto = { path = "../proto/", features = ["neovim"] } -mlua = { version = "0.8.0", features = ["luajit", "module", "vendored", "send", "serialize", "async", "macros"] } -async-trait = "0.1.56" -serde = { version = "1.0.137", features = ["derive"] } -linemux = "0.2.3" -process-stream = { version = "0.4.1", features = ["serde"] } -async_once = "0.2.6" -lazy_static = "1.4.0" -os_pipe = "1.0.1" -derive-deref-rs = "0.1.1" -paste = "1.0.7" -xcodeproj = "0.2.12" -wax = "0.5.0" diff --git a/client/src/lib.rs b/client/src/lib.rs deleted file mode 100644 index 7626aef..0000000 --- a/client/src/lib.rs +++ /dev/null @@ -1,48 +0,0 @@ -use xbase_proto::*; - -mod nvim; -mod runtime; - -/// Broadcast server function handler. -/// -/// See [`nvim::Broadcast`] -pub trait BroadcastHandler { - type Result; - fn handle(&self, msg: Message) -> Self::Result; - - fn parse(&self, content: String) -> Result> { - fn parse_single(line: &str) -> Result { - serde_json::from_str(&line) - .map_err(|e| Error::MessageParse(format!("\n{line}\n Error: {e}"))) - } - let mut vec = vec![]; - if content.contains("\n") { - for message in content - .split("\n") - .filter(|s| !s.trim().is_empty()) - .map(parse_single) - { - vec.push(message?); - } - } else { - vec.push(parse_single(&content)?); - } - Ok(vec) - } -} - -#[macro_export] -macro_rules! request { - ($method:ident, $($arg:tt)*) => { - rt().block_on(async move { - let rpc = rpc().await; - let ctx = context::current(); - rpc.$method(ctx, $($arg)*).await? - }) - }; -} - -#[mlua::lua_module] -fn xbase_client(_lua: &mlua::Lua) -> mlua::Result { - Ok(nvim::XBaseUserData) -} diff --git a/client/src/nvim.rs b/client/src/nvim.rs deleted file mode 100644 index 6dfa4ee..0000000 --- a/client/src/nvim.rs +++ /dev/null @@ -1,118 +0,0 @@ -use super::request; -mod broadcast; -mod global; - -pub use broadcast::*; -pub use global::*; - -use crate::{runtime::*, BroadcastHandler}; -use mlua::{chunk, prelude::*}; -use std::{collections::HashSet, path::PathBuf, str::FromStr}; -use tap::Pipe; -use xbase_proto::*; -use xcodeproj::pbxproj::PBXTargetPlatform; - -pub struct XBaseUserData; - -impl BroadcastHandler for Lua { - type Result = LuaResult<()>; - - fn handle(&self, msg: Message) -> Self::Result { - match msg { - Message::Notify { msg, level, .. } => self.notify(msg, level)?, - Message::Log { msg, level, .. } => self.log(msg, level)?, - Message::Execute(task) => match task { - Task::UpdateStatusline(state) => { - state.to_string().pipe(|s| { - self.load(chunk!(vim.g.xbase_watch_build_status = $s)) - .exec() - })?; - } - Task::OpenLogger => { - // TODO: Make auto open logger on error configurable - self.load(chunk!(require("xbase.log").toggle(nil, false))) - .exec()?; - } - Task::ReloadLspServer => { - self.load(chunk!(vim.cmd "LspRestart")).exec()?; - } - }, - } - Ok(()) - } -} - -impl mlua::UserData for XBaseUserData { - fn add_methods<'lua, M>(m: &mut M) - where - M: mlua::UserDataMethods<'lua, Self>, - { - m.add_function("register", |lua, root: String| -> LuaResult { - let root = PathBuf::from(root); - - if roots().lock().unwrap().contains(&root) - || !(root.join("project.yml").exists() - || root.join("Project.swift").exists() - || root.join("Package.swift").exists() - || wax::walk("*.xcodeproj", &root).to_lua_err()?.count() != 0) - { - return Ok(false); - } - - if ensure_daemon() { - lua.info("new instance initialized")?; - } - - Broadcast::init_or_skip(lua, &root)?; - - lua.load(chunk!(require("xbase.log").setup())).exec()?; - - lua.info(format!("[{}] Connected ", root.as_path().name().unwrap()))?; - - Ok(true) - }); - - m.add_function("build", |_lua, req: BuildRequest| { - request!(build, req).to_lua_err() - }); - - m.add_function("run", |_lua, req: RunRequest| { - request!(run, req).to_lua_err() - }); - - m.add_function("drop", |_lua, root: Option| { - let mut curr_roots = roots().lock().unwrap(); - let roots = if let Some(root) = root { - let root = PathBuf::from(root); - curr_roots.remove(&root); - HashSet::from([root]) - } else { - let roots = HashSet::from_iter(curr_roots.iter().map(Clone::clone)); - curr_roots.clear(); - roots - }; - request!(drop, roots).to_lua_err() - }); - - m.add_function("targets", |lua, root: Option| { - let targets = request!(targets, lua.root(root)?).to_lua_err()?; - lua.create_table_from(targets) - }); - - m.add_function("runners", |lua, platform: String| { - let platform = PBXTargetPlatform::from_str(&platform).to_lua_err()?; - let runners = request!(runners, platform).to_lua_err()?; - - let table = lua.create_table()?; - for (i, runner) in runners.into_iter().enumerate() { - table.set(i, lua.create_table_from(runner)?)?; - } - Ok(table) - }); - - m.add_function("watching", |lua, root: Option| { - let watching = request!(watching, lua.root(root)?).to_lua_err()?; - lua.create_table_from(watching.into_iter().enumerate()) - }); - } -} diff --git a/client/src/nvim/broadcast.rs b/client/src/nvim/broadcast.rs deleted file mode 100644 index 7d77874..0000000 --- a/client/src/nvim/broadcast.rs +++ /dev/null @@ -1,97 +0,0 @@ -use super::NvimGlobal; -use crate::runtime::{roots, rpc, rt}; -use crate::BroadcastHandler; -use mlua::{chunk, prelude::*}; -use os_pipe::{PipeReader, PipeWriter}; -use std::os::unix::io::IntoRawFd; -use std::thread::JoinHandle; -use std::{io::Write, path::PathBuf}; -use tokio::{ - io::{AsyncBufReadExt, BufReader}, - net::UnixStream, -}; -use xbase_proto::*; - -pub struct Broadcast; - -impl Broadcast { - /// Register a project and initialize command listener if the project isn't already initialized - pub fn init_or_skip(lua: &Lua, root: &PathBuf) -> LuaResult<()> { - let mut roots = roots().lock().unwrap(); - if !roots.contains(root) { - let (reader, writer) = os_pipe::pipe()?; - - Broadcast::start_reader(lua, reader)?; - Broadcast::start_writer(writer, root.clone()); - - roots.insert(root.clone()); - } - Ok(()) - } - - /// Main handler of daemon messages - fn handle(lua: &Lua, line: LuaString) -> LuaResult<()> { - match lua.parse(line.to_string_lossy().into()) { - Ok(msgs) => { - for msg in msgs { - lua.handle(msg)?; - } - Ok(()) - } - Err(err) => { - lua.notify(err.to_string(), MessageLevel::Error).ok(); - Ok(()) - } - } - } - - /// Setup and load a uv pipe to call [`Self::handle`] with read bytes - pub fn start_reader(lua: &Lua, reader: PipeReader) -> LuaResult<()> { - let reader_fd = reader.into_raw_fd(); - let callback = lua.create_function(Self::handle)?; - - // TODO: should closing be handled? - lua.load(chunk! { - local pipe = vim.loop.new_pipe() - pipe:open($reader_fd) - pipe:read_start(function(err, chunk) - assert(not err, err) - if chunk then - vim.schedule(function() - $callback(chunk) - end) - end - end) - }) - .exec() - } - - pub fn start_writer(mut writer: PipeWriter, root: PathBuf) -> JoinHandle> { - std::thread::spawn(move || { - rt().block_on(async move { - let rpc = rpc().await; - let address = rpc.register(context::current(), root).await??; - let mut stream = UnixStream::connect(address).await?; - drop(rpc); - - let (reader, _) = stream.split(); - let mut breader = BufReader::new(reader); - let mut line = vec![]; - - while let Ok(len) = breader.read_until(b'\n', &mut line).await { - if len == 0 { - break; - } - - writer.write_all(line.as_slice()).ok(); - - line.clear(); - } - - OK(()) - })?; - - OK(()) - }) - } -} diff --git a/client/src/nvim/global.rs b/client/src/nvim/global.rs deleted file mode 100644 index f9a7ec9..0000000 --- a/client/src/nvim/global.rs +++ /dev/null @@ -1,67 +0,0 @@ -use mlua::{chunk, prelude::*}; -use std::path::PathBuf; -use tap::Pipe; -use xbase_proto::MessageLevel; - -pub trait NvimGlobal { - fn vim(&self) -> LuaResult; - fn api(&self, fn_name: &'static str) -> LuaResult; - fn root(&self, root: Option) -> LuaResult; - fn info>(&self, msg: S) -> LuaResult<()>; - fn log>(&self, msg: S, level: MessageLevel) -> Result<(), LuaError>; - fn notify>(&self, msg: S, level: MessageLevel) -> LuaResult<()>; -} - -impl NvimGlobal for Lua { - fn vim(&self) -> LuaResult { - self.globals().get("vim") - } - - fn api(&self, fn_name: &'static str) -> LuaResult { - self.vim()?.get::<_, LuaTable>("api")?.get(fn_name) - } - - fn root(&self, root: Option) -> LuaResult { - match root { - Some(root) => root, - None => { - let vim = self.vim()?.get::<_, LuaTable>("loop")?; - vim.get::<_, LuaFunction>("cwd")?.call(())? - } - } - .pipe(PathBuf::from) - .pipe(Ok) - } - - fn info>(&self, msg: S) -> LuaResult<()> { - self.notify(msg, MessageLevel::Info) - } - - fn log>(&self, msg: S, level: MessageLevel) -> Result<(), LuaError> { - let log: LuaFunction = self.load(chunk!(return require("xbase.log").log)).eval()?; - log.call::<_, ()>((msg.as_ref(), level as u8)) - } - fn notify>(&self, msg: S, level: MessageLevel) -> LuaResult<()> { - let msg = msg.as_ref(); - if msg.trim().is_empty() { - return Ok(()); - } - - if matches!(level, MessageLevel::Success) { - return self - .api("nvim_echo")? - .call(([[msg, "healthSuccess"]], true, Vec::::new())); - } - - let vim = self.vim()?; - let level = level as u8; - let opts = self.create_table_from([("title", "XBase")])?; - let args = (msg, level, opts); - - // NOTE: Plugins like nvim-notify sets notify to metatable - match vim.get::<_, LuaFunction>("notify") { - Ok(f) => f.call(args), - Err(_) => vim.get::<_, LuaTable>("notify")?.call(args), - } - } -} diff --git a/client/src/runtime.rs b/client/src/runtime.rs deleted file mode 100644 index 93fd259..0000000 --- a/client/src/runtime.rs +++ /dev/null @@ -1,60 +0,0 @@ -use once_cell::sync::Lazy; -use std::collections::HashSet; -use std::path::PathBuf; -use std::sync::Mutex; -use std::{net::Shutdown, os::unix::net::UnixStream, process::Command}; -use tokio::{runtime::Runtime, sync::OnceCell}; -use xbase_proto::*; - -static ROOTS: Lazy>> = Lazy::new(Default::default); -static CLIENT: OnceCell = OnceCell::const_new(); -static RUNTIME: Lazy = Lazy::new(|| Runtime::new().expect("Tokio runtime")); -static DAEMON_SOCKET_ADDRESS: &str = "/tmp/xbase.socket"; -static DAEMON_BINARY_PATH: Lazy = Lazy::new(|| { - let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent() - .unwrap() - .to_path_buf(); - if cfg!(debug_assertions) { - root.extend(&["target", "debug", "xbase"]); - } else { - root.extend(&["bin", "xbase"]); - } - root -}); - -/// Get Tokio Runtime -pub fn rt() -> &'static Runtime { - &*RUNTIME -} - -/// Get Registered roots -pub fn roots() -> &'static Mutex> { - &*ROOTS -} - -/// Get RPC to make request to xbase daemon -pub async fn rpc() -> &'static XBaseClient { - CLIENT - .get_or_init(|| async { - let codec_builder = LengthDelimitedCodec::builder(); - let conn = tokio::net::UnixStream::connect(DAEMON_SOCKET_ADDRESS) - .await - .unwrap(); - let transport = transport::new(codec_builder.new_framed(conn), Json::default()); - XBaseClient::new(Default::default(), transport).spawn() - }) - .await -} - -#[inline] -pub fn ensure_daemon() -> bool { - if let Ok(stream) = UnixStream::connect(DAEMON_SOCKET_ADDRESS) { - stream.shutdown(Shutdown::Both).ok(); - false - } else { - Command::new(&*DAEMON_BINARY_PATH).spawn().unwrap(); - std::thread::sleep(std::time::Duration::new(1, 0)); - true - } -} diff --git a/sourcekit/Cargo.toml b/crates/sourcekit-helper/Cargo.toml similarity index 70% rename from sourcekit/Cargo.toml rename to crates/sourcekit-helper/Cargo.toml index ba34917..33e4067 100644 --- a/sourcekit/Cargo.toml +++ b/crates/sourcekit-helper/Cargo.toml @@ -9,10 +9,14 @@ license = "MIT OR Apache-2.0" xclog = "0.3.*" serde = {version = "1.0.137", features = ["derive"]} bsp-server = "0.1.*" -# TODO(sourcekit-helper): remove dirs crate dirs = "4.0" once_cell = "1.12.0" serde_json = "1.0.81" anyhow = "1.0.58" -log = { path = "../log/" } tap = "1.0.1" + +# Logging +tracing = { version = "0.1.32" } +tracing-attributes = { version = "0.1.21" } +tracing-appender = { version = "0.2.1" } +tracing-subscriber = { version = "0.3.9", features = ["env-filter"] } diff --git a/sourcekit/src/extensions.rs b/crates/sourcekit-helper/src/extensions.rs similarity index 100% rename from sourcekit/src/extensions.rs rename to crates/sourcekit-helper/src/extensions.rs diff --git a/sourcekit/src/helpers.rs b/crates/sourcekit-helper/src/helpers.rs similarity index 100% rename from sourcekit/src/helpers.rs rename to crates/sourcekit-helper/src/helpers.rs diff --git a/sourcekit/src/main.rs b/crates/sourcekit-helper/src/main.rs similarity index 83% rename from sourcekit/src/main.rs rename to crates/sourcekit-helper/src/main.rs index 5190e9b..301dda2 100644 --- a/sourcekit/src/main.rs +++ b/crates/sourcekit-helper/src/main.rs @@ -1,25 +1,26 @@ +use anyhow::{anyhow, Context, Result}; use bsp_server::types::{ BuildTargetSources, BuildTargetSourcesResult, InitializeBuild, Url, WorkspaceBuildTargetsResult, }; use bsp_server::{Connection, Message, Request, RequestId, Response}; -use log::Level; use once_cell::sync::OnceCell; use serde_json::{json, Value}; use std::path::Path; use std::sync::Mutex; use std::time::SystemTime; use std::{collections::HashMap, path::PathBuf}; +use tracing::Level; use xclog::{XCCompilationDatabase, XCCompileArgs, XCCompileCommand}; + mod extensions; mod helpers; -use anyhow::{anyhow, Context, Result}; -use log as tracing; // hack for tracing macros +mod tracing_setup; use extensions::*; use helpers::*; static SERVER_NAME: &str = "Xbase"; -static SERVER_VERSION: &str = "0.2"; +static SERVER_VERSION: &str = "0.3"; static STATE: OnceCell> = OnceCell::new(); type Conn = Connection; @@ -62,7 +63,7 @@ fn initialize(params: &InitializeBuild) -> Result { "indexStorePath": index_store_path, }), ); - log::trace!("{response:#?}"); + tracing::trace!("{response:#?}"); STATE .set(Mutex::new(State { @@ -79,6 +80,7 @@ fn initialize(params: &InitializeBuild) -> Result { fn get_compile_args<'a>(path: impl AsRef) -> Result { let mut state = state().lock().unwrap(); let path = path.as_ref(); + let file_name = path.file_name().and_then(|v| v.to_str()).unwrap(); if state.last_modified != std::fs::metadata(&state.compile_filepath)?.modified()? { state.compile_db = XCCompilationDatabase::try_from_filepath(&state.compile_filepath)?; @@ -86,10 +88,10 @@ fn get_compile_args<'a>(path: impl AsRef) -> Result { } if state.file_args.contains_key(path) { - log::debug!("Using Cached file args ..."); + tracing::debug!("[{file_name}] Using Cached file args"); state.file_args.get(path) } else { - log::debug!("Querying compile_db ..."); + tracing::debug!("[{file_name}] Querying compile_db"); let file_args = state .compile_db .iter() @@ -107,25 +109,25 @@ fn get_compile_args<'a>(path: impl AsRef) -> Result { /// Register or unregister a file options for changes. On change, must send /// SourceKitOptionsChanged with list of compiler options to compile the /// file. -#[log::instrument(name = "RegisterForChanges", skip_all)] +// #[tracing::instrument(name = "RegisterForChanges", skip_all)] fn register_for_changes(conn: &Conn, id: Id, params: OptionsChangedRequest) -> Result<()> { // Empty response, ensure response before notification conn.send(Response::ok(id, Value::Null))?; if !matches!(params.action, RegisterAction::Register) { - log::error!("Unhandled params: {:?}", params); + tracing::error!("Unhandled params: {:?}", params); return Ok(()); } let filepath = params.uri.path(); - log::info!("{filepath}"); + // tracing::info!("{filepath}"); let root_path = state().lock().unwrap().root_path.clone(); let uri = Url::from_directory_path(root_path).ok(); let args = get_compile_args(filepath)?.to_vec(); let notification: Message = OptionsChangedNotification::new(params.uri, args, uri).try_into()?; - log::info!(""); + tracing::info!(""); conn.send(notification)?; @@ -133,10 +135,10 @@ fn register_for_changes(conn: &Conn, id: Id, params: OptionsChangedRequest) -> R } /// List of compiler options necessary to compile a file. -#[log::instrument(name = "SourceKitOptions", skip_all)] +// #[tracing::instrument(name = "SourceKitOptions", skip_all)] fn sourcekit_options(conn: &Conn, id: Id, params: OptionsRequest) -> Result<()> { let filepath = params.uri.path(); - log::info!("{filepath}"); + tracing::info!("{filepath}"); let root_path = state().lock().unwrap().root_path.clone(); let uri = Url::from_directory_path(root_path).ok(); @@ -149,9 +151,9 @@ fn sourcekit_options(conn: &Conn, id: Id, params: OptionsRequest) -> Result<()> } /// Process Workspace BuildTarget request -#[log::instrument(name = "WorkspaceBuildTargets", skip_all)] +// #[tracing::instrument(name = "WorkspaceBuildTargets", skip_all)] fn workspace_build_targets(conn: &Conn, id: Id) -> Result<()> { - log::debug!("Processing"); + tracing::debug!("Processing"); let response = WorkspaceBuildTargetsResult::new(vec![]); conn.send((id, response))?; @@ -160,9 +162,9 @@ fn workspace_build_targets(conn: &Conn, id: Id) -> Result<()> { } /// Process BuildTarget output paths -#[log::instrument(name = "BuildTargetsOutputPaths", skip_all)] +// #[tracing::instrument(name = "BuildTargetsOutputPaths", skip_all)] fn output_paths(conn: &Conn, id: Id, params: BuildTargetOutputPathsRequest) -> Result<()> { - log::debug!("Processing {params:#?}"); + tracing::debug!("Processing {params:#?}"); let response = BuildTargetOutputPathsResponse::new(vec![]).as_response(id); conn.send(response)?; @@ -171,9 +173,9 @@ fn output_paths(conn: &Conn, id: Id, params: BuildTargetOutputPathsRequest) -> R } /// Process BuildTarget Sources Request -#[log::instrument(name = "BuildTargetsSources", skip_all)] +// #[tracing::instrument(name = "BuildTargetsSources", skip_all)] fn build_target_sources(conn: &Conn, id: Id, params: BuildTargetSources) -> Result<()> { - log::debug!("Processing {params:#?}"); + tracing::debug!("Processing {params:#?}"); let response = BuildTargetSourcesResult::new(vec![]); conn.send((id, response))?; Ok(()) @@ -181,8 +183,8 @@ fn build_target_sources(conn: &Conn, id: Id, params: BuildTargetSources) -> Resu /// Return Default response for unhandled requests. fn default_response(conn: &Conn, id: &Id, method: &str, params: Value) -> Result<()> { - log::warn!("Unable to handle:\n\n{:#?}\n", method); - log::debug!("Got Params:\n\n{:#?}\n", params); + tracing::warn!("Unable to handle:\n\n{:#?}\n", method); + tracing::debug!("Got Params:\n\n{:#?}\n", params); conn.send(Response::err( id.clone(), 123, @@ -235,30 +237,31 @@ fn handle_message(conn: &Conn, msg: Message) -> Result<()> { } } Message::Response(_) => { - log::warn!("skipping \n\n{:?}\n", msg); + tracing::warn!("skipping \n\n{:?}\n", msg); Ok(()) } Message::Notification(_) => { - log::warn!("skipping \n\n{:?}\n", msg); + tracing::warn!("skipping \n\n{:?}\n", msg); Ok(()) } } } fn main() -> Result<()> { - log::setup("/tmp/", "xbase-server.log", Level::DEBUG, false)?; + tracing_setup::setup("/tmp/", "xbase-build-server.log", Level::DEBUG, false)?; + let (conn, io_threads) = Connection::stdio(); - log::info!("Started"); + tracing::info!("Started"); conn.initialize(|params| initialize(¶ms).expect("Initialize"))?; - log::info!("Initialized"); + tracing::info!("Initialized"); for msg in &conn.receiver { if let Message::Request(ref req) = msg { match handle_shutdown(&conn, req) { - Err(err) => log::error!("Failure to shutdown server {:#?}", err), + Err(err) => tracing::error!("Failure to shutdown server {:#?}", err), Ok(should_break) => { if should_break { - log::info!("Shutdown"); + tracing::info!("Shutdown"); break; } } @@ -266,11 +269,11 @@ fn main() -> Result<()> { } if let Err(err) = handle_message(&conn, msg) { - log::error!("{:?}", err); + tracing::error!("{:?}", err); } } io_threads.join()?; - log::info!("Ended"); + // tracing::info!("Ended"); Ok(()) } diff --git a/log/src/lib.rs b/crates/sourcekit-helper/src/tracing_setup.rs similarity index 85% rename from log/src/lib.rs rename to crates/sourcekit-helper/src/tracing_setup.rs index c64674b..51a7c08 100644 --- a/log/src/lib.rs +++ b/crates/sourcekit-helper/src/tracing_setup.rs @@ -1,15 +1,12 @@ use std::io; -pub use tracing::dispatcher::SetGlobalDefaultError; +use tracing::dispatcher::SetGlobalDefaultError; use tracing::subscriber::set_global_default; +use tracing::Level; use tracing_appender::rolling; use tracing_subscriber::fmt::Layer; use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt; use tracing_subscriber::{registry, EnvFilter}; -pub use tracing::*; -pub use tracing_attributes; -pub use tracing_subscriber::*; - /// Setup tracing pub fn setup( root: &str, @@ -18,8 +15,7 @@ pub fn setup( with_stdout: bool, ) -> Result<(), SetGlobalDefaultError> { let default_filter = EnvFilter::try_from_default_env() - .unwrap_or_else(|_| EnvFilter::from_default_env().add_directive(default_level.into())) - .add_directive("tarpc=error".parse().unwrap()); + .unwrap_or_else(|_| EnvFilter::from_default_env().add_directive(default_level.into())); let fmt_file = Layer::new() .with_writer(rolling::never(root, filename)) diff --git a/daemon/Cargo.toml b/daemon/Cargo.toml deleted file mode 100644 index b0b3738..0000000 --- a/daemon/Cargo.toml +++ /dev/null @@ -1,57 +0,0 @@ -[package] -name = "xbase" -version = "0.2.0" -edition = "2021" - -[dependencies] - -# Internal -log = { path = "./../log/" } -xbase-proto = { path = "./../proto/", features = ["server"] } - -# Error Handling -anyhow = { version = "^1.0.58" } -thiserror = { version = "1.0.31" } - -# Serialization Feature -serde = { version = "1.0", features = ["derive"] } -serde_json = { version = "1.0.79" } -serde_yaml = { version = "0.8.23" } -strum = { version = "0.24.0", features = ["derive"] } - -# Async Runtime Feature -tokio = { version = "1.19.2", features = ["full"] } -futures = { version = "0.3.21" } -async-trait = { version = "0.1.52" } -async-stream = { version = "0.3.3" } -parity-tokio-ipc = { version = "0.9.0" } -process-stream = { version = "0.4.*", features = ["serde"] } -xcodeproj = { version = "0.2.*" } - -# Filesystem watcher -notify = { version = "5.0.0-pre.13" } -dirs = { version = "4.0" } -wax = { version = "0.5.0" } - -# Static -lazy_static = { version = "1.4.0" } - -# Other -tap = "1.0.1" -xclog = { version = "0.3.*", features = ["with_tracing"] } -simctl = { git = "https://github.com/xbase-lab/simctl" } -derive-deref-rs = { version = "0.1.1"} -shell-words = { version = "1.1.0" } - -# Unix Api -libc = { version = "0.2.126"} -libproc = { version = "0.12.0" } -paste = "1.0.7" -which = "4.2.5" -tarpc = { version = "0.29.0", features = ["serde-transport", "tokio1", "serde1"] } -linemux = "0.2.3" -os_pipe = "1.0.1" -once_cell = "1.12.0" - -[dev-dependencies] -tracing-test = "0.2.2" diff --git a/daemon/src/constants.rs b/daemon/src/constants.rs deleted file mode 100644 index 4d65c59..0000000 --- a/daemon/src/constants.rs +++ /dev/null @@ -1,29 +0,0 @@ -use once_cell::sync::Lazy; -use std::path::PathBuf; - -/// Where the daemon socket path will be located -pub static DAEMON_SOCKET_PATH: &str = "/tmp/xbase.socket"; - -/// Where the daemon pid will be located -pub static DAEMON_PID_PATH: &str = "/tmp/xbase.pid"; - -/// Where the server binary will be located. -pub static BUILD_SERVER_CONFIG: Lazy> = Lazy::new(|| { - let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent() - .unwrap() - .to_path_buf(); - if cfg!(debug_assertions) { - root.extend(&["target", "debug", "xbase-sourcekit-helper"]); - } else { - root.extend(&["bin", "xbase-sourcekit-helper"]); - } - let json = serde_json::json!({ - "name": "Xbase", - "argv": [root], - "version": "0.2", - "bspVersion": "0.2", - "languages": ["swift", "objective-c", "objective-cpp", "c", "cpp"] - }); - json.to_string().into_bytes() -}); diff --git a/daemon/src/drop.rs b/daemon/src/drop.rs deleted file mode 100644 index d4db30a..0000000 --- a/daemon/src/drop.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::store::{broadcasters, projects, watchers}; -use crate::Result; -use std::collections::HashSet; -use std::path::PathBuf; -use std::sync::Arc; -use xbase_proto::PathExt; - -/// handle drop request -pub async fn handle(roots: HashSet) -> Result<()> { - let mut watchers = watchers().await; - let mut broadcasters = broadcasters().await; - let mut projects = projects().await; - - for root in roots.into_iter() { - let mut project = if let Some(project) = projects.get(&root).map(Arc::clone) { - project.lock_owned().await - } else { - continue; - }; - - // Remove client pid from project. - project.dec_clients(); - - // Remove project only when no more client using that data. - if project.clients() == &0 { - let key = root.as_path().abbrv()?.display(); - log::info!("[{key}] project removed"); - projects.remove(&root); - - if let Some(watcher) = watchers.get(&root) { - watcher.lock().await.handler.abort(); - watchers.remove(&root); - log::info!("[{key}] watcher removed"); - }; - - broadcasters.remove(&root).map(|l| { - l.abort(); - }); - log::info!("dropped {}", root.as_path().name().unwrap()); - } - } - - Ok(()) -} diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs deleted file mode 100644 index 8dda5ae..0000000 --- a/daemon/src/lib.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub mod broadcast; -pub mod build; -pub mod constants; -pub mod device; -pub mod drop; -pub mod project; -pub mod register; -pub mod run; -pub mod store; -pub mod util; -pub mod watch; - -use process_stream::Stream; -use std::pin::Pin; - -pub use xbase_proto::{Error, IntoResult, Result}; -pub type StringStream = Pin + Send>>; diff --git a/daemon/src/main.rs b/daemon/src/main.rs deleted file mode 100644 index c3e3c6f..0000000 --- a/daemon/src/main.rs +++ /dev/null @@ -1,99 +0,0 @@ -use log::Level; -use std::collections::{HashMap, HashSet}; -use std::path::PathBuf; -use tap::Pipe; -use tokio::fs::{metadata, read_to_string, remove_file, write}; -use tokio::net::UnixListener; -use xbase::constants::*; -use xbase::store::{devices, TryGetDaemonObject}; -use xbase::util::pid; -use xbase_proto::*; -use xcodeproj::pbxproj::PBXTargetPlatform; - -#[derive(Clone)] -struct Server; - -#[tarpc::server] -impl xbase_proto::XBase for Server { - async fn register(self, _: Context, root: PathBuf) -> Result { - xbase::register::handle(root).await - } - async fn build(self, _: Context, req: BuildRequest) -> Result<()> { - tokio::spawn(async { xbase::build::handle(req).await }); - Ok(()) - } - async fn run(self, _: Context, req: RunRequest) -> Result<()> { - tokio::spawn(async { xbase::run::handle(req).await }); - Ok(()) - } - - async fn watching(self, _: Context, root: PathBuf) -> Result> { - root.try_get_watcher() - .await - .map(|w| w.listeners.iter().map(|(k, _)| k.clone()).collect()) - } - - async fn drop(self, _: Context, root: HashSet) -> Result<()> { - tokio::spawn(async { xbase::drop::handle(root).await }); - Ok(()) - } - - async fn targets(self, _: Context, root: PathBuf) -> Result> { - root.try_get_project().await.map(|p| p.targets().clone()) - } - - async fn runners( - self, - _: Context, - platform: PBXTargetPlatform, - ) -> Result>> { - devices() - .iter() - .filter(|(_, d)| d.platform == platform) - .map(|(id, d)| { - HashMap::from([("id".into(), id.clone()), ("name".into(), d.name.clone())]) - }) - .collect::>>() - .pipe(Ok) - } -} - -#[tokio::main] -async fn main() -> std::result::Result<(), Box> { - ensure_single_instance().await?; - - let listener = UnixListener::bind(DAEMON_SOCKET_PATH).unwrap(); - let codec_builder = LengthDelimitedCodec::builder(); - log::setup("/tmp", "xbase-daemon.log", Level::DEBUG, true)?; - log::info!("Started"); - - loop { - if let Ok((s, _)) = listener.accept().await { - tokio::spawn(async move { - let framed = codec_builder.new_framed(s); - let transport = transport::new(framed, Json::default()); - let channel = BaseChannel::with_defaults(transport); - - channel.execute(Server.serve()).await; - }); - // TODO: break loop if no more projects - } else { - log::error!("Fail to accept a connection") - }; - } -} - -async fn ensure_single_instance() -> Result<()> { - if metadata(DAEMON_SOCKET_PATH).await.ok().is_some() { - remove_file(DAEMON_SOCKET_PATH).await.ok(); - if metadata(DAEMON_PID_PATH).await.ok().is_some() { - read_to_string(DAEMON_PID_PATH) - .await? - .pipe_ref(pid::kill) - .await?; - } - remove_file(DAEMON_PID_PATH).await.ok(); - } - write(DAEMON_PID_PATH, std::process::id().to_string()).await?; - OK(()) -} diff --git a/daemon/src/register.rs b/daemon/src/register.rs deleted file mode 100644 index f052dc0..0000000 --- a/daemon/src/register.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::broadcast::Broadcast; -use crate::project::project; -use crate::store::*; -use crate::watch::WatchService; -use crate::Result; -use std::path::PathBuf; -use std::sync::Arc; -use tokio::sync::Mutex; -use xbase_proto::PathExt; -use xbase_proto::OK; - -/// Handle RegisterRequest -pub async fn handle(root: PathBuf) -> Result { - log::info!("Register {}", root.as_path().name().unwrap()); - - let (broadcast, logger_path) = if let Ok(broadcast) = root.try_get_broadcast().await { - (broadcast.clone(), broadcast.address().clone()) - } else { - let broadcast = Broadcast::new(&root).await.map(Arc::new)?; - let address = broadcast.address().clone(); - broadcasters().await.insert(root.clone(), broadcast.clone()); - (broadcast, address) - }; - - tokio::spawn(async move { - let mut projects = projects().await; - - if let Some(project) = projects.get(&root).map(Arc::clone) { - let mut project = project.lock_owned().await; - project.inc_clients(); - // NOTE: this doesn't make sense! - project.ensure_server_support(None, &broadcast).await?; - return Ok(()); - } - let project = project(&root, &broadcast).await?; - let name = project.name().to_string(); - let root = project.root().clone(); - let ignore = project.watchignore().clone(); - - let project = Arc::new(Mutex::new(project)); - let handler = WatchService::new( - &root, - ignore, - Arc::downgrade(&broadcast), - Arc::downgrade(&project), - ) - .await?; - - log::info!("[{}] Project added", name); - - projects.insert(root.clone(), project.clone()); - watchers() - .await - .insert(root.clone(), Arc::new(Mutex::new(handler))); - project - .lock() - .await - .ensure_server_support(None, &broadcast) - .await?; - OK(()) - }); - - Ok(logger_path) -} diff --git a/daemon/src/run.rs b/daemon/src/run.rs deleted file mode 100644 index 3e166a9..0000000 --- a/daemon/src/run.rs +++ /dev/null @@ -1,95 +0,0 @@ -mod bin; -mod handler; -mod service; -mod simulator; - -use crate::broadcast::Broadcast; -use crate::device::Device; -use crate::project::ProjectImplementer; -use crate::store::TryGetDaemonObject; -use crate::Result; -use process_stream::Process; -use std::sync::Arc; -use tokio::sync::OwnedMutexGuard; -use xbase_proto::{BuildSettings, RunRequest, StatuslineState}; - -pub use service::RunService; -pub use {bin::*, simulator::*}; - -#[async_trait::async_trait] -pub trait Runner { - /// Run Project - async fn run<'a>(&self, broadcast: &Broadcast) -> Result; -} - -/// Handle RunRequest -/// TODO: Watch runners -pub async fn handle(req: RunRequest) -> Result<()> { - let root = req.root.clone(); - - log::trace!("{:#?}", req); - - let ref key = req.to_string(); - let broadcast = root.try_get_broadcast().await?; - let mut project = root.try_get_project().await?; - - let watcher = req.root.try_get_mutex_watcher().await?; - let weak_watcher = Arc::downgrade(&watcher); - - if req.ops.is_once() { - // TODO(run): might want to keep track of ran services - RunService::new(&mut project, req, &broadcast, weak_watcher).await?; - - return Ok(Default::default()); - } - - let mut watcher = watcher.lock().await; - - if req.ops.is_watch() { - broadcast.update_statusline(StatuslineState::Watching); - if watcher.contains_key(key) { - broadcast.warn(format!("Already watching with {key}!!")); - } else { - let run_service = RunService::new(&mut project, req, &broadcast, weak_watcher).await?; - watcher.add(run_service)?; - } - } else { - let listener = watcher.remove(&req.to_string())?; - listener.discard().await?; - broadcast.info(format!("[{}] Watcher Stopped", &req.settings.target)); - broadcast.update_statusline(StatuslineState::Clear); - } - - Ok(()) -} - -async fn get_runner<'a>( - project: &mut OwnedMutexGuard, - settings: &BuildSettings, - device: Option<&Device>, - _is_once: bool, - broadcast: &Arc, -) -> Result { - let target = &settings.target; - let device_name = device.map(|d| d.to_string()).unwrap_or("macOs".into()); - - broadcast.info(format!("[{target}({device_name})] Running ⚙")); - - let (runner, args, mut recv) = project.get_runner(&settings, device, broadcast)?; - - broadcast.update_statusline(StatuslineState::Processing); - - if !recv.recv().await.unwrap_or_default() { - let msg = format!("[{target}] Failed to build for running "); - broadcast.error(&msg); - broadcast.log_error(format!("xcodebuild {}", args.join(" "))); - broadcast.open_logger(); - return Err(crate::Error::Run(msg)); - } - - let process = runner.run(broadcast).await?; - - broadcast.update_statusline(StatuslineState::Running); - - Ok(process) -} diff --git a/daemon/src/util/mod.rs b/daemon/src/util/mod.rs deleted file mode 100644 index c0120f1..0000000 --- a/daemon/src/util/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! General utilities - -pub mod fmt; -pub mod fs; -pub mod pid; diff --git a/dev.md b/dev.md new file mode 100644 index 0000000..e23a329 --- /dev/null +++ b/dev.md @@ -0,0 +1,70 @@ +# XBase Development + +This a quick guide to understand and edit xbase source code. + +- [Project Organization](#project-organization) +- [Development Setup](#development-setup) + +## Project Organization + +Here's an overview of the project architecture to help you contribute. + +### [xbase] source code + +- [Server Startup and OS Signal Handler Logic `main.rs`](./src/main.rs) +- [State Initialization and Access Logic `state.rs`](./src/state.rs) +- [Server/Client Logic (server/*)](./src/server/mod.rs) + - [Request Definition and Handler `request.rs`](./src/server/request.rs) + - [Response Definition `response.rs`](./src/server/response.rs) + - [Client Handler `stream.rs`](./src/server/stream.rs) + - [Register Handler `register.rs`](./src/server/register.rs) + - [Drop Handler `drop.rs`](./src/server/drop.rs) + - [Run Handler `run.rs`](./src/server/run.rs) + - [Build Handler `build.rs`](./src/server/build.rs) + - [Runners Handler `runners.rs`](./src/server/runners.rs) + - [ProjectInfo Handler `project_info.rs`](./src/server/project_info.rs) +- [Project Running Logic `runner/*`](./src/runner/) +- [Project Watching Logic `watcher/*`](./src/wathcer/) +- [Project Handling Logic `project/*`](./src/project/mod.rs) + - [XCodeGen Project Support `xcodegen.rs`](./src/project/xcodegen.rs) + - [Tuist Project Support `tuist.rs`](./src/project/tuist.rs) + - [Barebone Project Support `barebone.rs`](./src/project/barebone.rs) + - [Swift Package Support `swift.rs`](./src/project/swift.rs) +- [General Purpose Types `types.rs`](./src/types.rs) +- [Serializable/Deserializable Errors `error.rs`](./src/error.rs) + +### [sourcekit-helper] crate + +Build server implementing [BSP] protocol required because [sourcekit-lsp] can't define compile arguments required to _jump to definition_ and _symbol definition_. + + +[sourcekit-helper]: ./crates/sourcekit-helper/ +[xbase]: ./src/ +[lua]: ./lua/ +[xbase]: https://github.com/xbase-lab/xbase +[BSP]: https://build-server-protocol.github.io +[sourcekit-lsp]: https://github.com/apple/sourcekit-lsp + +## Development Setup + +#### Clone the repo + +```sh +git clone https://github.com/xbase-lab/xbase +``` + +#### Install in debug mode + +Do all the setup required to watch and develop + +```sh +make install_debug +``` + +#### Start watchers + +Watch all the products and trigger recompile when the source code changes. + +```sh +make watch +``` diff --git a/log/Cargo.toml b/log/Cargo.toml deleted file mode 100644 index dbb240f..0000000 --- a/log/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "log" -version = "0.1.0" -edition = "2021" -description = "Re-export of tracing setup with helper method" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -thiserror = "1.0.31" -tracing = "0.1.32" -tracing-attributes = "0.1.21" -tracing-appender = "0.2.1" -tracing-subscriber = { version = "0.3.9", features = ["env-filter"] } diff --git a/lua/xbase/broadcast.lua b/lua/xbase/broadcast.lua new file mode 100644 index 0000000..95aae00 --- /dev/null +++ b/lua/xbase/broadcast.lua @@ -0,0 +1,55 @@ +local logger = require "xbase.logger" +local notify = require "xbase.notify" +local socket = require "xbase.socket" +local M = {} + +local Task = { + OpenLogger = "OpenLogger", + ReloadLspServer = "ReloadLspServer", + UpdateStatusline = "UpdateStatusline", +} + +local MessageType = { + Log = "Log", + Notify = "Notify", + Execute = "Execute", +} + +local handlers = { + [MessageType.Log] = function(item) + if #item.msg > 0 then + logger.log(item.msg, item.level) + end + end, + [MessageType.Notify] = function(item) + notify(item.msg, item.level) + end, + [MessageType.Execute] = function(item) + local task = item.task + if Task.UpdateStatusline == task then + vim.g.xbase_watch_build_status = item.value + elseif Task.OpenLogger == task then + logger.toggle(nil, false) + elseif Task.ReloadLspServer == task then + vim.cmd "LspRestart" + end + end, +} + +function M.start(address) + local socket = socket:connect(address) + + socket:read_start(function(chunk) + local chunk = vim.trim(chunk) + for _, chunk in ipairs(vim.split(chunk, "\n")) do + local item = vim.json.decode(chunk) + vim.schedule(function() + handlers[item.type](item) + end) + end + end) + + return socket +end + +return M diff --git a/lua/xbase/init.lua b/lua/xbase/init.lua index cb77a8f..d454570 100644 --- a/lua/xbase/init.lua +++ b/lua/xbase/init.lua @@ -1,69 +1,46 @@ -local M = {} - -M.lib = require "xbase_client" -M.targets = M.lib.targets -M.runners = M.lib.runners -M.watching = M.lib.watching -M.drop = M.lib.drop -M.build = M.lib.build -M.run = M.lib.run +local initialized = false +local server = require "xbase.server" local util = require "xbase.util" -M.init = false +local config = require "xbase.config" +local autocmd = vim.api.nvim_create_autocmd -M.try_attach = function(root, config) - if not M.lib.register(root) then - return - end - if not M.init then - M.init = true - vim.api.nvim_create_autocmd({ "VimLeavePre" }, { - pattern = "*", - callback = function() - M.drop() - end, - }) - - vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, { - pattern = { "*.m", "*.swift", "*.c", "*.yml" }, - callback = function() - if config.mappings.enable then - util.bind(config.mappings) - end - end, - }) - - vim.api.nvim_create_autocmd({ "BufEnter" }, { - pattern = "xclog", - callback = function() - if config.mappings.enable then - util.bind(config.mappings) - end - end, - }) - end - - if config.mappings.enable then - util.bind(config.mappings) +local function try_attach_mappings() + if config.values.mappings.enable then + util.bind(config.values.mappings) end end -M.setup = function(opts) - vim.schedule(function() - opts = opts or {} - local root = vim.loop.cwd() - local config = require "xbase.config" - config.set(opts) - local config = config.values - - M.try_attach(root, config) - - vim.api.nvim_create_autocmd({ "DirChanged" }, { - pattern = "*", - callback = function() - M.try_attach(vim.loop.cwd(), config) - end, - }) - end) +local function try_attach(root) + local file_patterns = { "*.m", "*.swift", "*.c", "*.yml" } + if server.should_register(root) then + server.register(root) + if not initialized then + initialized = true + autocmd({ "VimLeavePre" }, { + pattern = "*", + callback = function() + server.drop(server.roots) + end, + }) + autocmd({ "BufEnter", "BufWinEnter" }, { pattern = file_patterns, callback = try_attach_mappings }) + autocmd({ "BufEnter" }, { pattern = "xclog", callback = try_attach_mappings }) + end + try_attach_mappings() + end end -return M +return { + setup = function(opts) + vim.schedule(function() + opts = opts or {} + config.set(opts) + try_attach(vim.loop.cwd()) + autocmd({ "DirChanged" }, { + pattern = "*", + callback = function() + try_attach(vim.loop.cwd()) + end, + }) + end) + end, +} diff --git a/lua/xbase/log.lua b/lua/xbase/logger.lua similarity index 78% rename from lua/xbase/log.lua rename to lua/xbase/logger.lua index 40d259c..00ae59c 100644 --- a/lua/xbase/log.lua +++ b/lua/xbase/logger.lua @@ -62,22 +62,23 @@ function M.toggle(vsplit, force) end end -function M.log(msg, level) - local config = require("xbase.config").values +---Checks whether the level is sufficient for logging. +---@param level number log level +---@returns (bool) true if would log, false if not +function M.should_log(level) + -- return true + return level >= require("xbase.config").values.log_level +end - if #msg == 0 or config.log_level > level then - return +function M.log(msg, level) + if M.should_log(level) then + local line_count = vim.api.nvim_buf_line_count(M.bufnr) + local line_first = vim.api.nvim_buf_get_lines(M.bufnr, 0, 1, false)[1] + local row = (line_count == 1 and #line_first == 0) and 0 or -1 + vim.api.nvim_buf_set_lines(M.bufnr, row, -1, false, vim.split(msg, "\n")) + --- FIXME: Sometimes getting Error log.lua:89: Cursor position outside buffer Ignoring .. + pcall(M.update_cursor_position, line_count) end - - local line_count = vim.api.nvim_buf_line_count(M.bufnr) - local line_first = vim.api.nvim_buf_get_lines(M.bufnr, 0, 1, false)[1] - local row = (line_count == 1 and #line_first == 0) and 0 or -1 - - vim.api.nvim_buf_set_lines(M.bufnr, row, -1, false, { msg }) - - --- FIXME: Sometimes getting Error log.lua:89: Cursor position outside buffer - -- Ignoring .. - pcall(M.update_cursor_position, line_count) end function M.update_cursor_position(line_count) diff --git a/lua/xbase/notify.lua b/lua/xbase/notify.lua new file mode 100644 index 0000000..052102a --- /dev/null +++ b/lua/xbase/notify.lua @@ -0,0 +1,28 @@ +local M = {} +local notify = function(msg, level) + if #vim.trim(msg) == 0 then + return + end + if level == 5 then + vim.api.nvim_echo({ { msg, "healthSuccess" } }, true, {}) + return + end + + vim.notify(msg, level, { + title = "XBase", + }) +end + +M = setmetatable(M, { + __index = function(_, level) + level = vim.log.levels[string.upper(level)] + return function(msg) + return notify(msg, level) + end + end, + __call = function(_, msg, level) + return notify(msg, level) + end, +}) + +return M diff --git a/lua/xbase/pickers.lua b/lua/xbase/pickers.lua index c0f433e..a212ae9 100644 --- a/lua/xbase/pickers.lua +++ b/lua/xbase/pickers.lua @@ -6,21 +6,25 @@ local finder = require("telescope.finders").new_table local picker = require("telescope.pickers").new local sorter = require("telescope.config").values.generic_sorter local maker = require("telescope.pickers.entry_display").create -local xbase = require "xbase" +local server = require "xbase.server" local themes = require "telescope.themes" local util = require "xbase.util" +local state = require "xbase.state" + +local C = { + Run = "Run", + Build = "Build", + Watch = "Watch", +} local mappings = function(_, _) - action_set.select:replace(function(bufnr, direction) + action_set.select:replace(function(bufnr, _) a.close(bufnr) - local entry = s.get_selected_entry() - entry.direction = direction + local req = s.get_selected_entry() - if entry.command == "Build" then - xbase.build(entry) - elseif entry.command == "Run" then - xbase.run(entry) - end + req.method = string.lower(req.command) + req.display = nil + server.request(req) end) return true @@ -32,20 +36,20 @@ local insert_entry = function(acc, picker, command, target, configuration, watch settings = { target = target, configuration = configuration }, } - if command == "Run" then + if command == C.Run then item.device = device end - if picker == "Watch" then + if picker == C.Watch then if util.is_watching(item.settings, command, item.device, watchlist) then - item.ops = "Stop" + item.operation = "Stop" item.kind = command else - item.ops = "Watch" + item.operation = "Watch" item.kind = command end else - item.ops = "Once" + item.operation = "Once" end if not item.root then item.root = vim.loop.cwd() @@ -54,11 +58,11 @@ local insert_entry = function(acc, picker, command, target, configuration, watch acc[#acc + 1] = item end -local get_selections = function(picker) - local commands = picker == "Watch" and { "Build", "Run" } or { picker } - local targets = xbase.targets() - local include_devices = picker == "Run" or picker == "Watch" - local watchlist = picker == "Watch" and xbase.watching() or {} +local get_selections = function(project_info, picker) + local commands = picker == C.Watch and { C.Build, C.Run } or { picker } + local targets = project_info.targets + local include_devices = picker == C.Run or picker == C.Watch + local watchlist = picker == "Watch" and project_info.watchlist or {} if targets == nil then error "No targets found" @@ -78,8 +82,8 @@ local get_selections = function(picker) for _, command in ipairs(commands) do for target, target_info in pairs(targets) do for _, configuration in ipairs(configurations) do - local devices = xbase.runners(target_info.platform) - if include_devices and command == "Run" and #devices ~= 0 then + local devices = state.runners[target_info.platform] + if include_devices and command == C.Run and not (devices == nil or #devices == 0) then for _, device in ipairs(devices) do insert_entry(results, picker, command, target, configuration, watchlist, device) end @@ -90,16 +94,7 @@ local get_selections = function(picker) end end - -- if picker == "Run" or picker == "Watch" then - -- table.sort(results, function(a, b) - -- if a.device and b.device then - -- return a.device.is_on and not b.device.is_on - -- else - -- return - -- end - -- end) - -- end - + -- TODO: Keep prioritize last used device return results end @@ -116,12 +111,12 @@ local entry_maker = function(entry) local items, parts = {}, {} local ti = table.insert - if entry.ops and entry.ops ~= "Once" then - entry.ordinal = string.format("%s %s", entry.ordinal, entry.ops) - local ops = string.format("%s", entry.ops) + if entry.operation and entry.operation ~= "Once" then + entry.ordinal = string.format("%s %s", entry.ordinal, entry.operation) + local operation = string.format("%s", entry.operation) ti(items, { width = 7 }) - ti(parts, { ops, "TSNone" }) + ti(parts, { operation, "TSNone" }) end if entry.kind then @@ -155,83 +150,95 @@ local entry_maker = function(entry) return entry end -M.watch = function(opts) +local find = function(name, opts) opts = themes.get_dropdown(opts or {}) - picker(opts, { - sorter = sorter {}, - prompt_title = "Watch", - finder = finder { results = get_selections "Watch", entry_maker = entry_maker }, - attach_mappings = mappings, - }):find() + opts.root = opts.root or vim.loop.cwd() + local _find = function(opts) + picker(opts, { + prompt_title = opts.name, + sorter = sorter {}, + finder = finder { + results = get_selections(opts.project_info, name), + entry_maker = entry_maker, + }, + attach_mappings = mappings, + }):find() + end + + if opts.project_info then + _find(opts) + else + server.get_project_info(opts.root, function(project_info) + opts.project_info = project_info + _find(opts) + end) + end +end + +M.watch = function(opts) + find(C.Watch, opts) end M.build = function(opts) - opts = themes.get_dropdown(opts or {}) - picker(opts, { - sorter = sorter {}, - prompt_title = "Build", - finder = finder { results = get_selections "Build", entry_maker = entry_maker }, - attach_mappings = mappings, - }):find() + find(C.Build, opts) end M.run = function(opts) - opts = themes.get_dropdown(opts or {}) - picker(opts, { - sorter = sorter {}, - prompt_title = "Run", - finder = finder { results = get_selections "Run", entry_maker = entry_maker }, - attach_mappings = mappings, - }):find() + find(C.Run, opts) end M.actions = function(opts) opts = require("telescope.themes").get_dropdown(opts or {}) - picker(opts, { - sorter = sorter {}, - prompt_title = "Pick Xbase Action Category", - finder = finder { - results = { - { value = "Watch" }, - { value = "Build" }, - { value = "Run" }, - }, - entry_maker = function(entry) - entry.ordinal = entry.value - entry.display = function(e) - local opts = {} - - opts.separator = " " - opts.hl_chars = { ["|"] = "TelescopeResultsNumber" } - opts.items = { { width = 40 } } + opts.root = opts.root or vim.loop.cwd() + server.get_project_info(opts.root, function(project_info) + opts.project_info = project_info + + picker(opts, { + sorter = sorter {}, + prompt_title = "Pick Xbase Action Category", + finder = finder { + results = { + { value = C.Build }, + { value = C.Watch }, + { value = C.Run }, + }, + entry_maker = function(entry) + entry.ordinal = entry.value + entry.display = function(e) + local opts = {} + + opts.separator = " " + opts.hl_chars = { ["|"] = "TelescopeResultsNumber" } + opts.items = { { width = 40 } } + + return maker(opts) { { e.value, "TelescopeResultsMethod" } } + end - return maker(opts) { { e.value, "TelescopeResultsMethod" } } - end + return entry + end, + }, + attach_mappings = function(_, _) + a.select_default:replace(function(bufnr) + local selected = s.get_selected_entry() + + a.close(bufnr) + if not selected then + print "No selection" + return + end - return entry + if selected.value == C.Watch then + M.watch(opts) + elseif selected.value == C.Build then + M.build(opts) + elseif selected.value == C.Run then + M.run(opts) + end + end) + return true end, - }, - attach_mappings = function(_, _) - a.select_default:replace(function(bufnr) - local selected = s.get_selected_entry() - - a.close(bufnr) - if not selected then - print "No selection" - return - end - - if selected.value == "Watch" then - M.watch(opts) - elseif selected.value == "Build" then - M.build(opts) - elseif selected.value == "Run" then - M.run(opts) - end - end) - return true - end, - }):find() + }):find() + end) end return M diff --git a/lua/xbase/server.lua b/lua/xbase/server.lua new file mode 100644 index 0000000..16053ce --- /dev/null +++ b/lua/xbase/server.lua @@ -0,0 +1,126 @@ +local util = require "xbase.util" +local socket = require "xbase.socket" +local validate = vim.validate +local notify = require "xbase.notify" +local broadcast = require "xbase.broadcast" +local server_address = "/tmp/xbase.socket" +local uv = vim.loop + +---@class XBase +local M = { + ---@type XBaseSocket @helper object to communcate with xbase daemon + socket = nil, + ---@type string[] @list of registered roots + roots = {}, +} + +---Spawn xbase daemon in detached mode and executes cb on first stdout +---@param cb function +function M.spawn_daemon(cb) + notify.info "Starting new dameon instance" + local bin = vim.env.HOME .. "/.local/share/xbase/xbase" + local stdout = uv.new_pipe() + local _, _ = uv.spawn(bin, { + stdio = { nil, stdout, nil }, + detached = true, + }) + stdout:read_start(vim.schedule_wrap(function(_, _) + M.socket = socket:connect(server_address) + stdout:read_stop() + cb() + end)) +end + +--- Ensure we have a connect socket and a running background daemon +function M.ensure_connection(cb) + if M.socket == nil then + if uv.fs_stat(server_address) == nil then + return M.spawn_daemon(cb) + else + M.socket = socket:connect(server_address) + end + end + cb() +end + +---Send Request to socket, and on response call on_response with data if no error +---@param req table +---@param on_response? function(response:table) +function M.request(req, on_response) + M.ensure_connection(function() + M.socket:read_start(function(chunk) + vim.schedule(function() + local res = vim.json.decode(chunk) + if res.error then + notify.error(string.format("%s %s", res.error.kind, res.error.msg)) + return + else + if on_response then + on_response(res.data) + end + end + end) + M.socket:read_stop() + end) + M.socket:write(req) + end) +end + +---Check whether the vim instance should be registered to xbase server. +---@param root string: current working directory +---@return boolean +function M.should_register(root) + if uv.fs_stat(root .. "/project.yml") then + return true + elseif uv.fs_stat(root .. "/Project.swift") then + return true + elseif uv.fs_stat(root .. "/Package.swift") then + return true + elseif vim.fn.glob(root .. "/*.xcodeproj"):len() ~= 0 then + return true + end + return false +end + +---Register given root and return true if the root is registered +---@param root string +---@return boolean +function M.register(root) + validate { root = { root, "string", false } } + + require("xbase.logger").setup() + + M.request({ method = "register", root = root }, function(broadcast_address) + notify.info(("[%s] Connected "):format(util.project_name(root))) + + broadcast.start(broadcast_address) + + M.roots[#M.roots + 1] = root + + -- Fill state with available runners + if not require("xbase.state").runners then + M.request({ method = "get_runners" }, function(runners) + require("xbase.state").runners = runners + end) + end + end) +end + +---Get Project information +function M.get_project_info(root, on_response) + M.request({ + method = "get_project_info", + root = root, + }, on_response) +end + +---Drop a given root or drop all tracked roots if root is nil +---@param roots string|string[] +function M.drop(roots) + M.request { + method = "drop", + roots = type(roots) == "string" and { roots } or roots, + } +end + +return M diff --git a/lua/xbase/socket.lua b/lua/xbase/socket.lua new file mode 100644 index 0000000..c629463 --- /dev/null +++ b/lua/xbase/socket.lua @@ -0,0 +1,55 @@ +local uv = require "luv" + +---@class XBaseSocket @Object to communcate with xbase sockets +---@field _socket any +---@field _stream_error any +local M = {} +M.__index = M + +function M:connect(address) + local socket = uv.new_pipe(false) + local self = setmetatable({ _socket = socket, _stream_error = nil }, M) + socket:connect(address, function(err) + self._stream_error = self._stream_error or err + end) + return self +end + +function M:write(data) + if self._stream_error then + error(self._stream_error) + end + uv.write(self._socket, vim.json.encode(data), function(err) + if err then + error(self._stream_error or err) + end + end) +end + +function M:read_start(cb) + if self._stream_error then + error(self._stream_error) + end + self._socket:read_start(function(err, chunk) + if err then + error(err) + elseif chunk ~= nil then + vim.schedule(function() + cb(chunk) + end) + end + end) +end + +function M:read_stop() + if self._stream_error then + error(self._stream_error) + end + uv.read_stop(self._socket) +end + +function M:close() + uv.close(self._socket) +end + +return M diff --git a/lua/xbase/state.lua b/lua/xbase/state.lua new file mode 100644 index 0000000..7886ee3 --- /dev/null +++ b/lua/xbase/state.lua @@ -0,0 +1,7 @@ +local M = { + --- Devices index by platform + ---@type table + runners = nil, +} + +return M diff --git a/lua/xbase/util.lua b/lua/xbase/util.lua index 3366f7d..703c834 100644 --- a/lua/xbase/util.lua +++ b/lua/xbase/util.lua @@ -36,6 +36,11 @@ function M.is_watching(config, command, device, watchlist) return false end +function M.project_name(root) + local parts = vim.split(root, "/") + return parts[#parts]:gsub("^%l", string.upper) +end + function M.try_map(key, fun, bufnr) if type(key) == "string" then vim.keymap.set("n", key, fun, { buffer = bufnr and bufnr or true }) @@ -49,11 +54,11 @@ function M.bind(m, bufnr) M.try_map(m.watch_picker, pickers.watch, bufnr) M.try_map(m.all_picker, pickers.actions, bufnr) M.try_map(m.toggle_split_log_buffer, function() - require("xbase.log").toggle(false, true) + require("xbase.logger").toggle(false, true) end, bufnr) M.try_map(m.toggle_vsplit_log_buffer, function() - require("xbase.log").toggle(true, true) + require("xbase.logger").toggle(true, true) end, bufnr) end diff --git a/proto/Cargo.toml b/proto/Cargo.toml deleted file mode 100644 index 709581c..0000000 --- a/proto/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "xbase-proto" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -async-trait = "0.1.56" -paste = "1.0.7" -serde = { version = "1", features = ["derive"] } -serde_json = "1" -strum = { version = "0.24.0", features = ["derive"] } -log = { path = "../log/" } -tarpc = { version = "0.29.0", features = ["serde-transport", "tokio1", "serde1"] } -# tokio = { version = "1.19.2", features = ["macros", "net"]} -futures = "0.3" -anyhow = "1.0" -thiserror = "1.0.31" -which = { version = "4.2.5", optional = true } -notify = { version = "4.0.17", optional = true } -simctl = { git = "https://github.com/xbase-lab/simctl", optional = true } - -tokio-serde = { version = "0.8.0", features = ["bincode", "json"] } -tokio-util = { version = "0.6.9", features = ["codec"] } -mlua = { version = "0.8.0", optional = true } -process-stream = { version = "0.4.*", features = ["serde"] } -tokio = { version = "1.19.2" } -linemux = {version = "0.2.3", optional = true} -xcodeproj = "0.2.12" - -[features] -default = [] -server = [ "which", "notify", "simctl"] -client = [ ] -neovim = [ "client", "mlua", "linemux" ] diff --git a/proto/src/lib.rs b/proto/src/lib.rs deleted file mode 100644 index 1438808..0000000 --- a/proto/src/lib.rs +++ /dev/null @@ -1,63 +0,0 @@ -mod error; -mod message; -mod request; -mod types; -mod util; - -use std::collections::{HashMap, HashSet}; -use std::path::PathBuf; - -pub use error::*; -pub use message::*; -pub use request::*; -pub use tarpc::context::{self, Context}; -pub use tarpc::serde_transport as transport; -pub use tarpc::server::{BaseChannel, Channel}; -pub use tokio_serde::formats::Json; -pub use tokio_util::codec::length_delimited::LengthDelimitedCodec; -pub use types::*; -pub use util::PathExt; -use xcodeproj::pbxproj::PBXTargetPlatform; -pub type Result = std::result::Result; - -/// Short hand to get Result with given Error -/// based by anyhow's -#[allow(non_snake_case)] -pub fn OK(t: T) -> Result { - Result::Ok(t) -} - -#[tarpc::service] -pub trait XBase { - /// - /// Register client and project root. - /// - /// If the project is already registered then it will not be re-registered and instead - /// broadcast address socket will be returned. - /// - async fn register(root: PathBuf) -> Result; - /// - /// Build Project and get path to where to build log will be located - /// - async fn build(req: BuildRequest) -> Result<()>; - /// - /// Run Project and get path to where to Runtime log will be located - /// - async fn run(req: RunRequest) -> Result<()>; - /// - /// Drop project at a given root - /// - async fn drop(roots: HashSet) -> Result<()>; - /// - /// Return targets for client projects - /// - async fn targets(root: PathBuf) -> Result>; - /// - /// Return device names and id for given target - /// - async fn runners(platform: PBXTargetPlatform) -> Result>>; - /// - /// Return all watched keys - /// - async fn watching(root: PathBuf) -> Result>; -} diff --git a/proto/src/request.rs b/proto/src/request.rs deleted file mode 100644 index 0d3dab7..0000000 --- a/proto/src/request.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::types::*; -use crate::util::value_or_default; -use serde::{Deserialize, Serialize}; -use std::{fmt::Display, path::PathBuf}; - -#[cfg(feature = "neovim")] -use mlua::prelude::*; - -/// Request to build a particular project -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct BuildRequest { - pub root: PathBuf, - pub settings: BuildSettings, - #[serde(deserialize_with = "value_or_default")] - pub direction: BufferDirection, - pub ops: Operation, -} - -/// Request to Run a particular project. -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RunRequest { - pub root: PathBuf, - pub settings: BuildSettings, - #[serde(deserialize_with = "value_or_default")] - pub device: DeviceLookup, - #[serde(deserialize_with = "value_or_default")] - pub direction: BufferDirection, - #[serde(deserialize_with = "value_or_default")] - pub ops: Operation, -} - -#[cfg(feature = "neovim")] -impl<'a> FromLua<'a> for BuildRequest { - fn from_lua(value: LuaValue<'a>, _: &'a Lua) -> LuaResult { - if let LuaValue::Table(table) = value { - Ok(Self { - root: table.get::<_, String>("root")?.into(), - settings: table.get("settings")?, - direction: table.get("direction")?, - ops: table.get("ops")?, - }) - } else { - Err(LuaError::external("Expected a table for BuildRequest")) - } - } -} - -impl Display for BuildRequest { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}:Build:{}", self.root.display(), self.settings) - } -} - -#[cfg(feature = "neovim")] -impl<'a> FromLua<'a> for RunRequest { - fn from_lua(value: LuaValue<'a>, _: &'a Lua) -> LuaResult { - if let LuaValue::Table(table) = value { - Ok(Self { - root: table.get::<_, String>("root")?.into(), - settings: table.get("settings")?, - direction: table.get("direction")?, - device: table.get("device")?, - ops: table.get("ops")?, - }) - } else { - Err(LuaError::external("Expected a table for BuildRequest")) - } - } -} - -impl Display for RunRequest { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}:Run:{}:{}", - self.root.display(), - self.device.name.as_ref().unwrap_or(&"Bin".to_string()), - self.settings - ) - } -} diff --git a/proto/src/types.rs b/proto/src/types.rs deleted file mode 100644 index 5416599..0000000 --- a/proto/src/types.rs +++ /dev/null @@ -1,214 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::fmt::Display; -use strum::{Display as EnumDisplay, EnumString}; -use xcodeproj::pbxproj::PBXTargetPlatform; - -#[cfg(feature = "neovim")] -use mlua::prelude::*; - -/// Build Configuration to run -#[derive(Clone, Debug, Serialize, Deserialize, EnumDisplay, EnumString)] -pub enum BuildConfiguration { - Debug, - Release, - Custom(String), -} - -#[cfg(feature = "neovim")] -impl<'a> FromLua<'a> for BuildConfiguration { - fn from_lua(value: LuaValue<'a>, _: &'a Lua) -> LuaResult { - use std::str::FromStr; - if let LuaValue::String(ref value) = value { - Self::from_str(value.to_str()?).to_lua_err() - } else { - Err(LuaError::external( - "Expected a string value for BuildConfiguration", - )) - } - } -} - -/// Operation -/// -/// Should request be executed once, stoped (if watched) or start new watch service? -#[derive(Clone, Debug, Serialize, Deserialize, EnumDisplay, EnumString)] -pub enum Operation { - Watch, - Stop, - Once, -} - -#[cfg(feature = "neovim")] -impl<'a> FromLua<'a> for Operation { - fn from_lua(value: LuaValue<'a>, _lua: &'a Lua) -> LuaResult { - use std::str::FromStr; - if let LuaValue::String(value) = value { - let value = value.to_string_lossy(); - Self::from_str(&*value).to_lua_err() - } else { - Ok(Operation::default()) - } - } -} - -/// Fields required to build a project -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct BuildSettings { - /// Target to build - pub target: String, - /// Configuration to build with, default Debug - pub configuration: BuildConfiguration, - /// Scheme to build with - pub scheme: Option, -} - -#[cfg(feature = "neovim")] -impl<'a> FromLua<'a> for BuildSettings { - fn from_lua(value: LuaValue<'a>, _: &'a Lua) -> LuaResult { - if let LuaValue::Table(table) = value { - Ok(Self { - target: table.get("target")?, - configuration: table.get("configuration")?, - scheme: table.get("scheme")?, - }) - } else { - Err(LuaError::external( - "Expected a table value for BuildSettings", - )) - } - } -} - -/// Fields required to build a project -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct TargetInfo { - /// Configuration to build with, default Debug - pub platform: PBXTargetPlatform, - /// Scheme to build with - pub watching: bool, -} - -#[cfg(feature = "neovim")] -impl<'a> ToLua<'a> for TargetInfo { - fn to_lua(self, lua: &'a Lua) -> LuaResult> { - let table = lua.create_table()?; - table.set("platform", self.platform.to_string())?; - table.set("watching", self.watching)?; - Ok(LuaValue::Table(table)) - } -} - -/// Log Buffer open direction -#[derive(Clone, Debug, strum::EnumString, Serialize, Deserialize)] -#[strum(ascii_case_insensitive)] -#[serde(untagged)] -pub enum BufferDirection { - Default, - Vertical, - Horizontal, - TabEdit, -} - -#[cfg(feature = "neovim")] -impl<'a> FromLua<'a> for BufferDirection { - fn from_lua(value: LuaValue<'a>, _: &'a Lua) -> LuaResult { - use std::str::FromStr; - if let LuaValue::String(value) = value { - let value = value.to_string_lossy(); - Self::from_str(&*value).to_lua_err() - } else { - Ok(Self::Default) - } - } -} - -/// Device Lookup information to run built project with -#[derive(Clone, Default, Debug, Serialize, Deserialize)] -pub struct DeviceLookup { - pub name: Option, - pub id: Option, -} - -#[cfg(feature = "neovim")] -impl<'a> FromLua<'a> for DeviceLookup { - fn from_lua(value: LuaValue<'a>, _: &'a Lua) -> LuaResult { - if let LuaValue::Table(table) = value { - Ok(Self { - name: table.get("name").ok(), - id: table.get("id").ok(), - }) - } else { - Ok(Self::default()) - } - } -} - -impl Default for BufferDirection { - fn default() -> Self { - Self::Default - } -} - -impl Default for Operation { - fn default() -> Self { - Self::Once - } -} - -impl Display for BuildSettings { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "-configuration {}", self.configuration)?; - - if let Some(ref scheme) = self.scheme { - write!(f, " -scheme {scheme}")?; - } - write!(f, " -target {}", self.target)?; - Ok(()) - } -} -impl BuildSettings { - pub fn to_args(&self) -> Vec { - self.to_string() - .split_whitespace() - .map(ToString::to_string) - .collect::>() - } -} - -impl Operation { - /// Returns `true` if the request kind is [`Watch`]. - /// - /// [`Watch`]: RequestKind::Watch - #[must_use] - pub fn is_watch(&self) -> bool { - matches!(self, Self::Watch) - } - - /// Returns `true` if the request kind is [`WatchStop`]. - /// - /// [`WatchStop`]: RequestKind::WatchStop - #[must_use] - pub fn is_stop(&self) -> bool { - matches!(self, Self::Stop) - } - - /// Returns `true` if the request kind is [`Once`]. - /// - /// [`Once`]: RequestKind::Once - #[must_use] - pub fn is_once(&self) -> bool { - matches!(self, Self::Once) - } -} - -impl BufferDirection { - pub fn to_nvim_command(&self, bufnr: i64) -> String { - match self { - // TOOD: support build log float as default - BufferDirection::Default => format!("sbuffer {bufnr}"), - BufferDirection::Vertical => format!("vert sbuffer {bufnr}"), - BufferDirection::Horizontal => format!("sbuffer {bufnr}"), - BufferDirection::TabEdit => format!("tabe {bufnr}"), - } - } -} diff --git a/src/broadcast/logger.rs b/src/broadcast/logger.rs new file mode 100644 index 0000000..8dc3532 --- /dev/null +++ b/src/broadcast/logger.rs @@ -0,0 +1,108 @@ +#![allow(dead_code)] +use super::{Message, MessageLevel, StatuslineState, Task}; +use std::path::PathBuf; + +impl super::Broadcast { + /// Explicitly Abort/Consume logger + pub fn abort(&self) { + self.abort.notify_waiters(); + } + + /// Get a reference to the logger's project root. + #[must_use] + pub fn root(&self) -> &PathBuf { + &self.root + } + + /// Get a reference to the logger's log path. + #[must_use] + pub fn address(&self) -> &PathBuf { + &self.address + } + + pub fn log_step>(&self, msg: S) { + let msg = msg.as_ref(); + tracing::info!("{msg}"); + self.tx.send(Message::log_info(msg)).ok(); + self.tx.send(Message::log_info(".".repeat(73))).ok(); + } + + pub fn success>(&self, msg: S) { + let msg = msg.as_ref(); + self.tx + .send(Message::Notify { + msg: msg.into(), + level: MessageLevel::Success, + }) + .ok(); + } + + pub fn info>(&self, msg: S) { + let msg = msg.as_ref(); + self.tx.send(msg.into()).ok(); + } + + pub fn error>(&self, msg: S) { + let msg = msg.as_ref(); + tracing::error!("{msg}"); + self.tx.send(Message::notify_error(msg)).ok(); + } + + pub fn warn>(&self, msg: S) { + let msg = msg.as_ref(); + tracing::warn!("{msg}"); + self.tx.send(Message::notify_warn(msg)).ok(); + } + + pub fn trace>(&self, msg: S) { + let msg = msg.as_ref(); + tracing::trace!("{msg}"); + self.tx.send(Message::notify_trace(msg)).ok(); + } + + pub fn debug>(&self, msg: S) { + let msg = msg.as_ref(); + tracing::debug!("{msg}"); + self.tx.send(Message::notify_debug(msg)).ok(); + } + + pub fn log_info>(&self, msg: S) { + let msg = msg.as_ref(); + tracing::info!("{msg}"); + self.tx.send(Message::log_info(msg)).ok(); + } + + pub fn log_error>(&self, msg: S) { + tracing::error!("{}", msg.as_ref()); + self.tx.send(Message::log_error(msg)).ok(); + } + + pub fn log_warn>(&self, msg: S) { + tracing::warn!("{}", msg.as_ref()); + self.tx.send(Message::log_warn(msg)).ok(); + } + + pub fn log_trace>(&self, msg: S) { + tracing::trace!("{}", msg.as_ref()); + self.tx.send(Message::log_trace(msg)).ok(); + } + + pub fn log_debug>(&self, msg: S) { + tracing::debug!("{}", msg.as_ref()); + self.tx.send(Message::log_debug(msg)).ok(); + } + + pub fn update_statusline(&self, state: StatuslineState) { + self.tx + .send(Message::Execute(Task::UpdateStatusline { value: state })) + .ok(); + } + + pub fn open_logger(&self) { + self.tx.send(Message::Execute(Task::OpenLogger)).ok(); + } + + pub fn reload_lsp_server(&self) { + self.tx.send(Message::Execute(Task::ReloadLspServer)).ok(); + } +} diff --git a/proto/src/message.rs b/src/broadcast/message.rs similarity index 79% rename from proto/src/message.rs rename to src/broadcast/message.rs index 094f0f5..1e4b28b 100644 --- a/proto/src/message.rs +++ b/src/broadcast/message.rs @@ -1,8 +1,10 @@ use process_stream::ProcessItem; use serde::{Deserialize, Serialize}; +use serde_repr::*; /// Representation of Messages that clients needs to process #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(tag = "type")] pub enum Message { /// Notify use with a message Notify { msg: String, level: MessageLevel }, @@ -12,112 +14,26 @@ pub enum Message { Execute(Task), } -impl Message { - pub fn notify_error>(value: S) -> Self { - Self::Notify { - msg: value.as_ref().to_string(), - level: MessageLevel::Error, - } - } - - pub fn notify_warn>(value: S) -> Self { - Self::Notify { - msg: value.as_ref().to_string(), - level: MessageLevel::Warn, - } - } - - pub fn notify_trace>(value: S) -> Self { - Self::Notify { - msg: value.as_ref().to_string(), - level: MessageLevel::Trace, - } - } - - pub fn notify_debug>(value: S) -> Self { - Self::Notify { - msg: value.as_ref().to_string(), - level: MessageLevel::Debug, - } - } - - pub fn log_error>(value: S) -> Self { - Self::Log { - msg: value.as_ref().to_string(), - level: MessageLevel::Error, - } - } - - pub fn log_info>(value: S) -> Self { - Self::Log { - msg: value.as_ref().to_string(), - level: MessageLevel::Error, - } - } - - pub fn log_warn>(value: S) -> Self { - Self::Log { - msg: value.as_ref().to_string(), - level: MessageLevel::Warn, - } - } - - pub fn log_trace>(value: S) -> Self { - Self::Log { - msg: value.as_ref().to_string(), - level: MessageLevel::Trace, - } - } - - pub fn log_debug>(value: S) -> Self { - Self::Log { - msg: value.as_ref().to_string(), - level: MessageLevel::Debug, - } - } -} - /// Statusline state -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "snake_case")] pub enum StatuslineState { - /// Last task was successful - Success, + /// Clear statusline + Clear, /// Last task failed Failure, /// A Request is being processed. Processing, - /// Something is being watched. - Watching, /// that is currently running. Running, - /// Clear statusline - Clear, -} - -impl std::fmt::Display for StatuslineState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let value = match self { - StatuslineState::Success => "success", - StatuslineState::Failure => "failure", - StatuslineState::Processing => "processing", - StatuslineState::Watching => "watching", - StatuslineState::Running => "running", - StatuslineState::Clear => "", - }; - write!(f, "{value}") - } -} - -/// Tasks that the clients should execute -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub enum Task { - UpdateStatusline(StatuslineState), - OpenLogger, - ReloadLspServer, + /// Last task was successful + Success, + /// Something is being watched. + Watching, } -/// Message Kind -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +/// Message Level +#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] #[repr(u8)] pub enum MessageLevel { /// Trace Message @@ -134,46 +50,27 @@ pub enum MessageLevel { Success = 5, } -impl MessageLevel { - /// Returns `true` if the message level is [`Trace`]. - /// - /// [`Trace`]: MessageLevel::Trace - #[must_use] - pub fn is_trace(&self) -> bool { - matches!(self, Self::Trace) - } - - /// Returns `true` if the message level is [`Debug`]. - /// - /// [`Debug`]: MessageLevel::Debug - #[must_use] - pub fn is_debug(&self) -> bool { - matches!(self, Self::Debug) - } - - /// Returns `true` if the message level is [`Info`]. - /// - /// [`Info`]: MessageLevel::Info - #[must_use] - pub fn is_info(&self) -> bool { - matches!(self, Self::Info) - } - - /// Returns `true` if the message level is [`Warn`]. - /// - /// [`Warn`]: MessageLevel::Warn - #[must_use] - pub fn is_warn(&self) -> bool { - matches!(self, Self::Warn) +impl std::fmt::Display for StatuslineState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let value = match self { + StatuslineState::Success => "success", + StatuslineState::Failure => "failure", + StatuslineState::Processing => "processing", + StatuslineState::Watching => "watching", + StatuslineState::Running => "running", + StatuslineState::Clear => "", + }; + write!(f, "{value}") } +} - /// Returns `true` if the message level is [`Error`]. - /// - /// [`Error`]: MessageLevel::Error - #[must_use] - pub fn is_error(&self) -> bool { - matches!(self, Self::Error) - } +/// Tasks that the clients should execute +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[serde(tag = "task")] +pub enum Task { + OpenLogger, + ReloadLspServer, + UpdateStatusline { value: StatuslineState }, } impl From for Message { @@ -240,3 +137,68 @@ impl From<&str> for Message { } } } + +impl Message { + pub fn notify_error>(value: S) -> Self { + Self::Notify { + msg: value.as_ref().to_string(), + level: MessageLevel::Error, + } + } + + pub fn notify_warn>(value: S) -> Self { + Self::Notify { + msg: value.as_ref().to_string(), + level: MessageLevel::Warn, + } + } + + pub fn notify_trace>(value: S) -> Self { + Self::Notify { + msg: value.as_ref().to_string(), + level: MessageLevel::Trace, + } + } + + pub fn notify_debug>(value: S) -> Self { + Self::Notify { + msg: value.as_ref().to_string(), + level: MessageLevel::Debug, + } + } + + pub fn log_error>(value: S) -> Self { + Self::Log { + msg: value.as_ref().to_string(), + level: MessageLevel::Error, + } + } + + pub fn log_info>(value: S) -> Self { + Self::Log { + msg: value.as_ref().to_string(), + level: MessageLevel::Info, + } + } + + pub fn log_warn>(value: S) -> Self { + Self::Log { + msg: value.as_ref().to_string(), + level: MessageLevel::Warn, + } + } + + pub fn log_trace>(value: S) -> Self { + Self::Log { + msg: value.as_ref().to_string(), + level: MessageLevel::Trace, + } + } + + pub fn log_debug>(value: S) -> Self { + Self::Log { + msg: value.as_ref().to_string(), + level: MessageLevel::Debug, + } + } +} diff --git a/daemon/src/broadcast.rs b/src/broadcast/mod.rs similarity index 52% rename from daemon/src/broadcast.rs rename to src/broadcast/mod.rs index 4f36a18..0c3ceb2 100644 --- a/daemon/src/broadcast.rs +++ b/src/broadcast/mod.rs @@ -1,4 +1,8 @@ -#![allow(unused_imports, unused_macros)] +mod logger; +mod message; + +pub use self::message::*; +use crate::util::extensions::PathExt; use crate::Result; use process_stream::*; use std::path::{Path, PathBuf}; @@ -8,7 +12,6 @@ use tokio::io::AsyncWriteExt; use tokio::net::{UnixListener, UnixStream}; use tokio::sync::{mpsc::*, Mutex, Notify}; use tokio::task::JoinHandle; -use xbase_proto::{Message, MessageLevel, PathExt, StatuslineState, Task}; /// Broadcast server to send task to clients #[derive(Debug)] @@ -43,16 +46,18 @@ impl Broadcast { } let address = base.join(name); + let name = address.file_name().unwrap().to_str().unwrap(); if address.exists() { - log::warn!("address {address:?} should have been removed automatically, removing .."); + tracing::warn!("[{address:?}] Exists, removing ..."); tokio::fs::remove_file(&address).await.ok(); }; let abort: Arc = Default::default(); let listeners: Arc>> = Default::default(); - let server = Self::start_server(&address, abort.clone(), listeners.clone())?; + tracing::info!("[{name}] Initialized"); + let server = Self::start_server(address.clone(), abort.clone(), listeners.clone())?; let handle = Self::start_messages_handler(rx, abort.clone(), listeners.clone())?; Ok(Self { @@ -87,15 +92,14 @@ impl Broadcast { break; }, result = stream.next() => match result { - None => break, Some(output) => { if let Some(succ) = output.is_success() { send_status.send(succ).await.ok(); - } - if let Err(e) = tx.send(output.into()) { - log::error!("Fail to send to channel {e}"); + } else if let Err(e) = tx.send(output.into()) { + tracing::error!("Fail to send to channel {e}"); }; } + None => break, } }; @@ -106,24 +110,26 @@ impl Broadcast { /// Start Broadcast server and start accepting clients fn start_server( - address: &PathBuf, + address: PathBuf, abort: Arc, listeners: Arc>>, ) -> Result> { let listener = UnixListener::bind(&address)?; tokio::spawn(async move { + let name = address.file_name().unwrap().to_str().unwrap(); loop { let listeners = listeners.clone(); tokio::select! { _ = abort.notified() => { - log::info!("Closing server"); + tracing::info!("[{name}] Closed"); + tokio::fs::remove_file(&address).await.ok(); break }, Ok((stream, _)) = listener.accept() => { let mut listeners = listeners.lock().await; listeners.push(stream); - log::info!("Connected"); + tracing::info!("[{name}] Registered new client"); } } } @@ -141,17 +147,25 @@ impl Broadcast { tokio::spawn(async move { loop { tokio::select! { - _ = abort.notified() => { log::info!("Stopping logging"); break; }, + _ = abort.notified() => { break; }, result = rx.recv() => match result { None => break, Some(output) => { - let mut listeners = listeners.lock().await; - if let Ok(value) = serde_json::to_string(&output) { - for listener in listeners.iter_mut() { - listener.write_all(format!("{value}\n").as_bytes()).await.ok(); - listener.flush().await.ok(); - }; - }; + let listeners = listeners.clone(); + tokio::spawn(async move { + let mut listeners = listeners.lock().await; + match serde_json::to_string(&output) { + Ok(mut value) => { + tracing::debug!("Sent: {value}"); + value.push('\n'); + for listener in listeners.iter_mut() { + listener.write_all(value.as_bytes()).await.ok(); + listener.flush().await.ok(); + }; + }, + Err(err) => tracing::warn!("SendError: `{output:?}` = `{err}`"), + } + }); } } @@ -160,108 +174,4 @@ impl Broadcast { }) .pipe(Ok) } - - /// Explicitly Abort/Consume logger - pub fn abort(&self) { - self.abort.notify_waiters(); - } - - /// Get a reference to the logger's project root. - #[must_use] - pub fn root(&self) -> &PathBuf { - &self.root - } - - /// Get a reference to the logger's log path. - #[must_use] - pub fn address(&self) -> &PathBuf { - &self.address - } -} - -impl Broadcast { - pub fn log_step>(&self, msg: S) { - let sep = ".".repeat(73); - self.tx.send(Message::log_info(msg)).ok(); - self.tx.send(Message::log_info(&sep)).ok(); - } - - pub fn log_separator(&self) { - let sep = ".".repeat(73); - log::info!("{sep}"); - self.tx.send(Message::log_info(&sep)).ok(); - } - - pub fn success>(&self, msg: S) { - log::info!("{}", msg.as_ref()); - self.tx - .send(Message::Notify { - msg: msg.as_ref().into(), - level: MessageLevel::Success, - }) - .ok(); - } - - pub fn info>(&self, msg: S) { - log::info!("{}", msg.as_ref()); - self.tx.send(msg.as_ref().into()).ok(); - } - - pub fn error>(&self, msg: S) { - log::error!("{}", msg.as_ref()); - self.tx.send(Message::notify_error(msg)).ok(); - } - - pub fn warn>(&self, msg: S) { - log::warn!("{}", msg.as_ref()); - self.tx.send(Message::notify_warn(msg)).ok(); - } - - pub fn trace>(&self, msg: S) { - log::trace!("{}", msg.as_ref()); - self.tx.send(Message::notify_trace(msg)).ok(); - } - - pub fn debug>(&self, msg: S) { - log::debug!("{}", msg.as_ref()); - self.tx.send(Message::notify_debug(msg)).ok(); - } - - pub fn log_info>(&self, msg: S) { - self.tx.send(Message::log_info(msg)).ok(); - } - - pub fn log_error>(&self, msg: S) { - log::error!("{}", msg.as_ref()); - self.tx.send(Message::log_error(msg)).ok(); - } - - pub fn log_warn>(&self, msg: S) { - log::warn!("{}", msg.as_ref()); - self.tx.send(Message::log_warn(msg)).ok(); - } - - pub fn log_trace>(&self, msg: S) { - log::trace!("{}", msg.as_ref()); - self.tx.send(Message::log_trace(msg)).ok(); - } - - pub fn log_debug>(&self, msg: S) { - log::debug!("{}", msg.as_ref()); - self.tx.send(Message::log_debug(msg)).ok(); - } - - pub fn update_statusline(&self, state: StatuslineState) { - self.tx - .send(Message::Execute(Task::UpdateStatusline(state))) - .ok(); - } - - pub fn open_logger(&self) { - self.tx.send(Message::Execute(Task::OpenLogger)).ok(); - } - - pub fn reload_lsp_server(&self) { - self.tx.send(Message::Execute(Task::ReloadLspServer)).ok(); - } } diff --git a/proto/src/error.rs b/src/error.rs similarity index 87% rename from proto/src/error.rs rename to src/error.rs index b9231e6..24d6157 100644 --- a/proto/src/error.rs +++ b/src/error.rs @@ -30,9 +30,6 @@ pub enum Error { DefinitionMutliFound, #[error("{0}")] Unexpected(String), - #[cfg(feature = "client")] - #[error("{0}")] - RPC(#[from] tarpc::client::RpcError), #[error("{0}")] JoinError(#[from] tokio::task::JoinError), #[error("{0}")] @@ -74,8 +71,6 @@ impl From<&Error> for ErrorInner { Error::DefinitionLocating => res.kind = "DefinitionLocating".into(), Error::DefinitionMutliFound => res.kind = "DefinitionMutliFound".into(), Error::Unexpected(_) => res.kind = "General".into(), - #[cfg(feature = "client")] - Error::RPC(_) => res.kind = "RPC".into(), Error::JoinError(_) => res.kind = "JoinError".into(), Error::SendError(_) => res.kind = "SendError".into(), Error::MessageParse(_) => res.kind = "MessageParse".into(), @@ -134,21 +129,18 @@ impl From for Error { } } -#[cfg(feature = "server")] -impl From for Error { - fn from(error: log::SetGlobalDefaultError) -> Self { - Self::Unexpected(error.to_string()) - } -} +// impl From for Error { +// fn from(error: tracing::SetGlobalDefaultError) -> Self { +// Self::Unexpected(error.to_string()) +// } +// } -#[cfg(feature = "server")] impl From for Error { fn from(error: which::Error) -> Self { Self::Unexpected(error.to_string()) } } -#[cfg(feature = "server")] impl From for Error { fn from(error: notify::Error) -> Self { Self::Unexpected(format!("Watcher: {error}")) @@ -166,7 +158,6 @@ impl IntoResult for Option { } } -#[cfg(feature = "server")] impl From for Error { fn from(e: simctl::Error) -> Self { Self::Run(match e { @@ -185,23 +176,8 @@ impl From for Error { } } -#[cfg(feature = "server")] impl From> for Error { fn from(v: tokio::sync::mpsc::error::SendError) -> Self { Self::SendError(format!("Channel closed, unable to send `{:?}`", v)) } } - -#[cfg(feature = "neovim")] -impl From for mlua::Error { - fn from(err: Error) -> Self { - Self::external(err) - } -} - -#[cfg(feature = "neovim")] -impl From for Error { - fn from(err: mlua::Error) -> Self { - Self::Unexpected(err.to_string()) - } -} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0343590 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,81 @@ +mod broadcast; +mod error; +mod project; +mod runner; +mod server; +mod state; +mod types; +mod util; +mod watcher; + +use {broadcast::*, error::*, project::*, runner::*, state::*, types::*, util::*, watcher::*}; + +static SOCK_ADDR: &str = "/tmp/xbase.socket"; +static PID_PATH: &str = "/tmp/xbase.pid"; +static LOG_PATH: &str = "/tmp/xbase.log"; + +/// Future that await and processes for os signals. +async fn handle_os_signals() -> Result<()> { + use futures::stream::StreamExt; + use signal_hook::consts::signal::*; + use signal_hook_tokio::Signals; + + let mut signals = Signals::new(&[SIGHUP, SIGTERM, SIGINT, SIGQUIT])?; + + while let Some(signal) = signals.next().await { + match signal { + SIGHUP => {} + SIGINT => { + tracing::warn!("SERVER STOPPED: Interruption Signal Received"); + break; + } + SIGQUIT => { + tracing::warn!("SERVER STOPPED: Quit Signal Received"); + break; + } + + SIGTERM => { + tracing::warn!("SERVER STOPPED: Termination Signal Received"); + break; + } + _ => unreachable!(), + } + } + Ok(()) +} + +#[tokio::main] +// TODO: store futures somewhere, to gracefully close connection to clients +async fn main() -> std::result::Result<(), Box> { + use fs::cleanup_daemon_runtime; + use tokio::fs::write; + use tokio::net::UnixListener; + use tokio::{pin, select}; + use tracing::info; + use tracing_setup::setup as tracing_setup; + + let os_signal_handler = tokio::spawn(handle_os_signals()); + + let listener = { + tracing_setup(LOG_PATH, tracing::Level::DEBUG, true)?; + cleanup_daemon_runtime(PID_PATH, SOCK_ADDR).await?; + write(PID_PATH, std::process::id().to_string()).await?; + UnixListener::bind(SOCK_ADDR).unwrap() + }; + + pin!(os_signal_handler); + info!("SERVER STARTED"); + + loop { + select! { + Ok((stream, _)) = listener.accept() => tokio::spawn(server::stream::handle(stream)), + _ = &mut os_signal_handler => break, + }; + } + + drop(listener); + + cleanup_daemon_runtime(PID_PATH, SOCK_ADDR).await?; + + Ok(()) +} diff --git a/daemon/src/project/barebone.rs b/src/project/barebone.rs similarity index 86% rename from daemon/src/project/barebone.rs rename to src/project/barebone.rs index 530e317..41dae1a 100644 --- a/daemon/src/project/barebone.rs +++ b/src/project/barebone.rs @@ -1,6 +1,5 @@ use super::*; -use crate::watch::Event; -use crate::{Error, Result}; +use crate::*; use serde::Serialize; use std::{collections::HashMap, path::PathBuf}; use xcodeproj::XCodeProject; @@ -71,7 +70,7 @@ impl ProjectCompile for BareboneProject { args.extend_from_slice(&["-project".into(), format!("{name}.xcodeproj")]); } - log::info!("xcodebuild {}", args.join(" ")); + broadcast.log_debug(format!("[{name}] xcodebuild {}", args.join(" "))); let xclogger = XCLogger::new(&root, &args)?; let xccommands = xclogger.compile_commands.clone(); let mut recv = broadcast.consume(Box::new(xclogger))?; @@ -92,7 +91,9 @@ impl ProjectGenerate for BareboneProject { } async fn generate(&mut self, _logger: &Arc) -> Result<()> { - log::error!("New File created or removed but generate barebone project is not supported"); + tracing::error!( + "New File created or removed but generate barebone project is not supported" + ); Ok(()) } @@ -110,7 +111,7 @@ impl Project for BareboneProject { let xcodeproj_paths = project.get_xcodeproj_paths()?; if xcodeproj_paths.len() > 1 { - log::warn!( + tracing::warn!( "Found more then on xcodeproj, using {:?}", xcodeproj_paths[0] ); @@ -125,18 +126,10 @@ impl Project for BareboneProject { .xcodeproj .targets_platform() .into_iter() - .map(|(k, platform)| { - ( - k, - TargetInfo { - platform, - watching: false, - }, - ) - }) + .map(|(k, platform)| (k, TargetInfo { platform })) .collect(); - log::info!("targets: {:?}", project.targets()); + tracing::info!("targets: {:?}", project.targets()); Ok(project) } } diff --git a/daemon/src/project/mod.rs b/src/project/mod.rs similarity index 87% rename from daemon/src/project/mod.rs rename to src/project/mod.rs index 0e153de..a0514f4 100644 --- a/daemon/src/project/mod.rs +++ b/src/project/mod.rs @@ -3,15 +3,13 @@ mod swift; mod tuist; mod xcodegen; -use crate::broadcast::Broadcast; -use crate::Result; -use crate::{device::*, run::*, util::*, watch::*}; +use crate::util::PathExt; +use crate::*; use anyhow::Context; +use once_cell::sync::Lazy; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::Arc; -use xbase_proto::{BuildSettings, TargetInfo}; -use xbase_proto::{PathExt, StatuslineState}; use xclog::{XCBuildSettings, XCLogger}; /// Project Data @@ -223,7 +221,6 @@ pub trait Project: event: Option<&Event>, broadcast: &Arc, ) -> Result<()> { - use crate::constants::BUILD_SERVER_CONFIG; use tokio::fs::File; use tokio::io::AsyncWriteExt; @@ -231,11 +228,40 @@ pub trait Project: let compile_path = root.join(".compile"); let is_swift_project = root.join("Package.swift").exists(); + /// Server Config + static BUILD_SERVER_CONFIG: Lazy> = Lazy::new(|| { + let mut sourcekit_helper_bin = dirs::home_dir().unwrap(); + sourcekit_helper_bin.extend([".local", "share", "xbase", "xbase-sourcekit-helper"]); + serde_json::json!({ + "name": "XBase", + "argv": [sourcekit_helper_bin], + "version": "0.3", + "bspVersion": "0.2", + "languages": ["swift", "objective-c", "objective-cpp", "c", "cpp"] + }) + .to_string() + .into_bytes() + }); + if !is_swift_project { let build_server_path = root.join("buildServer.json"); - if !build_server_path.exists() { + let build_server_file_exists = build_server_path.exists(); + + let outdated = if build_server_file_exists { + let content = tokio::fs::read(&build_server_path).await?; + serde_json::from_slice::(&content) + .unwrap() + .get("version") + .and_then(|v| v.as_str()) + .map(|v| v != "0.3") + .unwrap_or_default() + } else { + false + }; + + if !build_server_path.exists() || outdated { // NOTE: Use broadcast - log::info!("Creating {:?}", build_server_path); + tracing::info!("Creating {:?}", build_server_path); let mut build_server_file = File::create(build_server_path).await?; build_server_file.write_all(&BUILD_SERVER_CONFIG).await?; build_server_file.sync_all().await?; diff --git a/daemon/src/project/swift.rs b/src/project/swift.rs similarity index 94% rename from daemon/src/project/swift.rs rename to src/project/swift.rs index dfd48a3..150b024 100644 --- a/daemon/src/project/swift.rs +++ b/src/project/swift.rs @@ -1,5 +1,5 @@ use super::*; -use crate::watch::Event; +use crate::watcher::Event; use crate::{Error, Result}; use process_stream::Process; use serde::Serialize; @@ -93,7 +93,7 @@ impl ProjectRun for SwiftProject { let output = String::from_utf8(output.stdout).unwrap(); let bin_path = PathBuf::from(output.trim()).join(&cfg.target); - log::info!("Running {:?} via {bin_path:?}", self.name()); + tracing::info!("Running {:?} via {bin_path:?}", self.name()); Ok((Box::new(BinRunner::from_path(&bin_path)), args, recv)) } @@ -145,7 +145,7 @@ impl ProjectGenerate for SwiftProject { self.update_project_info().await?; - log::info!("(name: {:?}, targets: {:?})", self.name(), self.targets()); + tracing::info!("(name: {:?}, targets: {:?})", self.name(), self.targets()); Ok(()) } @@ -164,12 +164,12 @@ impl Project for SwiftProject { }; if !root.join(".build").exists() { - log::info!("no .build directory found at {root:?}"); + tracing::info!("no .build directory found at {root:?}"); project.generate(broadcast).await?; return Ok(project); } else { project.update_project_info().await?; - log::info!( + tracing::info!( "(name: {:?}, targets: {:?})", project.name(), project.targets() @@ -204,7 +204,7 @@ impl SwiftProject { .unwrap_or_default() .split("\n") .collect(); - log::error!("Fail to read swift package information {error}"); + tracing::error!("Fail to read swift package information {error}"); return Err(Error::DefinitionParsing(error)); }; @@ -233,7 +233,6 @@ impl SwiftProject { name, TargetInfo { platform: PBXTargetPlatform::MacOS, - watching: false, }, )) } else { diff --git a/daemon/src/project/tuist.rs b/src/project/tuist.rs similarity index 90% rename from daemon/src/project/tuist.rs rename to src/project/tuist.rs index 18fa38f..c9287c8 100644 --- a/daemon/src/project/tuist.rs +++ b/src/project/tuist.rs @@ -1,6 +1,6 @@ use super::*; use crate::util::fs::which; -use crate::watch::Event; +use crate::watcher::Event; use crate::{Error, Result}; use futures::StreamExt; use process_stream::{Process, ProcessExt}; @@ -76,7 +76,7 @@ impl ProjectCompile for TuistProject { "Manifests".into(), ]); - log::debug!("\n\nxcodebuild {}\n", arguments.join(" ")); + broadcast.log_debug(format!("[{name}] xcodebuild {}", arguments.join(" "))); let xclogger = XCLogger::new(&root, &arguments)?; let xccommands = xclogger.compile_commands.clone(); @@ -96,7 +96,7 @@ impl ProjectCompile for TuistProject { format!("{name}"), ]); - log::debug!("\n\nxcodebuild {}\n", arguments.join(" ")); + broadcast.log_debug(format!("[{name}] xcodebuild {}", arguments.join(" "))); let xclogger = XCLogger::new(&root, &arguments)?; let xccommands = xclogger.compile_commands.clone(); @@ -122,7 +122,7 @@ impl ProjectCompile for TuistProject { #[async_trait::async_trait] impl ProjectGenerate for TuistProject { fn should_generate(&self, event: &Event) -> bool { - log::trace!("manifest files {:?}", self.manifest_files); + tracing::trace!("manifest files {:?}", self.manifest_files); let is_config_file = self.manifest_files.contains(event.file_name()); let is_content_update = event.is_content_update_event(); let is_config_file_update = is_content_update && is_config_file; @@ -155,13 +155,7 @@ impl ProjectGenerate for TuistProject { let info = self.targets.get_mut(&key).unwrap(); info.platform = platform; } else { - self.targets.insert( - key, - TargetInfo { - platform, - watching: false, - }, - ); + self.targets.insert(key, TargetInfo { platform }); } } @@ -178,7 +172,7 @@ impl TuistProject { let (mut xcodeproj, mut manifest) = (None, None); if paths.len() > 2 { - log::warn!( + tracing::warn!( "Expected `2` xcodeproj Manifest and Main but found `{}`", paths.len() ) @@ -255,11 +249,11 @@ impl Project for TuistProject { (a.unwrap(), b.unwrap()) } _ => { - log::info!("no xcodeproj found at {root:?}"); + tracing::info!("no xcodeproj found at {root:?}"); project.generate(broadcast).await?; - log::info!("[{}] targets: {:?}", project.name(), project.targets()); + tracing::info!("[{}] targets: {:?}", project.name(), project.targets()); return Ok(project); } @@ -275,18 +269,10 @@ impl Project for TuistProject { .xcodeproj .targets_platform() .into_iter() - .map(|(k, platform)| { - ( - k, - TargetInfo { - platform, - watching: false, - }, - ) - }) + .map(|(k, platform)| (k, TargetInfo { platform })) .collect(); - log::info!("[{}] targets: {:?}", project.name(), project.targets()); + tracing::info!("[{}] targets: {:?}", project.name(), project.targets()); Ok(project) } diff --git a/daemon/src/project/xcodegen.rs b/src/project/xcodegen.rs similarity index 86% rename from daemon/src/project/xcodegen.rs rename to src/project/xcodegen.rs index f5ccbf9..a04a033 100644 --- a/daemon/src/project/xcodegen.rs +++ b/src/project/xcodegen.rs @@ -1,6 +1,6 @@ use super::*; use crate::util::fs::which; -use crate::watch::Event; +use crate::watcher::Event; use crate::Result; use futures::StreamExt; use process_stream::{Process, ProcessExt}; @@ -51,14 +51,14 @@ impl ProjectCompile for XCodeGenProject { use xclog::XCCompilationDatabase as CC; let root = self.root(); + let name = self.root().name().unwrap(); let cache_root = self.build_cache_root()?; let mut arguments = self.compile_arguments(); self.on_compile_start(broadcast)?; arguments.push(format!("SYMROOT={cache_root}")); - - log::debug!("\n\nxcodebuild {}\n", arguments.join(" ")); + broadcast.log_debug(format!("[{name}] xcodebuild {}", arguments.join(" "))); let xclogger = XCLogger::new(&root, &arguments)?; let compile_commands = xclogger.compile_commands.clone(); @@ -116,7 +116,7 @@ impl ProjectGenerate for XCodeGenProject { if xcodeproj_paths.len() > 1 { let using = xcodeproj_paths[0].display(); - log::warn!("[{name}] Found more then on xcodeproj, using {using}",); + tracing::warn!("[{name}] Found more then on xcodeproj, using {using}",); } self.xcodeproj = XCodeProject::new(&xcodeproj_paths[0])?; @@ -125,13 +125,7 @@ impl ProjectGenerate for XCodeGenProject { let info = self.targets.get_mut(&key).unwrap(); info.platform = platform; } else { - self.targets.insert( - key, - TargetInfo { - platform, - watching: false, - }, - ); + self.targets.insert(key, TargetInfo { platform }); } } @@ -155,7 +149,7 @@ impl Project for XCodeGenProject { let xcodeproj_paths = project.get_xcodeproj_paths()?; if xcodeproj_paths.len() > 1 { - log::warn!( + tracing::warn!( "Found more then on xcodeproj, using {:?}", xcodeproj_paths[0] ); @@ -167,21 +161,13 @@ impl Project for XCodeGenProject { .xcodeproj .targets_platform() .into_iter() - .map(|(k, platform)| { - ( - k, - TargetInfo { - platform, - watching: false, - }, - ) - }) + .map(|(k, platform)| (k, TargetInfo { platform })) .collect(); } else { project.generate(broadcast).await?; } - log::info!("[{}] targets: {:?}", project.name(), project.targets()); + tracing::info!("[{}] targets: {:?}", project.name(), project.targets()); Ok(project) } } diff --git a/daemon/src/run/bin.rs b/src/runner/bin.rs similarity index 96% rename from daemon/src/run/bin.rs rename to src/runner/bin.rs index d329a35..bc1c0a6 100644 --- a/daemon/src/run/bin.rs +++ b/src/runner/bin.rs @@ -1,4 +1,4 @@ -use crate::run::Broadcast; +use super::Broadcast; use crate::{Error, Result}; use process_stream::Process; use std::path::{Path, PathBuf}; diff --git a/daemon/src/device.rs b/src/runner/device.rs similarity index 100% rename from daemon/src/device.rs rename to src/runner/device.rs diff --git a/daemon/src/run/handler.rs b/src/runner/handler.rs similarity index 89% rename from daemon/src/run/handler.rs rename to src/runner/handler.rs index 82f5553..10e6a41 100644 --- a/daemon/src/run/handler.rs +++ b/src/runner/handler.rs @@ -1,12 +1,9 @@ -use crate::broadcast::Broadcast; -use crate::watch::WatchService; -use crate::Result; +use crate::*; use process_stream::ProcessExt; use process_stream::{Process, StreamExt}; use std::sync::Weak; use tokio::sync::Mutex; use tokio::task::JoinHandle; -use xbase_proto::StatuslineState; /// Run Service Task Handler pub struct RunServiceHandler { @@ -27,7 +24,7 @@ impl RunServiceHandler { let mut stream = process.spawn_and_stream()?; let abort = process.aborter().unwrap(); - let inner = tokio::spawn(async move { + let inner: _ = tokio::spawn(async move { // TODO: find a better way to close this! // // Right now it just wait till the user try print something @@ -35,7 +32,7 @@ impl RunServiceHandler { let ref mut broadcast = match broadcast.upgrade() { Some(broadcast) => broadcast, None => { - log::warn!("No client instance listening, closing runner .."); + tracing::warn!("No client instance listening, closing runner .."); if let Some(watcher) = watcher.upgrade() { watcher.lock().await.listeners.remove(&key); } @@ -65,7 +62,7 @@ impl RunServiceHandler { broadcast.update_statusline(StatuslineState::Failure); } - log::info!("[{target}] Runner Closed"); + tracing::info!("[{target}] Runner Closed"); break; } }; diff --git a/src/runner/mod.rs b/src/runner/mod.rs new file mode 100644 index 0000000..b4a3a64 --- /dev/null +++ b/src/runner/mod.rs @@ -0,0 +1,50 @@ +mod bin; +mod device; +mod handler; +mod service; +mod simulator; + +use crate::*; +use async_trait::async_trait; +use process_stream::Process; +use std::sync::Arc; +use tokio::sync::OwnedMutexGuard; + +pub use service::RunService; +pub use {bin::*, device::*, service::*, simulator::*}; + +#[async_trait] +pub trait Runner { + async fn run<'a>(&self, broadcast: &Broadcast) -> Result; +} + +pub async fn get_runner<'a>( + project: &mut OwnedMutexGuard, + settings: &BuildSettings, + device: Option<&Device>, + _is_once: bool, + broadcast: &Arc, +) -> Result { + let target = &settings.target; + let device_name = device.map(|d| d.to_string()).unwrap_or("macOs".into()); + + broadcast.info(format!("[{target}({device_name})] Running ⚙")); + + let (runner, args, mut recv) = project.get_runner(&settings, device, broadcast)?; + + broadcast.update_statusline(StatuslineState::Processing); + + if !recv.recv().await.unwrap_or_default() { + let msg = format!("[{target}] Failed to build for running "); + broadcast.error(&msg); + broadcast.log_error(format!("[{target}] xcodebuild {}", args.join(" "))); + broadcast.open_logger(); + return Err(crate::Error::Run(msg)); + } + + let process = runner.run(broadcast).await?; + + broadcast.update_statusline(StatuslineState::Running); + + Ok(process) +} diff --git a/daemon/src/run/service.rs b/src/runner/service.rs similarity index 83% rename from daemon/src/run/service.rs rename to src/runner/service.rs index d7a7e52..5c354d7 100644 --- a/daemon/src/run/service.rs +++ b/src/runner/service.rs @@ -1,19 +1,9 @@ -use super::handler::RunServiceHandler; -use crate::broadcast::Broadcast; -use crate::project::ProjectImplementer; -use crate::run::get_runner; -use crate::store::devices; -use crate::watch::WatchService; -use crate::{ - device::Device, - watch::{Event, Watchable}, - Result, -}; +use super::{handler::RunServiceHandler, *}; +use crate::*; use std::path::PathBuf; use std::sync::{Arc, Weak}; use tap::Pipe; use tokio::sync::{Mutex, OwnedMutexGuard}; -use xbase_proto::{BuildSettings, RunRequest}; /// Run Service pub struct RunService { @@ -24,30 +14,21 @@ pub struct RunService { pub device: Option, } -impl std::fmt::Display for RunService { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.key) - } -} - impl RunService { pub async fn new( - project: &mut OwnedMutexGuard, - req: RunRequest, + key: String, + root: PathBuf, + settings: BuildSettings, + device: DeviceLookup, + operation: Operation, broadcast: &Arc, watcher: Weak>, + project: &mut OwnedMutexGuard, ) -> Result { let weak_logger = Arc::downgrade(&broadcast); - let key = req.to_string(); - let RunRequest { - root, - settings, - device, - .. - } = req; let target = &settings.target; let device = devices().from_lookup(device); - let is_once = req.ops.is_once(); + let is_once = operation.is_once(); let process = get_runner(project, &settings, device.as_ref(), is_once, &broadcast).await?; @@ -65,6 +46,12 @@ impl RunService { } } +impl std::fmt::Display for RunService { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.key) + } +} + #[async_trait::async_trait] impl Watchable for RunService { async fn trigger( diff --git a/daemon/src/run/simulator.rs b/src/runner/simulator.rs similarity index 92% rename from daemon/src/run/simulator.rs rename to src/runner/simulator.rs index 042891e..fc3be29 100644 --- a/daemon/src/run/simulator.rs +++ b/src/runner/simulator.rs @@ -1,13 +1,9 @@ -use crate::broadcast::Broadcast; -use crate::device::Device; -use crate::run::Runner; -use crate::util::{fmt, pid}; -use crate::{Error, Result}; +use super::*; +use crate::*; use process_stream::Process; use std::path::PathBuf; use tap::Pipe; use tokio::process::Command; -use xbase_proto::DeviceLookup; use xclog::XCBuildSettings; /// Simulator Device runner @@ -37,7 +33,7 @@ impl SimulatorRunner { } pub async fn boot<'a>(&self, broadcast: &Broadcast) -> Result<()> { - match pid::get_by_name("Simulator") { + match pid::get_pid_by_name("Simulator") { Err(Error::Lookup(_, _)) => { let msg = format!("[Simulator] Launching"); broadcast.log_info(msg); @@ -47,7 +43,7 @@ impl SimulatorRunner { .wait() .await?; broadcast.log_info("[Simulator] Connected"); - broadcast.log_info(format!("{}", fmt::separator())); + broadcast.log_info(format!("{}", util::fmt::separator())); } Err(err) => { let msg = err.to_string(); @@ -91,7 +87,7 @@ impl SimulatorRunner { process.args(args); broadcast.log_info(self.connected_msg()); - broadcast.log_info(fmt::separator()); + broadcast.log_info(util::fmt::separator()); Ok(process) } diff --git a/daemon/src/build.rs b/src/server/build.rs similarity index 57% rename from daemon/src/build.rs rename to src/server/build.rs index 053ab75..1a5ef1c 100644 --- a/daemon/src/build.rs +++ b/src/server/build.rs @@ -1,42 +1,57 @@ -use crate::broadcast::Broadcast; -use crate::project::ProjectImplementer; -use crate::store::TryGetDaemonObject; -use crate::watch::{Event, WatchService, Watchable}; -use crate::Result; +use super::*; +use crate::*; use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::path::PathBuf; use std::sync::{Arc, Weak}; use tokio::sync::{Mutex, OwnedMutexGuard}; -use xbase_proto::{BuildRequest, StatuslineState}; -/// Handle build Request -pub async fn handle(req: BuildRequest) -> Result<()> { - let broadcast = req.root.try_get_broadcast().await?; - let target = &req.settings.target; - // let args = &req.settings.to_string(); +/// Request to build a particular project +#[derive(Debug, Serialize, Deserialize)] +pub struct BuildRequest { + pub root: PathBuf, + pub settings: BuildSettings, + pub operation: Operation, +} - log::trace!("{:#?}", req); +impl fmt::Display for BuildRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:Build:{}", self.root.display(), self.settings) + } +} - if req.ops.is_once() { - let mut project = req.root.try_get_project().await?; +#[async_trait] +impl RequestHandler<()> for BuildRequest { + async fn handle(self) -> Result<()> { + let broadcast = self.root.try_get_broadcast().await?; + let target = &self.settings.target; + // let args = &self.settings.to_string(); - req.trigger(&mut project, &Event::default(), &broadcast, Weak::new()) - .await?; - return Ok(()); - } + tracing::trace!("{:#?}", self); - let mut watcher = req.root.try_get_watcher().await?; + if self.operation.is_once() { + let mut project = self.root.try_get_project().await?; - if req.ops.is_watch() { - broadcast.success(format!("[{target}] Watching ")); - broadcast.update_statusline(StatuslineState::Watching); - watcher.add(req)?; - } else { - broadcast.info(format!("[{}] Wathcer Stopped", &req.settings.target)); - watcher.remove(&req.to_string())?; - broadcast.update_statusline(StatuslineState::Clear); - } + self.trigger(&mut project, &Event::default(), &broadcast, Weak::new()) + .await?; + return Ok(()); + } + + let mut watcher = self.root.try_get_watcher().await?; - Ok(()) + if self.operation.is_watch() { + broadcast.success(format!("[{target}] Watching ")); + broadcast.update_statusline(StatuslineState::Watching); + watcher.add(self)?; + } else { + broadcast.info(format!("[{}] Wathcer Stopped", &self.settings.target)); + watcher.remove(&self.to_string())?; + broadcast.update_statusline(StatuslineState::Clear); + } + + Ok(()) + } } #[async_trait] @@ -49,7 +64,7 @@ impl Watchable for BuildRequest { _watcher: Weak>, ) -> Result<()> { broadcast.update_statusline(StatuslineState::Processing); - let is_once = self.ops.is_once(); + let is_once = self.operation.is_once(); let config = &self.settings; let target = &self.settings.target; diff --git a/src/server/drop.rs b/src/server/drop.rs new file mode 100644 index 0000000..281f82b --- /dev/null +++ b/src/server/drop.rs @@ -0,0 +1,52 @@ +use super::*; +use crate::*; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use std::sync::Arc; + +#[derive(Debug, Serialize, Deserialize)] +pub struct DropRequest { + roots: Vec, +} + +#[async_trait] +impl RequestHandler<()> for DropRequest { + async fn handle(self) -> Result<()> { + let DropRequest { roots } = self; + let mut watchers = watchers().await; + let mut broadcasters = broadcasters().await; + let mut projects = projects().await; + + for root in roots.into_iter() { + let mut project = if let Some(project) = projects.get(&root).map(Arc::clone) { + project.lock_owned().await + } else { + continue; + }; + + // Remove client pid from project. + project.dec_clients(); + + // Remove project only when no more client using that data. + if project.clients() == &0 { + let key = root.as_path().abbrv()?.display(); + tracing::info!("[{key}] project removed"); + projects.remove(&root); + + if let Some(watcher) = watchers.get(&root) { + watcher.lock().await.handler.abort(); + watchers.remove(&root); + tracing::info!("[{key}] watcher removed"); + }; + + broadcasters.remove(&root).map(|l| { + l.abort(); + }); + tracing::info!("[{}] Dropped", root.as_path().name().unwrap()); + } + } + + Ok(()) + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 0000000..b82ff99 --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,19 @@ +mod build; +mod drop; +mod project_info; +mod register; +mod request; +mod response; +mod run; +mod runners; +pub mod stream; + +use { + build::*, drop::*, project_info::*, register::*, request::*, response::*, run::*, runners::*, +}; + +/// Trait that must be implemented by All Request members +#[async_trait::async_trait] +pub trait RequestHandler { + async fn handle(self) -> crate::Result; +} diff --git a/src/server/project_info.rs b/src/server/project_info.rs new file mode 100644 index 0000000..b73d353 --- /dev/null +++ b/src/server/project_info.rs @@ -0,0 +1,32 @@ +use super::*; +use crate::*; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::path::PathBuf; + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetProjectInfoRequest { + root: PathBuf, +} + +#[derive(Debug, Serialize)] +pub struct ProjectInfo { + /// Get watched configurations for given root + watchlist: Vec, + /// Get targets information for a registers project with a given root + targets: HashMap, +} + +#[async_trait] +impl RequestHandler for GetProjectInfoRequest { + async fn handle(self) -> Result { + let listeners = &self.root.try_get_watcher().await?.listeners; + let project = self.root.try_get_project().await?; + + Ok(ProjectInfo { + watchlist: listeners.iter().map(|(k, _)| k.clone()).collect(), + targets: project.targets().clone(), + }) + } +} diff --git a/src/server/register.rs b/src/server/register.rs new file mode 100644 index 0000000..4222966 --- /dev/null +++ b/src/server/register.rs @@ -0,0 +1,72 @@ +use super::*; +use crate::*; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use std::sync::Arc; +use tokio::sync::Mutex; + +#[derive(Debug, Serialize, Deserialize)] +pub struct RegisterRequest { + root: PathBuf, +} + +#[async_trait] +impl RequestHandler for RegisterRequest { + /// Handle RegisterRequest + async fn handle(self) -> Result { + let RegisterRequest { root } = self; + let name = root.as_path().name().unwrap(); + tracing::info!("[{name}] Registering"); + + let (broadcast, logger_path) = if let Ok(broadcast) = root.try_get_broadcast().await { + (broadcast.clone(), broadcast.address().clone()) + } else { + let broadcast = Broadcast::new(&root).await.map(Arc::new)?; + let address = broadcast.address().clone(); + broadcasters().await.insert(root.clone(), broadcast.clone()); + (broadcast, address) + }; + + tokio::spawn(async move { + let mut projects = projects().await; + + if let Some(project) = projects.get(&root).map(Arc::clone) { + let mut project = project.lock_owned().await; + project.inc_clients(); + // NOTE: this doesn't make sense! + project.ensure_server_support(None, &broadcast).await?; + tracing::info!("[{name}] Using existing instance"); + return Ok(()); + } + let project = project(&root, &broadcast).await?; + let name = project.name().to_string(); + let root = project.root().clone(); + let ignore = project.watchignore().clone(); + + let project = Arc::new(Mutex::new(project)); + let handler = WatchService::new( + &root, + ignore, + Arc::downgrade(&broadcast), + Arc::downgrade(&project), + ) + .await?; + + tracing::info!("[{name}] Registered"); + + projects.insert(root.clone(), project.clone()); + watchers() + .await + .insert(root.clone(), Arc::new(Mutex::new(handler))); + project + .lock() + .await + .ensure_server_support(None, &broadcast) + .await?; + Ok::<_, Error>(()) + }); + + Ok(logger_path) + } +} diff --git a/src/server/request.rs b/src/server/request.rs new file mode 100644 index 0000000..323286d --- /dev/null +++ b/src/server/request.rs @@ -0,0 +1,34 @@ +use super::*; +use serde::{Deserialize, Serialize}; +use tap::Pipe; + +/// All the requests that xbase can handle +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "method", rename_all = "snake_case")] +pub enum Request { + /// Register project root and get broadcaster reader file description + Register(RegisterRequest), + /// Build Project and get path to where to build log will be located + Build(BuildRequest), + /// Run Project and get path to where to Runtime log will be located + Run(RunRequest), + /// Drop projects at a given roots + Drop(DropRequest), + /// Get available runners + GetRunners(GetRunnersRequest), + /// Get project info that might change between calls, like targets or watchlist + GetProjectInfo(GetProjectInfoRequest), +} + +impl Request { + pub async fn handle(self) -> Response { + match self { + Request::Register(req) => req.handle().await.pipe(Response::new), + Request::Build(req) => req.handle().await.pipe(Response::new), + Request::Run(req) => req.handle().await.pipe(Response::new), + Request::Drop(req) => req.handle().await.pipe(Response::new), + Request::GetRunners(req) => req.handle().await.pipe(Response::new), + Request::GetProjectInfo(req) => req.handle().await.pipe(Response::new), + } + } +} diff --git a/src/server/response.rs b/src/server/response.rs new file mode 100644 index 0000000..1b3da92 --- /dev/null +++ b/src/server/response.rs @@ -0,0 +1,22 @@ +use crate::{error::Error, types::Result}; +use serde::Serialize; + +/// Server Response +#[derive(Default, Debug, Serialize)] +pub struct Response { + #[serde(skip_serializing_if = "Option::is_none")] + data: Option, + #[serde(skip_serializing_if = "Option::is_none")] + error: Option, +} + +impl Response { + pub fn new(v: Result) -> Response { + let mut response = Response::default(); + match v { + Ok(data) => response.data = serde_json::to_value(data).unwrap().into(), + Err(error) => response.error = error.into(), + }; + response + } +} diff --git a/src/server/run.rs b/src/server/run.rs new file mode 100644 index 0000000..bd1228d --- /dev/null +++ b/src/server/run.rs @@ -0,0 +1,97 @@ +use super::*; +use crate::{runner::*, *}; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use std::sync::Arc; +use std::{fmt, sync::Weak}; +use tap::Pipe; +use tokio::sync::{Mutex, OwnedMutexGuard}; + +/// Request to Run a particular project. +#[derive(Debug, Serialize, Deserialize)] +pub struct RunRequest { + pub root: PathBuf, + pub settings: BuildSettings, + #[serde(deserialize_with = "util::de::value_or_default")] + pub device: DeviceLookup, + #[serde(deserialize_with = "util::de::value_or_default")] + pub operation: Operation, +} + +impl fmt::Display for RunRequest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let root = self.root.display(); + let device = if let Some(name) = self.device.name.as_ref() { + name.to_string() + } else { + "Bin".into() + }; + let settings = &self.settings; + write!(f, "{root}:Run:{device}:{settings}",) + } +} + +#[async_trait] +impl RequestHandler<()> for RunRequest { + async fn handle(self) -> Result<()> { + tracing::trace!("{:#?}", self); + + let ref key = self.to_string(); + let broadcast = self.root.try_get_broadcast().await?; + let mut project = self.root.try_get_project().await?; + + let watcher = self.root.try_get_mutex_watcher().await?; + let weak_watcher = Arc::downgrade(&watcher); + + if self.operation.is_once() { + // TODO(run): might want to keep track of ran services + // RunService::new(&mut project, self, &broadcast, weak_watcher).await?; + self.into_service(&broadcast, weak_watcher, &mut project) + .await?; + + return Ok(Default::default()); + } + + let mut watcher = watcher.lock().await; + + if self.operation.is_watch() { + broadcast.update_statusline(StatuslineState::Watching); + if watcher.contains_key(key) { + broadcast.warn(format!("Already watching with {key}!!")); + } else { + self.into_service(&broadcast, weak_watcher, &mut project) + .await? + .pipe(|s| watcher.add(s))?; + } + } else { + let listener = watcher.remove(&self.to_string())?; + listener.discard().await?; + broadcast.info(format!("[{}] Watcher Stopped", &self.settings.target)); + broadcast.update_statusline(StatuslineState::Clear); + } + + Ok(()) + } +} + +impl RunRequest { + async fn into_service( + self, + broadcast: &Arc, + watcher: Weak>, + project: &mut OwnedMutexGuard, + ) -> Result { + RunService::new( + self.to_string(), + self.root, + self.settings, + self.device, + self.operation, + broadcast, + watcher, + project, + ) + .await + } +} diff --git a/src/server/runners.rs b/src/server/runners.rs new file mode 100644 index 0000000..d016f34 --- /dev/null +++ b/src/server/runners.rs @@ -0,0 +1,37 @@ +use super::*; +use crate::*; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use tap::Pipe; +use xcodeproj::pbxproj::PBXTargetPlatform; + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetRunnersRequest; + +#[async_trait] +impl RequestHandler>>> for GetRunnersRequest { + async fn handle(self) -> Result>>> { + let devices = devices(); + vec![ + PBXTargetPlatform::IOS, + PBXTargetPlatform::WatchOS, + PBXTargetPlatform::TvOS, + ] + .into_iter() + .map(|p| { + ( + p.to_string(), + devices + .iter() + .filter(|(_, d)| d.platform == p) + .map(|(id, d)| { + HashMap::from([("id".into(), id.into()), ("name".into(), d.name.clone())]) + }) + .collect::>>(), + ) + }) + .collect::>() + .pipe(Ok) + } +} diff --git a/src/server/stream.rs b/src/server/stream.rs new file mode 100644 index 0000000..51ccf0a --- /dev/null +++ b/src/server/stream.rs @@ -0,0 +1,50 @@ +use super::*; +use tokio::net::unix::{ReadHalf, WriteHalf}; +use tokio_serde::{formats::*, SymmetricallyFramed}; +use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite}; + +/// Generic over T and Framed Write/Read +type F = SymmetricallyFramed>; + +/// Stream of Requests to read Requests from +struct RequestStream; + +impl RequestStream { + fn new<'a>(r: ReadHalf<'a>) -> F, BytesCodec>> { + let transport = FramedRead::new(r, BytesCodec::default()); + F::new(transport, SymmetricalJson::default()) + } +} + +/// Stream of Responses to write response to +struct ResponseStream; + +impl ResponseStream { + fn new<'a>(w: WriteHalf<'a>) -> F, BytesCodec>> { + let transport = FramedWrite::new(w, BytesCodec::default()); + F::new(transport, SymmetricalJson::default()) + } +} + +/// Future that await and process client requests. +pub async fn handle(mut stream: tokio::net::UnixStream) { + use futures::{FutureExt, SinkExt, TryStreamExt}; + use tracing::{error, info}; + + info!("Handling a new client"); + + let (reader, writer) = stream.split(); + let (mut reader, mut writer) = (RequestStream::new(reader), ResponseStream::new(writer)); + + loop { + match reader.try_next().await { + Ok(Some(request)) => { + let send_result = request.handle().then(|res| writer.send(res)).await; + send_result.map_err(|err| error!("Send Error: {err}")).ok(); + } + Err(err) => error!("Read Error: {err:#?}"), + Ok(None) => break, + } + } + info!("Disconnecting a client"); +} diff --git a/daemon/src/store.rs b/src/state.rs similarity index 94% rename from daemon/src/store.rs rename to src/state.rs index 32ac051..c4f3f24 100644 --- a/daemon/src/store.rs +++ b/src/state.rs @@ -1,14 +1,10 @@ -use crate::broadcast::Broadcast; -use crate::project::ProjectImplementer; -use crate::run::Devices; -use crate::watch::WatchService; -use crate::Result; +use crate::runner::Devices; +use crate::*; use once_cell::sync::Lazy; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::Arc; use tokio::sync::{Mutex, OwnedMutexGuard}; -use xbase_proto::IntoResult; type ArcMutex = Arc>; type LazyArcMutex = Lazy>>; diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..7bd1a81 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,103 @@ +use crate::error::*; +use serde::{Deserialize, Serialize}; +use std::fmt::Display; +use strum::{Display as EnumDisplay, EnumString}; +use xcodeproj::pbxproj::PBXTargetPlatform; + +pub type Result = std::result::Result; + +/// Build Configuration to run +#[derive(Clone, Debug, Serialize, Deserialize, EnumDisplay, EnumString)] +pub enum BuildConfiguration { + Debug, + Release, + Custom(String), +} + +/// Operation +/// +/// Should request be executed once, stoped (if watched) or start new watch service? +#[derive(Clone, Debug, Serialize, Deserialize, EnumDisplay, EnumString)] +pub enum Operation { + Watch, + Stop, + Once, +} + +/// Fields required to build/run a project +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct BuildSettings { + /// Target to build + pub target: String, + /// Configuration to build with, default Debug + pub configuration: BuildConfiguration, + /// Scheme to build with + pub scheme: Option, +} + +/// Target specfic information +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TargetInfo { + /// Configuration to build with, default Debug + pub platform: PBXTargetPlatform, +} + +/// Device Lookup information to run built project with +#[derive(Clone, Default, Debug, Serialize, Deserialize)] +pub struct DeviceLookup { + pub name: Option, + pub id: Option, +} + +impl Default for Operation { + fn default() -> Self { + Self::Once + } +} + +impl Display for BuildSettings { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "-configuration {}", self.configuration)?; + + if let Some(ref scheme) = self.scheme { + write!(f, " -scheme {scheme}")?; + } + write!(f, " -target {}", self.target)?; + Ok(()) + } +} +impl BuildSettings { + pub fn to_args(&self) -> Vec { + self.to_string() + .split_whitespace() + .map(ToString::to_string) + .collect::>() + } +} + +impl Operation { + /// Returns `true` if the request kind is [`Watch`]. + /// + /// [`Watch`]: RequestKind::Watch + #[must_use] + pub fn is_watch(&self) -> bool { + matches!(self, Self::Watch) + } + + /// Returns `true` if the request kind is [`WatchStop`]. + /// + /// [`WatchStop`]: RequestKind::WatchStop + #[must_use] + #[allow(dead_code)] + pub fn is_stop(&self) -> bool { + matches!(self, Self::Stop) + } + + /// Returns `true` if the request kind is [`Once`]. + /// + /// [`Once`]: RequestKind::Once + #[must_use] + pub fn is_once(&self) -> bool { + matches!(self, Self::Once) + } +} diff --git a/src/util/de.rs b/src/util/de.rs new file mode 100644 index 0000000..39f5c2b --- /dev/null +++ b/src/util/de.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Deserializer}; + +/// Deserialize an optional type to default if none +pub fn value_or_default<'de, D, T>(d: D) -> Result +where + D: Deserializer<'de>, + T: Default + Deserialize<'de>, +{ + Deserialize::deserialize(d).map(|x: Option<_>| x.unwrap_or_default()) +} diff --git a/proto/src/util.rs b/src/util/extensions.rs similarity index 76% rename from proto/src/util.rs rename to src/util/extensions.rs index 7cf7b10..1baae4a 100644 --- a/proto/src/util.rs +++ b/src/util/extensions.rs @@ -1,16 +1,6 @@ use crate::{Error, Result}; -use serde::{Deserialize, Deserializer}; use std::path::Path; -/// Deserialize an optional type to default if none -pub fn value_or_default<'de, D, T>(d: D) -> Result -where - D: Deserializer<'de>, - T: Default + Deserialize<'de>, -{ - Deserialize::deserialize(d).map(|x: Option<_>| x.unwrap_or_default()) -} - pub trait PathExt { fn name(&self) -> Option; fn unique_name(&self) -> Option; diff --git a/daemon/src/util/fmt.rs b/src/util/fmt.rs similarity index 95% rename from daemon/src/util/fmt.rs rename to src/util/fmt.rs index 2edeae4..7c8fb1c 100644 --- a/daemon/src/util/fmt.rs +++ b/src/util/fmt.rs @@ -1,3 +1,4 @@ +#[allow(dead_code)] pub fn as_section(content: String) -> String { let empty_string = content.is_empty(); let mut content = format!("[{content}]"); diff --git a/daemon/src/util/fs.rs b/src/util/fs.rs similarity index 84% rename from daemon/src/util/fs.rs rename to src/util/fs.rs index b895e59..21dafed 100644 --- a/daemon/src/util/fs.rs +++ b/src/util/fs.rs @@ -1,8 +1,9 @@ -//! Functions to query filesystem for files and directories +//! Functions to query/access filesystem +use crate::BuildSettings; use anyhow::Result; use std::{fmt::Debug, path::Path}; use tap::Pipe; -use xbase_proto::BuildSettings; +use tokio::fs; pub fn get_dirname_dir_root(path: impl AsRef) -> Option { let path = path.as_ref(); @@ -14,6 +15,21 @@ pub fn get_dirname_dir_root(path: impl AsRef) -> Option { .pipe(Some) } +/// Ensure single socket server and process running +pub async fn cleanup_daemon_runtime(pid_path: &'static str, sock_addr: &'static str) -> Result<()> { + if fs::metadata(sock_addr).await.ok().is_some() { + fs::remove_file(sock_addr).await.ok(); + if fs::metadata(pid_path).await.ok().is_some() { + fs::read_to_string(pid_path) + .await? + .pipe_ref(super::pid::kill_process_by_pid) + .await?; + fs::remove_file(pid_path).await.ok(); + } + } + Ok(()) +} + pub fn _get_build_cache_dir + Debug>( root_path: P, config: Option<&BuildSettings>, @@ -113,17 +129,3 @@ fn test_gitignore_patterns() { println!("{gitignore_patterns:#?}"); } -pub fn abbrv_path>(path: P) -> String { - let abbr = || { - let path = path.as_ref(); - - Some( - path.strip_prefix(path.ancestors().nth(3)?) - .ok()? - .display() - .to_string(), - ) - }; - - abbr().unwrap_or_default() -} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..87bfc7e --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,9 @@ +//! General utilities + +pub mod de; +pub mod extensions; +pub mod fmt; +pub mod fs; +pub mod pid; +pub mod tracing_setup; +pub use extensions::*; diff --git a/daemon/src/util/pid.rs b/src/util/pid.rs similarity index 78% rename from daemon/src/util/pid.rs rename to src/util/pid.rs index f6ecc1d..01ea272 100644 --- a/daemon/src/util/pid.rs +++ b/src/util/pid.rs @@ -1,9 +1,9 @@ use std::{ffi::OsStr, fmt::Display, string::String}; /// Kill process using kill command -pub async fn kill(pid_str: &String) -> anyhow::Result { +pub async fn kill_process_by_pid(pid_str: &String) -> anyhow::Result { Ok(tokio::process::Command::new("kill") - .arg("-9") + .arg("-15") .arg(pid_str) .output() .await? @@ -11,8 +11,9 @@ pub async fn kill(pid_str: &String) -> anyhow::Result { .success()) } -/// check if process exists -pub fn exists(pid: &i32, cb: impl FnOnce()) -> bool { +/// Check if a process exists with a given pid +#[allow(dead_code)] +pub fn is_valid_pid(pid: &i32, cb: impl FnOnce()) -> bool { if libproc::libproc::proc_pid::name(*pid).is_err() { cb(); false @@ -27,7 +28,7 @@ pub fn exists(pid: &i32, cb: impl FnOnce()) -> bool { /// otherwise process with given name is not, an Error::Lookup will be returned. /// /// WARNNING: The first match will be returned, and duplicates will be ignored -pub fn get_by_name(name: S) -> crate::Result +pub fn get_pid_by_name(name: S) -> crate::Result where S: AsRef + Display, String: PartialEq, @@ -49,8 +50,8 @@ where #[test] fn test_get_by_name() { - let existing_process = get_by_name("DockHelper"); - let not_process = get_by_name("afsd8439f"); + let existing_process = get_pid_by_name("DockHelper"); + let not_process = get_pid_by_name("afsd8439f"); assert!(existing_process.is_ok()); assert!(not_process.is_err()); diff --git a/src/util/tracing_setup.rs b/src/util/tracing_setup.rs new file mode 100644 index 0000000..3de3fa8 --- /dev/null +++ b/src/util/tracing_setup.rs @@ -0,0 +1,52 @@ +use std::io; +use std::path::Path; +use tracing::dispatcher::SetGlobalDefaultError; +use tracing::subscriber::set_global_default; +use tracing::Level; +use tracing_appender::rolling; +use tracing_subscriber::fmt::Layer; +use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt; +use tracing_subscriber::{registry, EnvFilter}; + +/// Setup tracing +pub fn setup( + path: impl AsRef, + default_level: Level, + with_stdout: bool, +) -> Result<(), SetGlobalDefaultError> { + let path = path.as_ref(); + let root = path.parent().unwrap(); + let filename = path.file_name().unwrap().to_str().unwrap(); + + let default_filter = EnvFilter::try_from_default_env() + .unwrap_or_else(|_| EnvFilter::from_default_env().add_directive(default_level.into())); + + let fmt_file = Layer::new() + .with_writer(rolling::never(root, filename)) + .with_target(true) + .with_file(false) + .without_time() + .with_thread_names(false) + .with_thread_ids(false) + // .with_ansi(false) + .compact(); + let fmt_stdout = Layer::new() + .with_writer(io::stdout) + .with_target(true) + .with_line_number(true) + .without_time() + .with_file(false) + .compact(); + + if with_stdout { + set_global_default( + registry() + .with(default_filter) + .with(fmt_file) + .with(fmt_stdout), + )? + } else { + set_global_default(registry().with(default_filter).with(fmt_file))? + } + Ok(()) +} diff --git a/daemon/src/watch/event.rs b/src/watcher/event.rs similarity index 90% rename from daemon/src/watch/event.rs rename to src/watcher/event.rs index ae38aaa..c12ee79 100644 --- a/daemon/src/watch/event.rs +++ b/src/watcher/event.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] use super::state::InternalState; use notify::{Event as NotifyEvent, EventKind as NotifyEventKind}; use std::{ @@ -42,7 +43,7 @@ impl Event { use NotifyEventKind::*; if event.paths.len() > 1 { - log::error!("More than one path! {:#?}", event) + tracing::error!("More than one path! {:#?}", event) } let kind = match event.kind { @@ -59,7 +60,7 @@ impl Event { let file_name = match path.file_name() { Some(name) => name.to_string_lossy().to_string(), None => { - log::error!("Unable to get event file path name!!! {path:?}",); + tracing::error!("Unable to get event file path name!!! {path:?}",); Default::default() } }; @@ -68,13 +69,13 @@ impl Event { // Skip ignore paths if is_match(ignore, &*path.to_string_lossy()) { - log::trace!(r#""{file_name}" ignored"#); + tracing::trace!(r#""{file_name}" ignored"#); return None; } // Skip if Unsupported event if let EventKind::Other(kind) = kind { - log::trace!(r#"Skip {:?} of "{file_name}""#, kind,); + tracing::trace!(r#"Skip {:?} of "{file_name}""#, kind,); return None; } @@ -88,7 +89,7 @@ impl Event { // Skip when last run was less then 1 second agot let last_run = state.last_run(); if !(last_run > 1) { - log::trace!("Skip [last_run: {last_run}] [{event}]"); + tracing::trace!("Skip [last_run: {last_run}] [{event}]"); return None; } @@ -144,7 +145,7 @@ impl Event { /// Get the event's is seen. #[must_use] pub fn is_seen(&self) -> bool { - log::trace!("{}", self.file_name); + tracing::trace!("{}", self.file_name); if self.file_name.eq("project.yml") { return false; @@ -152,7 +153,7 @@ impl Event { let mut last_path = match self.last_path.lock() { Ok(path) => path, Err(err) => { - log::error!("{err}"); + tracing::error!("{err}"); err.into_inner() } }; @@ -175,7 +176,7 @@ impl fmt::Display for Event { FileUpdated => "modified", FileRenamed => "renamed", Other(event) => { - log::trace!("[FsEvent] Other: {:?}", event); + tracing::trace!("[FsEvent] Other: {:?}", event); "other" } _ => "", diff --git a/daemon/src/watch/interface.rs b/src/watcher/interface.rs similarity index 88% rename from daemon/src/watch/interface.rs rename to src/watcher/interface.rs index 8a8f77f..b8af1bb 100644 --- a/daemon/src/watch/interface.rs +++ b/src/watcher/interface.rs @@ -1,7 +1,5 @@ use super::*; -use crate::broadcast::Broadcast; -use crate::project::ProjectImplementer; -use crate::Result; +use crate::*; use async_trait::async_trait; use std::sync::{Arc, Weak}; use tokio::sync::{Mutex, OwnedMutexGuard}; @@ -33,19 +31,19 @@ pub trait Watchable: ToString + Send + Sync + 'static { impl WatchService { pub fn add(&mut self, watchable: W) -> Result<()> { let key = watchable.to_string(); - log::info!(r#"Add: {key:?}"#); + tracing::info!(r#"Add: {key:?}"#); let other = self.listeners.insert(key, Box::new(watchable)); if let Some(watchable) = other { let key = watchable.to_string(); - log::error!("Watchable with `{key}` already exists!") + tracing::error!("Watchable with `{key}` already exists!") } Ok(()) } pub fn remove(&mut self, key: &String) -> Result> { - log::info!("Remove: `{key}`"); + tracing::info!("Remove: `{key}`"); let item = self.listeners.remove(key).into_result("Watchable", key)?; Ok(item) } diff --git a/daemon/src/watch/mod.rs b/src/watcher/mod.rs similarity index 84% rename from daemon/src/watch/mod.rs rename to src/watcher/mod.rs index 80b2a5a..8a11dbb 100644 --- a/daemon/src/watch/mod.rs +++ b/src/watcher/mod.rs @@ -2,17 +2,13 @@ mod event; mod interface; mod state; -use crate::broadcast::Broadcast; -use crate::project::ProjectImplementer; -use crate::store::TryGetDaemonObject; -use crate::Result; +use crate::*; use std::collections::HashMap; use std::path::PathBuf; use std::sync::{Arc, Weak}; use tokio::sync::mpsc::channel; use tokio::sync::Mutex; use tokio::task::JoinHandle; -use xbase_proto::{IntoResult, PathExt}; pub use {event::*, interface::*}; @@ -48,14 +44,14 @@ impl WatchService { // IGNORE EVENTS OF RENAME FOR PATHS THAT NO LONGER EXISTS if !event.path().exists() && event.is_rename_event() { - log::debug!("{} [ignored]", event); + tracing::debug!("{} [ignored]", event); continue; } let broadcast = match broadcast.upgrade() { Some(broadcast) => broadcast, None => { - log::warn!(r"No broadcast found for {root:?}, dropping watcher .."); + tracing::warn!(r"No broadcast found for {root:?}, dropping watcher .."); break; } }; @@ -83,8 +79,8 @@ impl WatchService { let w = match root.try_get_mutex_watcher().await { Ok(w) => w, Err(err) => { - log::error!(r#"Unable to get watcher for {root:?}: {err}"#); - log::info!(r#"Dropping watcher for {root:?}: {err}"#); + tracing::error!(r#"Unable to get watcher for {root:?}: {err}"#); + tracing::info!(r#"Dropping watcher for {root:?}: {err}"#); break; } }; @@ -94,7 +90,7 @@ impl WatchService { for (key, listener) in watcher.listeners.iter() { if listener.should_discard(event).await { if let Err(err) = listener.discard().await { - log::error!(" discard errored for `{key}`!: {err}"); + tracing::error!(" discard errored for `{key}`!: {err}"); } else { discards.push(key.to_string()); } @@ -108,23 +104,23 @@ impl WatchService { listener.trigger(project, event, &broadcast, Arc::downgrade(&w)); if let Err(err) = trigger.await { - log::error!("trigger errored for `{key}`!: {err}"); + tracing::error!("trigger errored for `{key}`!: {err}"); } } } for key in discards.iter() { - log::info!("[{key:?}] discarded"); + tracing::info!("[{key:?}] discarded"); watcher.listeners.remove(key); } discards.clear(); internal_state.update_debounce(); - log::info!("{event} consumed successfully"); + tracing::info!("{event} consumed successfully"); } - log::info!("Dropped {:?}!!", root.as_path().abbrv()?.display()); + tracing::info!("Dropped {:?}!!", root.as_path().abbrv()?.display()); Ok(()) }); diff --git a/daemon/src/watch/state.rs b/src/watcher/state.rs similarity index 94% rename from daemon/src/watch/state.rs rename to src/watcher/state.rs index 7853cfd..af6f137 100644 --- a/daemon/src/watch/state.rs +++ b/src/watcher/state.rs @@ -22,7 +22,7 @@ impl InternalState { pub fn update_debounce(&self) { let mut debounce = self.debounce.lock().unwrap(); *debounce = SystemTime::now(); - log::trace!("Debounce updated!!!"); + tracing::trace!("Debounce updated!!!"); } pub fn last_run(&self) -> u128 {