Skip to content

Commit

Permalink
Allow multiple targets on single Compilation unit component
Browse files Browse the repository at this point in the history
commit-id:c5200c0d
  • Loading branch information
maciektr committed May 21, 2024
1 parent f3d8b1b commit 58f6658
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 63 deletions.
120 changes: 109 additions & 11 deletions scarb/src/compiler/compilation_unit.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use std::fmt::Write;
use std::hash::{Hash, Hasher};

use anyhow::{ensure, Result};
use cairo_lang_filesystem::cfg::CfgSet;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;
use typed_builder::TypedBuilder;

use crate::compiler::Profile;
use crate::core::{ManifestCompilerConfig, Package, PackageId, Target, Workspace};
use crate::core::{ManifestCompilerConfig, Package, PackageId, Target, TargetKind, Workspace};
use crate::flock::Filesystem;
use scarb_stable_hash::StableHasher;

Expand Down Expand Up @@ -75,7 +78,7 @@ pub struct CompilationUnitComponent {
/// The Scarb [`Package`] to be built.
pub package: Package,
/// Information about the specific target to build, out of the possible targets in `package`.
pub target: Target,
pub targets: Vec<Target>,
/// Items for the Cairo's `#[cfg(...)]` attribute to be enabled in this component.
pub cfg_set: Option<CfgSet>,
}
Expand All @@ -101,10 +104,6 @@ pub trait CompilationUnitAttributes {
component
}

fn target(&self) -> &Target {
&self.main_component().target
}

fn id(&self) -> String {
format!("{}-{}", self.main_package_id().name, self.digest())
}
Expand All @@ -121,18 +120,18 @@ pub trait CompilationUnitAttributes {
}

fn has_custom_name(&self) -> bool {
self.main_component().target.kind.as_str() != self.main_package_id().name.as_str()
self.main_component().target_kind().as_str() != self.main_package_id().name.as_str()
}

