#[cfg(feature = "std")]
use alloc::{borrow::ToOwned, format, string::ToString, vec};
use alloc::{string::String, sync::Arc, vec::Vec};
#[cfg(feature = "std")]
use std::ffi::OsString;
#[cfg(feature = "std")]
use clap::{Parser, builder::ArgPredicate};
use midenc_session::{
ColorChoice, DebugInfo, InputFile, IrFilter, LinkLibrary, OptLevel, Options, OutputFile,
OutputType, OutputTypeSpec, OutputTypes, Path, PathBuf, ProjectType, Session, TargetEnv,
Verbosity, Warnings, add_target_link_libraries,
diagnostics::{DefaultSourceManager, Emitter},
};
#[derive(Debug)]
#[cfg_attr(feature = "std", derive(Parser))]
#[cfg_attr(feature = "std", command(name = "midenc"))]
pub struct Compiler {
#[cfg_attr(
feature = "std",
arg(
long,
value_name = "DIR",
env = "MIDENC_TARGET_DIR",
default_value = "target/midenc",
help_heading = "Output"
)
)]
pub target_dir: PathBuf,
#[cfg_attr(
feature = "std",
arg(long, value_name = "DIR", help_heading = "Output")
)]
pub working_dir: Option<PathBuf>,
#[cfg_attr(
feature = "std",
arg(
long,
value_name = "DIR",
env = "MIDENC_SYSROOT",
help_heading = "Compiler"
)
)]
pub sysroot: Option<PathBuf>,
#[cfg_attr(
feature = "std",
arg(
long,
short = 'O',
value_name = "DIR",
env = "MIDENC_OUT_DIR",
help_heading = "Output"
)
)]
pub output_dir: Option<PathBuf>,
#[cfg_attr(
feature = "std",
arg(long, short = 'o', value_name = "FILENAME", help_heading = "Output")
)]
pub output_file: Option<PathBuf>,
#[cfg_attr(
feature = "std",
arg(long, conflicts_with("output_file"), help_heading = "Output")
)]
pub stdout: bool,
#[cfg_attr(feature = "std", arg(
long,
short = 'n',
value_name = "NAME",
default_value = None,
help_heading = "Diagnostics"
))]
pub name: Option<String>,
#[cfg_attr(feature = "std", arg(
long = "verbose",
short = 'v',
value_enum,
value_name = "LEVEL",
default_value_t = Verbosity::Info,
default_missing_value = "debug",
num_args(0..=1),
help_heading = "Diagnostics"
))]
pub verbosity: Verbosity,
#[cfg_attr(feature = "std", arg(
long,
short = 'W',
value_enum,
value_name = "LEVEL",
default_value_t = Warnings::All,
default_missing_value = "all",
num_args(0..=1),
help_heading = "Diagnostics"
))]
pub warn: Warnings,
#[cfg_attr(feature = "std", arg(
long,
value_enum,
default_value_t = ColorChoice::Auto,
default_missing_value = "auto",
num_args(0..=1),
help_heading = "Diagnostics"
))]
pub color: ColorChoice,
#[cfg_attr(feature = "std", arg(
long,
value_name = "TARGET",
default_value_t = TargetEnv::Base,
help_heading = "Compiler"
))]
pub target: TargetEnv,
#[cfg_attr(feature = "std", arg(long, help_heading = "Compiler", hide(true)))]
pub entrypoint: Option<String>,
#[cfg_attr(feature = "std", arg(
long = "exe",
default_value_t = true,
default_value_ifs([
// When targeting the rollup, never build an executable
("target", "rollup".into(), Some("false")),
// Setting the entrypoint implies building an executable in all other cases
("entrypoint", ArgPredicate::IsPresent, Some("true")),
]),
help_heading = "Linker"
))]
pub is_program: bool,
#[cfg_attr(feature = "std", arg(
long = "lib",
conflicts_with("is_program"),
conflicts_with("entrypoint"),
default_value_t = false,
default_value_ifs([
// When an entrypoint is specified, always set the default to false
("entrypoint", ArgPredicate::IsPresent, Some("false")),
// When targeting the rollup, we always build as a library
("target", "rollup".into(), Some("true")),
]),
help_heading = "Linker"
))]
pub is_library: bool,
#[cfg_attr(
feature = "std",
arg(
long = "search-path",
short = 'L',
value_name = "PATH",
help_heading = "Linker"
)
)]
pub search_path: Vec<PathBuf>,
#[cfg_attr(
feature = "std",
arg(
long = "link-library",
short = 'l',
value_name = "[KIND=]NAME",
value_delimiter = ',',
next_line_help(true),
help_heading = "Linker"
)
)]
pub link_libraries: Vec<LinkLibrary>,
#[cfg_attr(
feature = "std",
arg(
long = "emit",
value_name = "SPEC",
value_delimiter = ',',
env = "MIDENC_EMIT",
next_line_help(true),
help_heading = "Output"
)
)]
pub output_types: Vec<OutputTypeSpec>,
#[cfg_attr(feature = "std", arg(
long,
value_enum,
value_name = "LEVEL",
next_line_help(true),
default_value_t = DebugInfo::Full,
default_missing_value = "full",
num_args(0..=1),
help_heading = "Output"
))]
pub debug: DebugInfo,
#[cfg_attr(feature = "std", arg(
long = "optimize",
value_enum,
value_name = "LEVEL",
next_line_help(true),
default_value_t = OptLevel::None,
default_missing_value = "balanced",
num_args(0..=1),
help_heading = "Output"
))]
pub opt_level: OptLevel,
#[cfg_attr(
feature = "std",
arg(
long,
short = 'C',
value_name = "OPT[=VALUE]",
help_heading = "Compiler"
)
)]
pub codegen: Vec<String>,
#[cfg_attr(
feature = "std",
arg(
long,
short = 'Z',
value_name = "OPT[=VALUE]",
help_heading = "Compiler"
)
)]
pub unstable: Vec<String>,
#[cfg_attr(feature = "std", arg(long, hide = true, default_value_t = false))]
pub release: bool,
#[cfg_attr(feature = "std", arg(long, value_name = "PATH", hide = true))]
pub manifest_path: Option<PathBuf>,
#[cfg_attr(feature = "std", arg(long, hide = true, default_value_t = false))]
pub workspace: bool,
#[cfg_attr(feature = "std", arg(long, value_name = "SPEC", hide = true))]
pub package: Vec<String>,
}
#[derive(Default, Debug, Clone)]
#[cfg_attr(feature = "std", derive(Parser))]
#[cfg_attr(feature = "std", command(name = "-C"))]
pub struct CodegenOptions {
#[cfg_attr(feature = "std", arg(
long,
conflicts_with_all(["analyze_only", "link_only"]),
default_value_t = false,
))]
pub parse_only: bool,
#[cfg_attr(feature = "std", arg(
long,
conflicts_with_all(["parse_only", "link_only"]),
default_value_t = false,
))]
pub analyze_only: bool,
#[cfg_attr(feature = "std", arg(
long,
conflicts_with_all(["no_link"]),
default_value_t = false,
))]
pub link_only: bool,
#[cfg_attr(feature = "std", arg(long, default_value_t = false))]
pub no_link: bool,
}
#[derive(Default, Debug, Clone)]
#[cfg_attr(feature = "std", derive(Parser))]
#[cfg_attr(feature = "std", command(name = "-Z"))]
pub struct UnstableOptions {
#[cfg_attr(
feature = "std",
arg(long, default_value_t = false, help_heading = "Passes")
)]
pub print_cfg_after_all: bool,
#[cfg_attr(
feature = "std",
arg(
long,
value_name = "PASS",
value_delimiter = ',',
help_heading = "Passes"
)
)]
pub print_cfg_after_pass: Vec<String>,
#[cfg_attr(
feature = "std",
arg(
long,
value_name = "STAGE",
value_delimiter = ',',
next_line_help(true),
help_heading = "Passes"
)
)]
pub print_ir_before_stage: Vec<String>,
#[cfg_attr(
feature = "std",
arg(long, default_value_t = false, help_heading = "Passes")
)]
pub print_ir_after_all: bool,
#[cfg_attr(
feature = "std",
arg(
long,
value_name = "PASS",
value_delimiter = ',',
help_heading = "Passes"
)
)]
pub print_ir_after_pass: Vec<String>,
#[cfg_attr(
feature = "std",
arg(long, default_value_t = false, help_heading = "Passes")
)]
pub print_ir_after_modified: bool,
#[cfg_attr(
feature = "std",
arg(
long,
action = clap::ArgAction::Append,
value_name = "FILTER",
value_delimiter = ',',
next_line_help(true),
help_heading = "Passes"
)
)]
pub print_ir_filter: Vec<IrFilter>,
#[cfg_attr(
feature = "std",
arg(
long = "print-hir-source-locations",
default_value_t = false,
help_heading = "Printers"
)
)]
pub print_hir_source_locations: bool,
#[cfg_attr(
feature = "std",
arg(
long = "trim-path-prefix",
value_name = "PATH",
help_heading = "Debugging"
)
)]
pub trim_path_prefixes: Vec<PathBuf>,
}
impl CodegenOptions {
#[cfg(feature = "std")]
fn parse_argv(argv: Vec<String>) -> Self {
let command = <CodegenOptions as clap::CommandFactory>::command()
.no_binary_name(true)
.arg_required_else_help(false)
.help_template(
"\
Available codegen options:
Usage: midenc -C <opt>
{all-args}{after-help}
NOTE: When specifying these options, strip the leading '--'",
);
let argv = if argv.iter().any(|arg| matches!(arg.as_str(), "--help" | "-h" | "help")) {
vec!["--help".to_string()]
} else {
argv.into_iter()
.flat_map(|arg| match arg.split_once('=') {
None => vec![format!("--{arg}")],
Some((opt, value)) => {
vec![format!("--{opt}"), value.to_string()]
}
})
.collect::<Vec<_>>()
};
let mut matches = command.try_get_matches_from(argv).unwrap_or_else(|err| err.exit());
<CodegenOptions as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
.map_err(format_error::<CodegenOptions>)
.unwrap_or_else(|err| err.exit())
}
#[cfg(not(feature = "std"))]
fn parse_argv(_argv: Vec<String>) -> Self {
Self::default()
}
}
impl UnstableOptions {
#[cfg(feature = "std")]
fn parse_argv(argv: Vec<String>) -> Self {
let command = <UnstableOptions as clap::CommandFactory>::command()
.no_binary_name(true)
.arg_required_else_help(false)
.help_template(
"\
Available unstable options:
Usage: midenc -Z <opt>
{all-args}{after-help}
NOTE: When specifying these options, strip the leading '--'",
);
let argv = if argv.iter().any(|arg| matches!(arg.as_str(), "--help" | "-h" | "help")) {
vec!["--help".to_string()]
} else {
argv.into_iter()
.flat_map(|arg| match arg.split_once('=') {
None => vec![format!("--{arg}")],
Some((opt, value)) => {
vec![format!("--{opt}"), value.to_string()]
}
})
.collect::<Vec<_>>()
};
let mut matches = command.try_get_matches_from(argv).unwrap_or_else(|err| err.exit());
<UnstableOptions as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
.map_err(format_error::<UnstableOptions>)
.unwrap_or_else(|err| err.exit())
}
#[cfg(not(feature = "std"))]
fn parse_argv(_argv: Vec<String>) -> Self {
Self::default()
}
}
impl Compiler {
#[cfg(feature = "std")]
pub fn try_parse_from<I, T>(iter: I) -> Result<Self, clap::Error>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let argv = [OsString::from("midenc")]
.into_iter()
.chain(iter.into_iter().map(|arg| arg.into()));
let command = <Self as clap::CommandFactory>::command();
let command = midenc_session::flags::register_flags(command);
let mut matches = command.try_get_matches_from(argv)?;
<Self as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
.map_err(format_error::<Self>)
}
#[cfg(feature = "std")]
pub fn new_session<I, A, S>(inputs: I, emitter: Option<Arc<dyn Emitter>>, argv: A) -> Session
where
I: IntoIterator<Item = InputFile>,
A: IntoIterator<Item = S>,
S: Into<std::ffi::OsString> + Clone,
{
let argv = [OsString::from("midenc")]
.into_iter()
.chain(argv.into_iter().map(|arg| arg.into()));
let command = <Self as clap::CommandFactory>::command();
let command = midenc_session::flags::register_flags(command);
let mut matches = command.try_get_matches_from(argv).unwrap_or_else(|err| err.exit());
let compile_matches = matches.clone();
let opts = <Self as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
.map_err(format_error::<Self>)
.unwrap_or_else(|err| err.exit());
let inputs = inputs.into_iter().collect();
opts.into_session(inputs, emitter).with_extra_flags(compile_matches.into())
}
pub fn into_session(
self,
inputs: Vec<InputFile>,
emitter: Option<Arc<dyn Emitter>>,
) -> Session {
let cwd = self.working_dir.unwrap_or_else(current_dir);
log::trace!(target: "driver", "current working directory = {}", cwd.display());
let output_file = match self.output_file {
Some(path) => Some(OutputFile::Real(path)),
None if self.stdout => Some(OutputFile::Stdout),
None => None,
};
let mut output_types = OutputTypes::new(self.output_types).unwrap_or_else(|err| err.exit());
let has_final_output =
output_types.keys().any(|ty| matches!(ty, OutputType::Mast | OutputType::Masp));
if !has_final_output {
output_types.insert(OutputType::Masp, output_file.clone());
} else if output_file.is_some() && output_types.get(&OutputType::Masp).is_some() {
output_types.insert(OutputType::Masp, output_file.clone());
}
let project_type = match self.target {
TargetEnv::Rollup {
target:
midenc_session::RollupTarget::Account
| midenc_session::RollupTarget::NoteScript
| midenc_session::RollupTarget::AuthComponent,
} => ProjectType::Library,
_ if self.is_program => ProjectType::Program,
_ => ProjectType::Library,
};
let codegen = CodegenOptions::parse_argv(self.codegen);
let unstable = UnstableOptions::parse_argv(self.unstable);
let mut options = Options::new(self.name, self.target, project_type, cwd, self.sysroot)
.with_color(self.color)
.with_verbosity(self.verbosity)
.with_warnings(self.warn)
.with_debug_info(self.debug)
.with_optimization(self.opt_level)
.with_output_types(output_types);
options.search_paths = self.search_path;
let link_libraries = add_target_link_libraries(self.link_libraries, &self.target);
options.link_libraries = link_libraries;
options.entrypoint = self.entrypoint;
options.parse_only = codegen.parse_only;
options.analyze_only = codegen.analyze_only;
options.link_only = codegen.link_only;
options.no_link = codegen.no_link;
options.print_cfg_after_all = unstable.print_cfg_after_all;
options.print_cfg_after_pass = unstable.print_cfg_after_pass;
options.print_ir_after_all = unstable.print_ir_after_all;
options.print_ir_after_pass = unstable.print_ir_after_pass;
options.print_ir_after_modified = unstable.print_ir_after_modified;
options.print_ir_filters = unstable.print_ir_filter;
options.print_hir_source_locations = unstable.print_hir_source_locations;
options.trim_path_prefixes = unstable.trim_path_prefixes;
let target_dir = if self.target_dir.is_absolute() {
self.target_dir
} else {
options.current_dir.join(&self.target_dir)
};
create_target_dir(target_dir.as_path());
if inputs.is_empty() {
let cmd = <Compiler as clap::CommandFactory>::command();
let mut err =
clap::Error::new(clap::error::ErrorKind::MissingRequiredArgument).with_cmd(&cmd);
err.insert(
clap::error::ContextKind::InvalidArg,
clap::error::ContextValue::String("INPUT".to_string()),
);
err.exit();
}
let source_manager = Arc::new(DefaultSourceManager::default());
Session::new(
inputs,
self.output_dir,
output_file,
target_dir,
options,
emitter,
source_manager,
)
}
}
#[cfg(feature = "std")]
fn format_error<I: clap::CommandFactory>(err: clap::Error) -> clap::Error {
let mut cmd = I::command();
err.format(&mut cmd)
}
#[cfg(feature = "std")]
fn current_dir() -> PathBuf {
std::env::current_dir().expect("no working directory available")
}
#[cfg(not(feature = "std"))]
fn current_dir() -> PathBuf {
<str as AsRef<Path>>::as_ref(".").to_path_buf()
}
#[cfg(feature = "std")]
fn create_target_dir(path: &Path) {
std::fs::create_dir_all(path)
.unwrap_or_else(|err| panic!("unable to create --target-dir '{}': {err}", path.display()));
}
#[cfg(not(feature = "std"))]
fn create_target_dir(_path: &Path) {}