Skip to content

Commit 358cc29

Browse files
authored
feat: compile-time fallback include path (#98)
This is a result of a small investigation into how to make protols work out of the box with the well-known protobuf types, even though it does not ship them. See NixOS/nixpkgs#465302. The idea is to be able to configure, in compile time, a lowest-priority include path that a distro package could set to make sure, without compromising other sources of include paths, that protols can find the well-known types even when `pkg-config` and `protobuf` are missing from the environment and no paths have been manually configured via CLI or settings. Please advise on the best name for the environment variable.
1 parent 806090e commit 358cc29

File tree

7 files changed

+139
-10
lines changed

7 files changed

+139
-10
lines changed

Cargo.lock

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ serde_json = "1.0"
3030
basic-toml = "0.1"
3131
pkg-config = "0.3"
3232
clap = { version = "4.5", features = ["derive"] }
33+
const_format = "0.2"
3334

3435
[dev-dependencies]
3536
insta = { version = "1.43", features = ["yaml", "redactions"] }

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
- [Hover Information](#hover-information)
3939
- [Rename Symbols](#rename-symbols)
4040
- [Find References](#find-references)
41+
- [Protocol Buffers Well-Known Types](#protocol-buffers-well-known-types)
42+
- [Packaging](#📦-packaging)
4143
- [Contributing](#contributing)
4244
- [Setting Up Locally](#setting-up-locally)
4345
- [License](#license)
@@ -130,7 +132,12 @@ The `[config]` section contains stable settings that should generally remain unc
130132
- **Configuration file**: Workspace-specific paths defined in `protols.toml`
131133
- **Command line**: Global paths using `--include-paths` flag that apply to all workspaces
132134
- **Initialization parameters**: Dynamic paths set via LSP `initializationParams` (useful for editors like Neovim)
133-
135+
136+
When a file is not found in any of the paths above, the following directories are searched:
137+
- **Protobuf Include Path**: the path containing the [Protocol Buffers Well-Known Types](https://protobuf.dev/reference/protobuf/google.protobuf/) as detected by [`pkg-config`](https://www.freedesktop.org/wiki/Software/pkg-config/) (requires `pkg-config` present in environment and capable of finding the installation of `protobuf`)
138+
- **Fallback Include Path**: the fallback include path configured at compile time
139+
140+
134141
All include paths from these sources are combined when resolving proto imports.
135142

136143
#### Path Configuration
@@ -182,8 +189,27 @@ Rename symbols like messages or enums, and Propagate the changes throughout the
182189

183190
Find all references to user-defined types like messages or enums. Nested fields are fully supported, making it easier to track symbol usage across your project.
184191

192+
## Protocol Buffers Well-Known Types
193+
194+
Protols does not ship with the [Protocol Buffers Well-Known Types](https://protobuf.dev/reference/protobuf/google.protobuf/) unless configured to do so by a distribution.
195+
In order for features above to work for the well-known types, the well-known imports must either resolve against one of the configured import paths or the environment must contain in `PATH` a `pkg-config` executable capable of resolving the package `protobuf`.
196+
You can verify this by running
197+
198+
```bash
199+
pkg-config --modversion protobuf
200+
```
201+
202+
in protols' environment.
203+
185204
---
186205

206+
## 📦 Packaging
207+
208+
Distributions may set an absolute include path which contains the Protocol Buffers Well-Known Types,
209+
for example pointing to the files provided by the `protobuf` package, by compiling protols with the
210+
environment variable `FALLBACK_INCLUDE_PATH` set to the desired path. This path will be used by the
211+
compiled executable for resolution of any proto files that could not be resolved otherwise.
212+
187213
## 🤝 Contributing
188214

189215
We welcome contributions from developers of all experience levels! To get started:

build.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use std::path::Path;
2+
3+
fn main() {
4+
if let Some(path) = option_env!("FALLBACK_INCLUDE_PATH") {
5+
let path = Path::new(path);
6+
assert!(
7+
path.is_absolute(),
8+
"Environment variable FALLBACK_INCLUDE_PATH must be absolute: {path:?}"
9+
);
10+
}
11+
}

src/config/workspace.rs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ pub struct WorkspaceProtoConfigs {
2020
protoc_include_prefix: Vec<PathBuf>,
2121
cli_include_paths: Vec<PathBuf>,
2222
init_include_paths: Vec<PathBuf>,
23+
fallback_include_path: Option<PathBuf>,
2324
}
2425

2526
impl WorkspaceProtoConfigs {
26-
pub fn new(cli_include_paths: Vec<PathBuf>) -> Self {
27+
pub fn new(cli_include_paths: Vec<PathBuf>, fallback_include_path: Option<PathBuf>) -> Self {
2728
// Try to find protobuf library and get its include paths
2829
// Do not emit metadata on stdout as LSP programs can consider
2930
// it part of spec
@@ -39,6 +40,7 @@ impl WorkspaceProtoConfigs {
3940
workspaces: HashSet::new(),
4041
formatters: HashMap::new(),
4142
configs: HashMap::new(),
43+
fallback_include_path,
4244
protoc_include_prefix,
4345
cli_include_paths,
4446
init_include_paths: Vec::new(),
@@ -128,6 +130,7 @@ impl WorkspaceProtoConfigs {
128130

129131
ipath.push(w.to_path_buf());
130132
ipath.extend_from_slice(&self.protoc_include_prefix);
133+
ipath.extend_from_slice(self.fallback_include_path.as_slice());
131134
Some(ipath)
132135
}
133136

@@ -180,7 +183,7 @@ mod test {
180183
let f = tmpdir.path().join("protols.toml");
181184
std::fs::write(f, include_str!("input/protols-valid.toml")).unwrap();
182185

183-
let mut ws = WorkspaceProtoConfigs::new(vec![]);
186+
let mut ws = WorkspaceProtoConfigs::new(vec![], None);
184187
ws.add_workspace(&WorkspaceFolder {
185188
uri: Url::from_directory_path(tmpdir.path()).unwrap(),
186189
name: "Test".to_string(),
@@ -214,7 +217,7 @@ mod test {
214217
let f = tmpdir.path().join("protols.toml");
215218
std::fs::write(f, include_str!("input/protols-valid.toml")).unwrap();
216219

217-
let mut ws = WorkspaceProtoConfigs::new(vec![]);
220+
let mut ws = WorkspaceProtoConfigs::new(vec![], None);
218221
ws.add_workspace(&WorkspaceFolder {
219222
uri: Url::from_directory_path(tmpdir.path()).unwrap(),
220223
name: "Test".to_string(),
@@ -249,7 +252,7 @@ mod test {
249252
let f = tmpdir.path().join(file);
250253
std::fs::write(f, include_str!("input/protols-valid.toml")).unwrap();
251254

252-
let mut ws = WorkspaceProtoConfigs::new(vec![]);
255+
let mut ws = WorkspaceProtoConfigs::new(vec![], None);
253256
ws.add_workspace(&WorkspaceFolder {
254257
uri: Url::from_directory_path(tmpdir.path()).unwrap(),
255258
name: "Test".to_string(),
@@ -272,7 +275,7 @@ mod test {
272275
PathBuf::from("/path/to/protos"),
273276
PathBuf::from("relative/path"),
274277
];
275-
let mut ws = WorkspaceProtoConfigs::new(cli_paths);
278+
let mut ws = WorkspaceProtoConfigs::new(cli_paths, None);
276279
ws.add_workspace(&WorkspaceFolder {
277280
uri: Url::from_directory_path(tmpdir.path()).unwrap(),
278281
name: "Test".to_string(),
@@ -309,7 +312,7 @@ mod test {
309312
PathBuf::from("relative/init/path"),
310313
];
311314

312-
let mut ws = WorkspaceProtoConfigs::new(cli_paths);
315+
let mut ws = WorkspaceProtoConfigs::new(cli_paths, None);
313316
ws.set_init_include_paths(init_paths);
314317
ws.add_workspace(&WorkspaceFolder {
315318
uri: Url::from_directory_path(tmpdir.path()).unwrap(),
@@ -329,4 +332,37 @@ mod test {
329332
// CLI paths should still be included
330333
assert!(include_paths.contains(&PathBuf::from("/cli/path")));
331334
}
335+
336+
#[test]
337+
fn test_fallback_include_path() {
338+
let tmpdir = tempdir().expect("failed to create temp directory");
339+
let f = tmpdir.path().join("protols.toml");
340+
std::fs::write(f, include_str!("input/protols-valid.toml")).unwrap();
341+
342+
// Set both CLI and initialization include paths
343+
let cli_paths = vec![PathBuf::from("/cli/path")];
344+
let init_paths = vec![
345+
PathBuf::from("/init/path1"),
346+
PathBuf::from("relative/init/path"),
347+
];
348+
349+
let mut ws = WorkspaceProtoConfigs::new(cli_paths, Some("fallback_path".into()));
350+
ws.set_init_include_paths(init_paths);
351+
ws.add_workspace(&WorkspaceFolder {
352+
uri: Url::from_directory_path(tmpdir.path()).unwrap(),
353+
name: "Test".to_string(),
354+
});
355+
356+
let inworkspace = Url::from_file_path(tmpdir.path().join("foobar.proto")).unwrap();
357+
let include_paths = ws.get_include_paths(&inworkspace).unwrap();
358+
359+
// Fallback path should be included and on the last position
360+
assert_eq!(
361+
include_paths
362+
.iter()
363+
.rev()
364+
.position(|p| p == "fallback_path"),
365+
Some(0)
366+
);
367+
}
332368
}

src/main.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use async_lsp::panic::CatchUnwindLayer;
66
use async_lsp::server::LifecycleLayer;
77
use async_lsp::tracing::TracingLayer;
88
use clap::Parser;
9+
use const_format::concatcp;
910
use server::{ProtoLanguageServer, TickEvent};
1011
use tower::ServiceBuilder;
1112
use tracing::Level;
@@ -25,13 +26,32 @@ mod workspace;
2526

2627
/// Language server for proto3 files
2728
#[derive(Parser, Debug)]
28-
#[command(author, version, about, long_about = None, ignore_errors(true))]
29+
#[command(
30+
author,
31+
version = concatcp!(
32+
env!("CARGO_PKG_VERSION"),
33+
"\n",
34+
BUILD_INFO
35+
),
36+
about,
37+
long_about = None,
38+
ignore_errors(true)
39+
)]
2940
struct Cli {
3041
/// Include paths for proto files
3142
#[arg(short, long, value_delimiter = ',')]
3243
include_paths: Option<Vec<String>>,
3344
}
3445

46+
const FALLBACK_INCLUDE_PATH: Option<&str> = option_env!("FALLBACK_INCLUDE_PATH");
47+
const BUILD_INFO: &str = concatcp!(
48+
"fallback include path: ",
49+
match FALLBACK_INCLUDE_PATH {
50+
Some(path) => path,
51+
None => "not set",
52+
}
53+
);
54+
3555
#[tokio::main(flavor = "current_thread")]
3656
async fn main() {
3757
let cli = Cli::parse();
@@ -48,14 +68,18 @@ async fn main() {
4868
.with_writer(file_appender.0)
4969
.init();
5070

71+
let fallback_include_path = FALLBACK_INCLUDE_PATH.map(Into::into);
72+
5173
tracing::info!("server version: {}", env!("CARGO_PKG_VERSION"));
5274
let (server, _) = async_lsp::MainLoop::new_server(|client| {
5375
tracing::info!("Using CLI options: {:?}", cli);
76+
tracing::info!("Using fallback include path: {:?}", fallback_include_path);
5477
let router = ProtoLanguageServer::new_router(
5578
client.clone(),
5679
cli.include_paths
5780
.map(|ic| ic.into_iter().map(std::path::PathBuf::from).collect())
5881
.unwrap_or_default(),
82+
fallback_include_path,
5983
);
6084

6185
tokio::spawn({

src/server.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,16 @@ pub struct ProtoLanguageServer {
3232
}
3333

3434
impl ProtoLanguageServer {
35-
pub fn new_router(client: ClientSocket, cli_include_paths: Vec<PathBuf>) -> Router<Self> {
35+
pub fn new_router(
36+
client: ClientSocket,
37+
cli_include_paths: Vec<PathBuf>,
38+
fallback_include_path: Option<PathBuf>,
39+
) -> Router<Self> {
3640
let mut router = Router::new(Self {
3741
client,
3842
counter: 0,
3943
state: ProtoLanguageState::new(),
40-
configs: WorkspaceProtoConfigs::new(cli_include_paths),
44+
configs: WorkspaceProtoConfigs::new(cli_include_paths, fallback_include_path),
4145
});
4246

4347
router.event::<TickEvent>(|st, _| {

0 commit comments

Comments
 (0)