Skip to content

Commit e2663a3

Browse files
committed
enhance transitive dependency features
1 parent 6353294 commit e2663a3

41 files changed

Lines changed: 2610 additions & 176 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

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

crates/ty/docs/configuration.md

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

crates/ty/docs/rules.md

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

crates/ty/src/by_commands.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::HashMap;
1+
use std::collections::{BTreeSet, HashMap};
22
use std::ffi::OsStr;
33
use std::fs;
44
use std::io::{self, Read};
@@ -444,14 +444,22 @@ pub(crate) fn cmd_build(
444444
}
445445
let file_count = handles.len();
446446
let roots = module_roots(&db, &root);
447+
let mut packages: BTreeSet<PathBuf> = BTreeSet::new();
447448
if !render_check_and_transpile(
448449
&db,
449450
&handles,
450451
&config,
451452
CheckGate::ParseErrorsOnly,
452453
&rebuilder,
453454
|bpy, src, _line_map| {
454-
let py = out.join(module_relative_path(&roots, &root, bpy));
455+
let relative = module_relative_path(&roots, &root, bpy);
456+
if relative.components().count() > 1
457+
&& let Some(package) = relative.components().next()
458+
{
459+
packages.insert(out.join(package));
460+
}
461+
462+
let py = out.join(relative);
455463
fs::create_dir_all(py.parent().unwrap())?;
456464
fs::write(&py, src)?;
457465
eprintln!("{} -> {}", bpy.display(), py.display());
@@ -461,10 +469,42 @@ pub(crate) fn cmd_build(
461469
return Ok(ExitStatus::Failure);
462470
}
463471

472+
write_markers(&db, &packages)?;
473+
464474
eprintln!("\nbuild complete ({file_count} files)");
465475
Ok(ExitStatus::Success)
466476
}
467477

478+
/// Writes the `by.typed` marker into every package the build emitted.
479+
///
480+
/// The marker is what tells a project that installs this one that its packages
481+
/// are basedpython's, and it carries the one thing a `pyproject.toml` cannot tell
482+
/// them: which of this project's dependencies are part of its own interface.
483+
/// Nothing installs a `pyproject.toml`, and this rides along inside the package.
484+
#[allow(clippy::print_stderr)]
485+
fn write_markers(db: &ProjectDatabase, packages: &BTreeSet<PathBuf>) -> anyhow::Result<()> {
486+
let exported = db
487+
.project()
488+
.settings(db)
489+
.analysis()
490+
.exported_dependencies
491+
.clone()
492+
.unwrap_or_default();
493+
let marker = ty_module_resolver::Marker::render(&exported);
494+
495+
for package in packages {
496+
let path = package.join(ty_module_resolver::BY_TYPED);
497+
fs::create_dir_all(package)?;
498+
fs::write(&path, &marker)?;
499+
}
500+
501+
if !exported.is_empty() {
502+
eprintln!("exporting {}", exported.join(", "));
503+
}
504+
505+
Ok(())
506+
}
507+
468508
// ── compile ─────────────────────────────────────────────────────────────────
469509

470510
/// How `by compile` was invoked.

crates/ty/tests/by_e2e.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1556,6 +1556,62 @@ fn build_skips_hidden_directories() {
15561556
);
15571557
}
15581558

1559+
/// what a project exports is not in its `pyproject.toml` as far as its users are
1560+
/// concerned — nothing installs one — so the build writes it into the package
1561+
#[test]
1562+
fn build_writes_what_the_project_exports_into_its_marker() {
1563+
let dir = tempfile::tempdir().expect("tempdir");
1564+
fs::write(
1565+
dir.path().join("pyproject.toml"),
1566+
"[project]\nname = \"my-lib\"\nversion = \"0.1.0\"\n\
1567+
\n[tool.basedpython.analysis]\nexported-dependencies = [\"numpy\"]\n",
1568+
)
1569+
.unwrap();
1570+
let package = dir.path().join("my_lib");
1571+
fs::create_dir_all(&package).unwrap();
1572+
fs::write(package.join("__init__.by"), "").unwrap();
1573+
fs::write(
1574+
package.join("frames.by"),
1575+
"def frame() -> int:\n return 1\n",
1576+
)
1577+
.unwrap();
1578+
1579+
let output = Command::new(env!("CARGO_BIN_EXE_by"))
1580+
.arg("build")
1581+
.current_dir(dir.path())
1582+
.output()
1583+
.expect("failed to spawn by");
1584+
1585+
let stderr = String::from_utf8_lossy(&output.stderr);
1586+
assert!(output.status.success(), "by build failed:\n{stderr}");
1587+
assert_eq!(
1588+
fs::read_to_string(dir.path().join("out").join("my_lib").join("by.typed")).unwrap(),
1589+
"exported-dependencies = [\"numpy\"]\n"
1590+
);
1591+
}
1592+
1593+
/// a package the build emitted is marked as basedpython's even when the project
1594+
/// exports nothing: the file's presence is what marks it
1595+
#[test]
1596+
fn build_writes_a_marker_for_a_project_that_exports_nothing() {
1597+
let dir = tempfile::tempdir().expect("tempdir");
1598+
let package = dir.path().join("my_lib");
1599+
fs::create_dir_all(&package).unwrap();
1600+
fs::write(package.join("__init__.by"), "").unwrap();
1601+
1602+
let output = Command::new(env!("CARGO_BIN_EXE_by"))
1603+
.arg("build")
1604+
.current_dir(dir.path())
1605+
.output()
1606+
.expect("failed to spawn by");
1607+
1608+
let stderr = String::from_utf8_lossy(&output.stderr);
1609+
assert!(output.status.success(), "by build failed:\n{stderr}");
1610+
let marker = dir.path().join("out").join("my_lib").join("by.typed");
1611+
assert!(marker.exists(), "expected out/my_lib/by.typed:\n{stderr}");
1612+
assert_eq!(fs::read_to_string(marker).unwrap(), "");
1613+
}
1614+
15591615
/// a src-layout project's `src/pkg/main.by` is the module `pkg.main`, so the
15601616
/// emitted tree has to be rooted at `src` — mirroring the directory instead
15611617
/// emits `out/src/pkg/main.py`, whose module is `src.pkg.main`, a name nothing

crates/ty_ide/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ tracing = { workspace = true }
5252
ruff_python_parser = { workspace = true }
5353
ruff_ranged_value = { workspace = true }
5454
ty_project = { workspace = true, features = ["testing"] }
55+
ty_static = { workspace = true }
5556

5657
camino = { workspace = true }
5758
insta = { workspace = true, features = ["filters"] }

0 commit comments

Comments
 (0)