Skip to content

Commit bffd585

Browse files
authored
Procedural macro compilation (#1110)
Resolves #1129 commit-id:aebc6424 --- **Stack**: - #1161 - #1159 - #1157 - #1143 - #1151 - #1150 - #1148 - #1100 - #1155 - #1110⚠️ *Part of a stack created by [spr](https://github.com/ejoffe/spr). Do not merge manually using the UI - doing so may have unexpected results.*
1 parent 21e6d18 commit bffd585

File tree

7 files changed

+138
-26
lines changed

7 files changed

+138
-26
lines changed

scarb/src/compiler/compilation_unit.rs

+4
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ impl CompilationUnit {
9191
ws.target_dir().child(self.profile.as_str())
9292
}
9393

94+
pub fn is_cairo_plugin(&self) -> bool {
95+
self.target().is_cairo_plugin()
96+
}
97+
9498
pub fn is_sole_for_package(&self) -> bool {
9599
self.main_component()
96100
.package

scarb/src/compiler/plugin/proc_macro/compilation.rs

+47-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
use crate::compiler::plugin::proc_macro::PROC_MACRO_BUILD_PROFILE;
2-
use crate::core::{Config, Package};
2+
use crate::compiler::CompilationUnit;
3+
use crate::core::{Config, Package, Workspace};
34
use crate::flock::Filesystem;
5+
use crate::process::exec_piping;
6+
use anyhow::Result;
47
use camino::Utf8PathBuf;
58
use libloading::library_filename;
9+
use std::process::Command;
10+
use tracing::trace_span;
611

712
/// This trait is used to define the shared library path for a package.
813
pub trait SharedLibraryProvider {
@@ -38,3 +43,44 @@ impl SharedLibraryProvider for Package {
3843
.join(lib_name)
3944
}
4045
}
46+
47+
pub fn compile_unit(unit: CompilationUnit, ws: &Workspace<'_>) -> Result<()> {
48+
let main_package = unit.components.first().unwrap().package.clone();
49+
let cmd = CargoCommand {
50+
current_dir: main_package.root().to_path_buf(),
51+
target_dir: main_package
52+
.target_path(ws.config())
53+
.path_unchecked()
54+
.to_path_buf(),
55+
};
56+
{
57+
let _ = trace_span!("compile_proc_macro").enter();
58+
exec(&mut cmd.into(), ws.config())?;
59+
}
60+
Ok(())
61+
}
62+
63+
struct CargoCommand {
64+
current_dir: Utf8PathBuf,
65+
target_dir: Utf8PathBuf,
66+
}
67+
68+
impl From<CargoCommand> for Command {
69+
fn from(args: CargoCommand) -> Self {
70+
let mut cmd = Command::new("cargo");
71+
cmd.current_dir(args.current_dir);
72+
cmd.args(["build", "--release"]);
73+
cmd.arg("--target-dir");
74+
cmd.arg(args.target_dir);
75+
cmd
76+
}
77+
}
78+
79+
fn exec(cmd: &mut Command, config: &Config) -> Result<()> {
80+
exec_piping(
81+
cmd,
82+
config,
83+
|line: &str| config.ui().print(line),
84+
|line: &str| config.ui().print(line),
85+
)
86+
}

scarb/src/compiler/plugin/proc_macro/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ pub mod compilation;
22
mod ffi;
33
mod host;
44

5+
pub use compilation::compile_unit;
56
pub use ffi::*;
67
pub use host::*;

scarb/src/ops/compile.rs

+28-17
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ use anyhow::{anyhow, Result};
22
use cairo_lang_compiler::db::RootDatabase;
33
use cairo_lang_compiler::diagnostics::DiagnosticsError;
44
use indoc::formatdoc;
5+
use itertools::Itertools;
56

67
use scarb_ui::components::Status;
78
use scarb_ui::HumanDuration;
89

910
use crate::compiler::db::{build_scarb_root_database, has_starknet_plugin};
1011
use crate::compiler::helpers::build_compiler_config;
12+
use crate::compiler::plugin::proc_macro;
1113
use crate::compiler::CompilationUnit;
1214
use crate::core::{PackageId, PackageName, TargetKind, Utf8PathWorkspaceExt, Workspace};
1315
use crate::ops;
@@ -59,11 +61,21 @@ where
5961

6062
let compilation_units = ops::generate_compilation_units(&resolve, ws)?
6163
.into_iter()
62-
.filter(|cu| !opts.exclude_targets.contains(&cu.target().kind))
6364
.filter(|cu| {
64-
opts.include_targets.is_empty() || opts.include_targets.contains(&cu.target().kind)
65+
let is_excluded = opts.exclude_targets.contains(&cu.target().kind);
66+
let is_included =
67+
opts.include_targets.is_empty() || opts.include_targets.contains(&cu.target().kind);
68+
let is_selected = packages.contains(&cu.main_package_id);
69+
let is_cairo_plugin = cu.components.first().unwrap().target.is_cairo_plugin();
70+
is_cairo_plugin || (is_selected && is_included && !is_excluded)
71+
})
72+
.sorted_by_key(|cu| {
73+
if cu.components.first().unwrap().target.is_cairo_plugin() {
74+
0
75+
} else {
76+
1
77+
}
6578
})
66-
.filter(|cu| packages.contains(&cu.main_package_id))
6779
.collect::<Vec<_>>();
6880

6981
for unit in compilation_units {
@@ -89,22 +101,21 @@ fn compile_unit(unit: CompilationUnit, ws: &Workspace<'_>) -> Result<()> {
89101
.ui()
90102
.print(Status::new("Compiling", &unit.name()));
91103

92-
let mut db = build_scarb_root_database(&unit, ws)?;
93-
94-
check_starknet_dependency(&unit, ws, &db, &package_name);
95-
96-
ws.config()
97-
.compilers()
98-
.compile(unit, &mut db, ws)
99-
.map_err(|err| {
100-
if !suppress_error(&err) {
101-
ws.config().ui().anyhow(&err);
102-
}
104+
let result = if unit.is_cairo_plugin() {
105+
proc_macro::compile_unit(unit, ws)
106+
} else {
107+
let mut db = build_scarb_root_database(&unit, ws)?;
108+
check_starknet_dependency(&unit, ws, &db, &package_name);
109+
ws.config().compilers().compile(unit, &mut db, ws)
110+
};
103111

104-
anyhow!("could not compile `{package_name}` due to previous error")
105-
})?;
112+
result.map_err(|err| {
113+
if !suppress_error(&err) {
114+
ws.config().ui().anyhow(&err);
115+
}
106116

107-
Ok(())
117+
anyhow!("could not compile `{package_name}` due to previous error")
118+
})
108119
}
109120

110121
fn check_unit(unit: CompilationUnit, ws: &Workspace<'_>) -> Result<()> {

scarb/src/ops/resolve.rs

+33-3
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,24 @@ pub fn generate_compilation_units(
155155
let mut units = Vec::with_capacity(ws.members().size_hint().0);
156156
for member in ws.members() {
157157
units.extend(if member.is_cairo_plugin() {
158-
generate_cairo_plugin_compilation_units()?
158+
generate_cairo_plugin_compilation_units(&member, ws)?
159159
} else {
160160
generate_cairo_compilation_units(&member, resolve, ws)?
161161
});
162162
}
163163

164+
let cairo_plugins = units
165+
.iter()
166+
.flat_map(|unit| unit.cairo_plugins.clone())
167+
.filter(|plugin| !plugin.builtin)
168+
.map(|plugin| plugin.package.clone())
169+
.unique_by(|plugin| plugin.id)
170+
.collect_vec();
171+
172+
for plugin in cairo_plugins {
173+
units.extend(generate_cairo_plugin_compilation_units(&plugin, ws)?);
174+
}
175+
164176
assert!(
165177
units.iter().map(CompilationUnit::id).all_unique(),
166178
"All generated compilation units must have unique IDs."
@@ -425,6 +437,24 @@ fn check_cairo_version_compatibility(packages: &[Package], ws: &Workspace<'_>) -
425437
Ok(())
426438
}
427439

428-
fn generate_cairo_plugin_compilation_units() -> Result<Vec<CompilationUnit>> {
429-
bail!("compiling Cairo plugin packages is not possible yet")
440+
fn generate_cairo_plugin_compilation_units(
441+
member: &Package,
442+
ws: &Workspace<'_>,
443+
) -> Result<Vec<CompilationUnit>> {
444+
Ok(vec![CompilationUnit {
445+
main_package_id: member.id,
446+
components: vec![CompilationUnitComponent {
447+
package: member.clone(),
448+
cfg_set: None,
449+
target: member
450+
.fetch_target(&TargetKind::CAIRO_PLUGIN)
451+
.cloned()
452+
// Safe to unwrap, as member.is_cairo_plugin() has been ensured before.
453+
.expect("main component of procedural macro must define `cairo-plugin` target"),
454+
}],
455+
cairo_plugins: Vec::new(),
456+
profile: ws.current_profile()?,
457+
compiler_config: member.manifest.compiler_config.clone(),
458+
cfg_set: Default::default(),
459+
}])
430460
}

scarb/src/process.rs

+24-5
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,28 @@ mod imp {
8787
}
8888
}
8989

90-
/// Runs the process, waiting for completion, and mapping non-success exit codes to an error.
9190
#[tracing::instrument(level = "trace", skip_all)]
9291
pub fn exec(cmd: &mut Command, config: &Config) -> Result<()> {
92+
exec_piping(
93+
cmd,
94+
config,
95+
|line: &str| {
96+
debug!("{line}");
97+
},
98+
|line: &str| {
99+
debug!("{line}");
100+
},
101+
)
102+
}
103+
104+
/// Runs the process, waiting for completion, and mapping non-success exit codes to an error.
105+
#[tracing::instrument(level = "trace", skip_all)]
106+
pub fn exec_piping(
107+
cmd: &mut Command,
108+
config: &Config,
109+
stdout_callback: impl Fn(&str) + Send,
110+
stderr_callback: impl Fn(&str) + Send,
111+
) -> Result<()> {
93112
let cmd_str = shlex_join(cmd);
94113

95114
config.ui().verbose(Status::new("Running", &cmd_str));
@@ -112,7 +131,7 @@ pub fn exec(cmd: &mut Command, config: &Config) -> Result<()> {
112131
let span = debug_span!("out");
113132
move || {
114133
let mut stdout = stdout;
115-
pipe_to_logs(&span, &mut stdout);
134+
pipe(&span, &mut stdout, stdout_callback);
116135
}
117136
});
118137

@@ -121,7 +140,7 @@ pub fn exec(cmd: &mut Command, config: &Config) -> Result<()> {
121140
let span = debug_span!("err");
122141
move || {
123142
let mut stderr = stderr;
124-
pipe_to_logs(&span, &mut stderr);
143+
pipe(&span, &mut stderr, stderr_callback);
125144
}
126145
});
127146

@@ -135,12 +154,12 @@ pub fn exec(cmd: &mut Command, config: &Config) -> Result<()> {
135154
}
136155
});
137156

138-
fn pipe_to_logs(span: &Span, stream: &mut dyn Read) {
157+
fn pipe(span: &Span, stream: &mut dyn Read, callback: impl Fn(&str)) {
139158
let _enter = span.enter();
140159
let stream = BufReader::with_capacity(128, stream);
141160
for line in stream.lines() {
142161
match line {
143-
Ok(line) => debug!("{line}"),
162+
Ok(line) => callback(line.as_str()),
144163
Err(err) => warn!("{err:?}"),
145164
}
146165
}

scarb/tests/build_cairo_plugin.rs

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use scarb_test_support::command::Scarb;
55
use scarb_test_support::project_builder::ProjectBuilder;
66

77
#[test]
8+
#[ignore = "TODO(maciektr): Remove when proc-macros are implemented."]
89
fn compile_cairo_plugin() {
910
let t = TempDir::new().unwrap();
1011
ProjectBuilder::start()

0 commit comments

Comments
 (0)