fn name(&self) -> String {
let mut string = String::new();

let main_component = self.main_component();
if self.is_sole_for_package() || self.target().is_test() {
write!(&mut string, "{}", main_component.target.kind).unwrap();
if self.is_sole_for_package() || self.main_component().target_kind().is_test() {
write!(&mut string, "{}", main_component.target_kind()).unwrap();

if self.has_custom_name() {
write!(&mut string, "({})", main_component.target.name).unwrap();
write!(&mut string, "({})", main_component.target_name()).unwrap();
}

write!(&mut string, " ").unwrap();
Expand Down Expand Up @@ -218,15 +217,114 @@ impl CairoCompilationUnit {
pub fn target_dir(&self, ws: &Workspace<'_>) -> Filesystem {
ws.target_dir().child(self.profile.as_str())
}

/// Rewrite single compilation unit with multiple targets, into multiple compilation units
/// with single targets.
pub fn rewrite_to_single_source_paths(&self) -> Vec<Self> {
let rewritten_main = self
.main_component()
.targets
.iter()
.map(|target| {
let mut main = self.main_component().clone();
main.targets = vec![target.clone()];
main
})
.collect_vec();

let mut components = self.components.clone();
components.remove(0);

rewritten_main
.into_iter()
.map(|component| {
let mut unit = self.clone();
unit.components = vec![component];
unit.components.extend(components.clone());
unit
})
.collect_vec()
}
}

impl CompilationUnitComponent {
/// Validate input and create new [CompilationUnitComponent] instance.
pub fn try_new(
package: Package,
targets: Vec<Target>,
cfg_set: Option<CfgSet>,
) -> Result<Self> {
ensure!(
!targets.is_empty(),
"a compilation unit component must have at least one target"
);
ensure!(
targets
.iter()
.map(|t| &t.kind)
.collect::<std::collections::HashSet<_>>()
.len()
== 1,
"all targets in a compilation unit component must have the same kind"
);
ensure!(
targets
.iter()
.map(|t| &t.params)
.all(|p| *p == targets[0].params),
"all targets in a compilation unit component must have the same params"
);
ensure!(
targets
.iter()
.map(|t| t.source_root())
.all(|p| p == targets[0].source_root()),
"all targets in a compilation unit component must have the same source path parent"
);
if targets.len() > 1 {
ensure!(
targets.iter().all(|t| t.group_id.is_some()),
"all targets in a compilation unit component with multiple targets must have group_id defined"
);
}
Ok(Self {
package,
targets,
cfg_set,
})
}

pub fn first_target(&self) -> &Target {
&self.targets[0]
}

pub fn target_kind(&self) -> TargetKind {
self.first_target().kind.clone()
}

pub fn target_props<'de, P>(&self) -> Result<P>
where
P: Default + Serialize + Deserialize<'de>,
{
self.first_target().props::<P>()
}

pub fn target_name(&self) -> SmolStr {
if self.targets.len() > 1 {
self.first_target().group_id.clone().expect(
"compilation unit component with multiple targets must have group_id defined",
)
} else {
self.first_target().name.clone()
}
}

pub fn cairo_package_name(&self) -> SmolStr {
self.package.id.name.to_smol_str()
}

fn hash(&self, hasher: &mut impl Hasher) {
self.package.id.hash(hasher);
self.target.hash(hasher);
self.targets.hash(hasher);
}
}
13 changes: 8 additions & 5 deletions scarb/src/compiler/compilers/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl Compiler for LibCompiler {
db: &mut RootDatabase,
ws: &Workspace<'_>,
) -> Result<()> {
let props: Props = unit.target().props()?;
let props: Props = unit.main_component().target_props()?;
if !props.sierra && !props.casm && !props.sierra_text {
ws.config().ui().warn(
"Sierra, textual Sierra and CASM lib targets have been disabled, \
Expand Down Expand Up @@ -74,20 +74,23 @@ impl Compiler for LibCompiler {

if props.sierra {
write_json(
format!("{}.sierra.json", unit.target().name).as_str(),
format!("{}.sierra.json", unit.main_component().target_name()).as_str(),
"output file",
&target_dir,
ws,
&sierra_program,
)
.with_context(|| {
format!("failed to serialize Sierra program {}", unit.target().name)
format!(
"failed to serialize Sierra program {}",
unit.main_component().target_name()
)
})?;
}

if props.sierra_text {
write_string(
format!("{}.sierra", unit.target().name).as_str(),
format!("{}.sierra", unit.main_component().target_name()).as_str(),
"output file",
&target_dir,
ws,
Expand Down Expand Up @@ -121,7 +124,7 @@ impl Compiler for LibCompiler {
};

write_string(
format!("{}.casm", unit.target().name).as_str(),
format!("{}.casm", unit.main_component().target_name()).as_str(),
"output file",
&target_dir,
ws,
Expand Down
4 changes: 2 additions & 2 deletions scarb/src/compiler/compilers/starknet_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ impl Compiler for StarknetContractCompiler {
db: &mut RootDatabase,
ws: &Workspace<'_>,
) -> Result<()> {
let props: Props = unit.target().props()?;
let props: Props = unit.main_component().target_props()?;
if !props.sierra && !props.casm {
ws.config().ui().warn(
"both Sierra and CASM Starknet contract targets have been disabled, \
Expand Down Expand Up @@ -276,7 +276,7 @@ impl Compiler for StarknetContractCompiler {
let mut artifacts = StarknetArtifacts::default();
let mut file_stem_calculator = ContractFileStemCalculator::new(contract_paths);

let target_name = &unit.target().name;
let target_name = &unit.main_component().target_name();
for (decl, class, casm_class) in izip!(contracts, classes, casm_classes) {
let contract_name = decl.submodule_id.name(db.upcast_mut());
let contract_path = decl.module_id().full_path(db.upcast_mut());
Expand Down
2 changes: 1 addition & 1 deletion scarb/src/compiler/compilers/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl Compiler for TestCompiler {

{
let _ = trace_span!("serialize_test").enter();
let file_name = format!("{}.test.json", unit.target().name);
let file_name = format!("{}.test.json", unit.main_component().target_name());
write_json(
&file_name,
"output file",
Expand Down
45 changes: 29 additions & 16 deletions scarb/src/compiler/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,37 +65,50 @@ fn load_plugins(
///
/// This approach allows compiling crates that do not define `lib.cairo` file.
/// For example, single file crates can be created this way.
/// The actual single file module is defined as `mod` item in created lib file.
/// The actual single file modules are defined as `mod` items in created lib file.
fn inject_virtual_wrapper_lib(db: &mut RootDatabase, unit: &CairoCompilationUnit) -> Result<()> {
let components: Vec<&CompilationUnitComponent> = unit
.components
.iter()
.filter(|component| !component.package.id.is_core())
// Skip components defining the default source path, as they already define lib.cairo files.
.filter(|component| {
component
.target
.source_path
.file_name()
.map(|file_name| file_name != DEFAULT_MODULE_MAIN_FILE)
.unwrap_or(false)
!component.targets.is_empty()
&& (component.targets.len() > 1
|| component
.first_target()
.source_path
.file_name()
.map(|file_name| file_name != DEFAULT_MODULE_MAIN_FILE)
.unwrap_or(false))
})
.collect();

for component in components {
let crate_name = component.cairo_package_name();
let crate_id = db.intern_crate(CrateLongId::Real(crate_name));
let file_stem = component.target.source_path.file_stem().ok_or_else(|| {
anyhow!(
"failed to get file stem for component {}",
component.target.source_path
)
})?;
let file_stems = component
.targets
.iter()
.map(|target| {
target
.source_path
.file_stem()
.map(|file_stem| format!("mod {file_stem};"))
.ok_or_else(|| {
anyhow!(
"failed to get file stem for component {}",
target.source_path
)
})
})
.collect::<Result<Vec<_>>>()?;
let content = file_stems.join("\n");
let module_id = ModuleId::CrateRoot(crate_id);
let file_id = db.module_main_file(module_id).unwrap();
// Inject virtual lib file wrapper.
db.as_files_group_mut()
.override_file_content(file_id, Some(Arc::new(format!("mod {file_stem};"))));
.override_file_content(file_id, Some(Arc::new(content)));
}

Ok(())
Expand All @@ -109,7 +122,7 @@ fn build_project_config(unit: &CairoCompilationUnit) -> Result<ProjectConfig> {
.map(|component| {
(
component.cairo_package_name(),
component.target.source_root().into(),
component.first_target().source_root().into(),
)
})
.collect();
Expand Down Expand Up @@ -142,7 +155,7 @@ fn build_project_config(unit: &CairoCompilationUnit) -> Result<ProjectConfig> {

let corelib = unit
.core_package_component()
.map(|core| Directory::Real(core.target.source_root().into()));
.map(|core| Directory::Real(core.first_target().source_root().into()));

let content = ProjectConfigContent {
crate_roots,
Expand Down
2 changes: 1 addition & 1 deletion scarb/src/compiler/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl CompilerRepository {
db: &mut RootDatabase,
ws: &Workspace<'_>,
) -> Result<()> {
let target_kind = &unit.target().kind;
let target_kind = &unit.main_component().target_kind();
let Some(compiler) = self.compilers.get(target_kind.as_str()) else {
bail!("unknown compiler for target `{target_kind}`");
};
Expand Down
14 changes: 10 additions & 4 deletions scarb/src/ops/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,13 @@ where
let compilation_units = ops::generate_compilation_units(&resolve, &opts.features, ws)?
.into_iter()
.filter(|cu| {
let is_excluded = opts.exclude_targets.contains(&cu.target().kind);
let is_included =
opts.include_targets.is_empty() || opts.include_targets.contains(&cu.target().kind);
let is_excluded = opts
.exclude_targets
.contains(&cu.main_component().target_kind());
let is_included = opts.include_targets.is_empty()
|| opts
.include_targets
.contains(&cu.main_component().target_kind());
let is_selected = packages.contains(&cu.main_package_id());
let is_cairo_plugin = matches!(cu, CompilationUnit::ProcMacro(_));
is_cairo_plugin || (is_selected && is_included && !is_excluded)
Expand Down Expand Up @@ -204,7 +208,9 @@ fn check_starknet_dependency(
// `starknet` dependency will error in 99% real-world Starknet contract projects.
// I think we can get away with emitting false positives for users who write raw contracts
// without using Starknet code generators. Such people shouldn't do what they do 😁
if unit.target().kind == TargetKind::STARKNET_CONTRACT && !has_starknet_plugin(db) {
if unit.main_component().target_kind() == TargetKind::STARKNET_CONTRACT
&& !has_starknet_plugin(db)
{
ws.config().ui().warn(formatdoc! {
r#"
package `{package_name}` declares `starknet-contract` target, but does not depend on `starknet` package
Expand Down
Loading

0 comments on commit 58f6658

Please sign in to comment.