Skip to content

Commit d2d1811

Browse files
committed
fix(embedded): Don't create an intermediate manifest
To parse the manifest, we have to write it out so our regular manifest loading code could handle it. This updates the manifest parsing code to handle it. This doesn't mean this will work everywhere in all cases though. For example, ephemeral workspaces parses a manifest from the SourceId and these won't have valid SourceIds. As a consequence, `Cargo.lock` and `CARGO_TARGET_DIR` are changing from being next to the temp manifest to being next to the script. This still isn't the desired behavior but stepping stones.
1 parent bff5cee commit d2d1811

File tree

7 files changed

+107
-229
lines changed

7 files changed

+107
-229
lines changed

src/bin/cargo/commands/run.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::path::Path;
55
use crate::command_prelude::*;
66
use crate::util::restricted_names::is_glob_pattern;
77
use cargo::core::Verbosity;
8+
use cargo::core::Workspace;
89
use cargo::ops::{self, CompileFilter, Packages};
910
use cargo_util::ProcessError;
1011

@@ -101,8 +102,10 @@ pub fn exec_manifest_command(config: &Config, cmd: &str, args: &[OsString]) -> C
101102
);
102103
}
103104
let manifest_path = crate::util::try_canonicalize(manifest_path)?;
104-
let script = cargo::util::toml::embedded::parse_from(&manifest_path)?;
105-
let ws = cargo::util::toml::embedded::to_workspace(&script, config)?;
105+
let mut ws = Workspace::new(&manifest_path, config)?;
106+
if config.cli_unstable().avoid_dev_deps {
107+
ws.set_require_optional_deps(false);
108+
}
106109

107110
let mut compile_opts =
108111
cargo::ops::CompileOptions::new(config, cargo::core::compiler::CompileMode::Build)?;

src/cargo/core/manifest.rs

+6
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ pub struct Manifest {
6464
metabuild: Option<Vec<String>>,
6565
resolve_behavior: Option<ResolveBehavior>,
6666
lint_rustflags: Vec<String>,
67+
embedded: bool,
6768
}
6869

6970
/// When parsing `Cargo.toml`, some warnings should silenced
@@ -407,6 +408,7 @@ impl Manifest {
407408
metabuild: Option<Vec<String>>,
408409
resolve_behavior: Option<ResolveBehavior>,
409410
lint_rustflags: Vec<String>,
411+
embedded: bool,
410412
) -> Manifest {
411413
Manifest {
412414
summary,
@@ -433,6 +435,7 @@ impl Manifest {
433435
metabuild,
434436
resolve_behavior,
435437
lint_rustflags,
438+
embedded,
436439
}
437440
}
438441

@@ -500,6 +503,9 @@ impl Manifest {
500503
pub fn links(&self) -> Option<&str> {
501504
self.links.as_deref()
502505
}
506+
pub fn is_embedded(&self) -> bool {
507+
self.embedded
508+
}
503509

504510
pub fn workspace_config(&self) -> &WorkspaceConfig {
505511
&self.workspace

src/cargo/core/workspace.rs

+11
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,10 @@ impl<'cfg> Workspace<'cfg> {
726726
if self.members.contains(&manifest_path) {
727727
return Ok(());
728728
}
729+
if is_path_dep && self.root_maybe().is_embedded() {
730+
// Embedded manifests cannot have workspace members
731+
return Ok(());
732+
}
729733
if is_path_dep
730734
&& !manifest_path.parent().unwrap().starts_with(self.root())
731735
&& self.find_root(&manifest_path)? != self.root_manifest
@@ -1580,6 +1584,13 @@ impl MaybePackage {
15801584
MaybePackage::Virtual(ref vm) => vm.workspace_config(),
15811585
}
15821586
}
1587+
1588+
fn is_embedded(&self) -> bool {
1589+
match self {
1590+
MaybePackage::Package(p) => p.manifest().is_embedded(),
1591+
MaybePackage::Virtual(_) => false,
1592+
}
1593+
}
15831594
}
15841595

15851596
impl WorkspaceRootConfig {

src/cargo/ops/cargo_package.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ fn build_lock(ws: &Workspace<'_>, orig_pkg: &Package) -> CargoResult<String> {
393393
let package_root = orig_pkg.root();
394394
let source_id = orig_pkg.package_id().source_id();
395395
let (manifest, _nested_paths) =
396-
TomlManifest::to_real_manifest(&toml_manifest, source_id, package_root, config)?;
396+
TomlManifest::to_real_manifest(&toml_manifest, false, source_id, package_root, config)?;
397397
let new_pkg = Package::new(manifest, orig_pkg.manifest_path());
398398

399399
// Regenerate Cargo.lock using the old one as a guide.

src/cargo/util/toml/embedded.rs

+27-186
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use anyhow::Context as _;
22

3-
use crate::core::Workspace;
43
use crate::CargoResult;
54
use crate::Config;
65

@@ -9,21 +8,13 @@ const DEFAULT_EDITION: crate::core::features::Edition =
98
const DEFAULT_VERSION: &str = "0.0.0";
109
const DEFAULT_PUBLISH: bool = false;
1110

12-
pub struct RawScript {
13-
manifest: String,
14-
body: String,
15-
path: std::path::PathBuf,
16-
}
17-
18-
pub fn parse_from(path: &std::path::Path) -> CargoResult<RawScript> {
19-
let body = std::fs::read_to_string(path)
20-
.with_context(|| format!("failed to script at {}", path.display()))?;
21-
parse(&body, path)
22-
}
23-
24-
fn parse(body: &str, path: &std::path::Path) -> CargoResult<RawScript> {
25-
let comment = match extract_comment(body) {
26-
Ok(manifest) => Some(manifest),
11+
pub fn expand_manifest(
12+
content: &str,
13+
path: &std::path::Path,
14+
config: &Config,
15+
) -> CargoResult<String> {
16+
let comment = match extract_comment(content) {
17+
Ok(comment) => Some(comment),
2718
Err(err) => {
2819
log::trace!("failed to extract doc comment: {err}");
2920
None
@@ -38,83 +29,19 @@ fn parse(body: &str, path: &std::path::Path) -> CargoResult<RawScript> {
3829
}
3930
}
4031
.unwrap_or_default();
41-
let body = body.to_owned();
42-
let path = path.to_owned();
43-
Ok(RawScript {
44-
manifest,
45-
body,
46-
path,
47-
})
48-
}
49-
50-
pub fn to_workspace<'cfg>(
51-
script: &RawScript,
52-
config: &'cfg Config,
53-
) -> CargoResult<Workspace<'cfg>> {
54-
let target_dir = config
55-
.target_dir()
56-
.transpose()
57-
.unwrap_or_else(|| default_target_dir().map(crate::util::Filesystem::new))?;
58-
// HACK: without cargo knowing about embedded manifests, the only way to create a
59-
// `Workspace` is either
60-
// - Create a temporary one on disk
61-
// - Create an "ephemeral" workspace **but** compilation re-loads ephemeral workspaces
62-
// from the registry rather than what we already have on memory, causing it to fail
63-
// because the registry doesn't know about embedded manifests.
64-
let manifest_path = write(script, config, target_dir.as_path_unlocked())?;
65-
let workspace = Workspace::new(&manifest_path, config)?;
66-
Ok(workspace)
67-
}
68-
69-
fn write(
70-
script: &RawScript,
71-
config: &Config,
72-
target_dir: &std::path::Path,
73-
) -> CargoResult<std::path::PathBuf> {
74-
let hash = hash(script).to_string();
75-
assert_eq!(hash.len(), 64);
76-
77-
let file_name = script
78-
.path
79-
.file_stem()
80-
.ok_or_else(|| anyhow::format_err!("no file name"))?
81-
.to_string_lossy();
82-
let separator = '_';
83-
let name = sanitize_package_name(file_name.as_ref(), separator);
84-
85-
let mut workspace_root = target_dir.to_owned();
86-
workspace_root.push("eval");
87-
workspace_root.push(&hash[0..2]);
88-
workspace_root.push(&hash[2..4]);
89-
workspace_root.push(&hash[4..]);
90-
workspace_root.push(name);
91-
std::fs::create_dir_all(&workspace_root).with_context(|| {
92-
format!(
93-
"failed to create temporary workspace at {}",
94-
workspace_root.display()
95-
)
96-
})?;
97-
let manifest_path = workspace_root.join("Cargo.toml");
98-
let manifest = expand_manifest(script, config)?;
99-
write_if_changed(&manifest_path, &manifest)?;
100-
Ok(manifest_path)
101-
}
102-
103-
fn expand_manifest(script: &RawScript, config: &Config) -> CargoResult<String> {
104-
let manifest = expand_manifest_(script, config)
105-
.with_context(|| format!("failed to parse manifest at {}", script.path.display()))?;
106-
let manifest = remap_paths(
107-
manifest,
108-
script.path.parent().ok_or_else(|| {
109-
anyhow::format_err!("no parent directory for {}", script.path.display())
110-
})?,
111-
)?;
32+
let manifest = expand_manifest_(content, &manifest, path, config)
33+
.with_context(|| format!("failed to parse manifest at {}", path.display()))?;
11234
let manifest = toml::to_string_pretty(&manifest)?;
11335
Ok(manifest)
11436
}
11537

116-
fn expand_manifest_(script: &RawScript, config: &Config) -> CargoResult<toml::Table> {
117-
let mut manifest: toml::Table = toml::from_str(&script.manifest)?;
38+
fn expand_manifest_(
39+
content: &str,
40+
manifest: &str,
41+
path: &std::path::Path,
42+
config: &Config,
43+
) -> CargoResult<toml::Table> {
44+
let mut manifest: toml::Table = toml::from_str(&manifest)?;
11845

11946
for key in ["workspace", "lib", "bin", "example", "test", "bench"] {
12047
if manifest.contains_key(key) {
@@ -135,14 +62,13 @@ fn expand_manifest_(script: &RawScript, config: &Config) -> CargoResult<toml::Ta
13562
anyhow::bail!("`package.{key}` is not allowed in embedded manifests")
13663
}
13764
}
138-
let file_name = script
139-
.path
65+
let file_name = path
14066
.file_stem()
14167
.ok_or_else(|| anyhow::format_err!("no file name"))?
14268
.to_string_lossy();
14369
let separator = '_';
14470
let name = sanitize_package_name(file_name.as_ref(), separator);
145-
let hash = hash(script);
71+
let hash = hash(content);
14672
let bin_name = format!("{name}{separator}{hash}");
14773
package
14874
.entry("name".to_owned())
@@ -166,9 +92,7 @@ fn expand_manifest_(script: &RawScript, config: &Config) -> CargoResult<toml::Ta
16692
bin.insert(
16793
"path".to_owned(),
16894
toml::Value::String(
169-
script
170-
.path
171-
.to_str()
95+
path.to_str()
17296
.ok_or_else(|| anyhow::format_err!("path is not valid UTF-8"))?
17397
.into(),
17498
),
@@ -217,26 +141,8 @@ fn sanitize_package_name(name: &str, placeholder: char) -> String {
217141
slug
218142
}
219143

220-
fn hash(script: &RawScript) -> blake3::Hash {
221-
blake3::hash(script.body.as_bytes())
222-
}
223-
224-
fn default_target_dir() -> CargoResult<std::path::PathBuf> {
225-
let mut cargo_home = home::cargo_home()?;
226-
cargo_home.push("eval");
227-
cargo_home.push("target");
228-
Ok(cargo_home)
229-
}
230-
231-
fn write_if_changed(path: &std::path::Path, new: &str) -> CargoResult<()> {
232-
let write_needed = match std::fs::read_to_string(path) {
233-
Ok(current) => current != new,
234-
Err(_) => true,
235-
};
236-
if write_needed {
237-
std::fs::write(path, new).with_context(|| format!("failed to write {}", path.display()))?;
238-
}
239-
Ok(())
144+
fn hash(content: &str) -> blake3::Hash {
145+
blake3::hash(content.as_bytes())
240146
}
241147

242148
/// Locates a "code block manifest" in Rust source.
@@ -438,10 +344,12 @@ mod test_expand {
438344

439345
macro_rules! si {
440346
($i:expr) => {{
441-
let script = parse($i, std::path::Path::new("/home/me/test.rs"))
442-
.unwrap_or_else(|err| panic!("{}", err));
443-
expand_manifest(&script, &Config::default().unwrap())
444-
.unwrap_or_else(|err| panic!("{}", err))
347+
expand_manifest(
348+
$i,
349+
std::path::Path::new("/home/me/test.rs"),
350+
&Config::default().unwrap(),
351+
)
352+
.unwrap_or_else(|err| panic!("{}", err))
445353
}};
446354
}
447355

@@ -693,73 +601,6 @@ fn main() {}
693601
}
694602
}
695603

696-
/// Given a Cargo manifest, attempts to rewrite relative file paths to absolute ones, allowing the manifest to be relocated.
697-
fn remap_paths(
698-
mani: toml::Table,
699-
package_root: &std::path::Path,
700-
) -> anyhow::Result<toml::value::Table> {
701-
// Values that need to be rewritten:
702-
let paths: &[&[&str]] = &[
703-
&["build-dependencies", "*", "path"],
704-
&["dependencies", "*", "path"],
705-
&["dev-dependencies", "*", "path"],
706-
&["package", "build"],
707-
&["target", "*", "dependencies", "*", "path"],
708-
];
709-
710-
let mut mani = toml::Value::Table(mani);
711-
712-
for path in paths {
713-
iterate_toml_mut_path(&mut mani, path, &mut |v| {
714-
if let toml::Value::String(s) = v {
715-
if std::path::Path::new(s).is_relative() {
716-
let p = package_root.join(&*s);
717-
if let Some(p) = p.to_str() {
718-
*s = p.into()
719-
}
720-
}
721-
}
722-
Ok(())
723-
})?
724-
}
725-
726-
match mani {
727-
toml::Value::Table(mani) => Ok(mani),
728-
_ => unreachable!(),
729-
}
730-
}
731-
732-
/// Iterates over the specified TOML values via a path specification.
733-
fn iterate_toml_mut_path<F>(
734-
base: &mut toml::Value,
735-
path: &[&str],
736-
on_each: &mut F,
737-
) -> anyhow::Result<()>
738-
where
739-
F: FnMut(&mut toml::Value) -> anyhow::Result<()>,
740-
{
741-
if path.is_empty() {
742-
return on_each(base);
743-
}
744-
745-
let cur = path[0];
746-
let tail = &path[1..];
747-
748-
if cur == "*" {
749-
if let toml::Value::Table(tab) = base {
750-
for (_, v) in tab {
751-
iterate_toml_mut_path(v, tail, on_each)?;
752-
}
753-
}
754-
} else if let toml::Value::Table(tab) = base {
755-
if let Some(v) = tab.get_mut(cur) {
756-
iterate_toml_mut_path(v, tail, on_each)?;
757-
}
758-
}
759-
760-
Ok(())
761-
}
762-
763604
#[cfg(test)]
764605
mod test_manifest {
765606
use super::*;

0 commit comments

Comments
 (0)