@@ -94,9 +94,7 @@ use parity_wasm::elements::{
9494} ;
9595use semver:: Version ;
9696use std:: {
97- collections:: VecDeque ,
9897 fs,
99- io,
10098 path:: {
10199 Path ,
102100 PathBuf ,
@@ -112,6 +110,19 @@ pub const DEFAULT_MAX_MEMORY_PAGES: u32 = 16;
112110/// Version of the currently executing `cargo-contract` binary.
113111const VERSION : & str = env ! ( "CARGO_PKG_VERSION" ) ;
114112
113+ /// Configuration of the linting module.
114+ ///
115+ /// Ensure it is kept up-to-date when updating `cargo-contract`.
116+ pub ( crate ) mod linting {
117+ /// Toolchain used to build ink_linting:
118+ /// https://github.com/paritytech/ink/blob/master/linting/rust-toolchain.toml
119+ pub const TOOLCHAIN_VERSION : & str = "nightly-2023-12-28" ;
120+ /// Git repository with ink_linting libraries
121+ pub const GIT_URL : & str = "https://github.com/paritytech/ink/" ;
122+ /// Git revision number of the linting crate
123+ pub const GIT_REV : & str = "1c029a153ead15cd0bd76631613967c9e679e0c1" ;
124+ }
125+
115126/// Arguments to use when executing `build` or `check` commands.
116127#[ derive( Clone ) ]
117128pub struct ExecuteArgs {
@@ -125,7 +136,7 @@ pub struct ExecuteArgs {
125136 pub unstable_flags : UnstableFlags ,
126137 pub optimization_passes : Option < OptimizationPasses > ,
127138 pub keep_debug_symbols : bool ,
128- pub dylint : bool ,
139+ pub extra_lints : bool ,
129140 pub output_type : OutputType ,
130141 pub skip_wasm_validation : bool ,
131142 pub target : Target ,
@@ -145,7 +156,7 @@ impl Default for ExecuteArgs {
145156 unstable_flags : Default :: default ( ) ,
146157 optimization_passes : Default :: default ( ) ,
147158 keep_debug_symbols : Default :: default ( ) ,
148- dylint : Default :: default ( ) ,
159+ extra_lints : Default :: default ( ) ,
149160 output_type : Default :: default ( ) ,
150161 skip_wasm_validation : Default :: default ( ) ,
151162 target : Default :: default ( ) ,
@@ -289,14 +300,8 @@ fn exec_cargo_for_onchain_target(
289300 "--target-dir={}" ,
290301 crate_metadata. target_directory. to_string_lossy( )
291302 ) ;
292-
293- let mut args = vec ! [
294- format!( "--target={}" , target. llvm_target( ) ) ,
295- "-Zbuild-std=core,alloc" . to_owned( ) ,
296- "--no-default-features" . to_owned( ) ,
297- "--release" . to_owned( ) ,
298- target_dir,
299- ] ;
303+ let mut args = vec ! [ target_dir, "--release" . to_owned( ) ] ;
304+ args. extend ( onchain_cargo_options ( target) ) ;
300305 network. append_to_args ( & mut args) ;
301306
302307 let mut features = features. clone ( ) ;
@@ -344,10 +349,13 @@ fn exec_cargo_for_onchain_target(
344349 env. push ( ( "CARGO_ENCODED_RUSTFLAGS" , Some ( rustflags) ) ) ;
345350 } ;
346351
347- let cargo =
348- util:: cargo_cmd ( command, & args, manifest_path. directory ( ) , * verbosity, env) ;
349-
350- invoke_cargo_and_scan_for_error ( cargo)
352+ execute_cargo ( util:: cargo_cmd (
353+ command,
354+ & args,
355+ manifest_path. directory ( ) ,
356+ * verbosity,
357+ env,
358+ ) )
351359 } ;
352360
353361 if unstable_flags. original_manifest {
@@ -433,7 +441,7 @@ fn check_buffer_size_invoke_cargo_clean(
433441 "Detected a change in the configured buffer size. Rebuilding the project."
434442 . bold( )
435443 ) ;
436- invoke_cargo_and_scan_for_error ( cargo) ?;
444+ execute_cargo ( cargo) ?;
437445 }
438446 Err ( _) => {
439447 verbose_eprintln ! (
@@ -443,7 +451,7 @@ fn check_buffer_size_invoke_cargo_clean(
443451 "Cannot find the previous size of the static buffer. Rebuilding the project."
444452 . bold( )
445453 ) ;
446- invoke_cargo_and_scan_for_error ( cargo) ?;
454+ execute_cargo ( cargo) ?;
447455 }
448456 }
449457 }
@@ -452,59 +460,22 @@ fn check_buffer_size_invoke_cargo_clean(
452460
453461/// Executes the supplied cargo command, reading the output and scanning for known errors.
454462/// Writes the captured stderr back to stderr and maintains the cargo tty progress bar.
455- fn invoke_cargo_and_scan_for_error ( cargo : duct:: Expression ) -> Result < ( ) > {
456- macro_rules! eprintln_red {
457- ( $value: expr) => { {
458- use colored:: Colorize as _;
459- :: std:: eprintln!( "{}" , $value. bright_red( ) . bold( ) ) ;
460- } } ;
463+ fn execute_cargo ( cargo : duct:: Expression ) -> Result < ( ) > {
464+ match cargo. unchecked ( ) . run ( ) {
465+ Ok ( out) if out. status . success ( ) => Ok ( ( ) ) ,
466+ Ok ( out) => anyhow:: bail!( String :: from_utf8_lossy( & out. stderr) . to_string( ) ) ,
467+ Err ( e) => anyhow:: bail!( "Cannot run `cargo` command: {:?}" , e) ,
461468 }
462-
463- // unchecked: Even capture output on non exit return status
464- let cargo = util:: cargo_tty_output ( cargo) . unchecked ( ) ;
465-
466- let missing_main_err = "error[E0601]" . as_bytes ( ) ;
467- let mut err_buf = VecDeque :: with_capacity ( missing_main_err. len ( ) ) ;
468-
469- let mut reader = cargo. stderr_to_stdout ( ) . reader ( ) ?;
470- let mut buffer = [ 0u8 ; 1 ] ;
471-
472- loop {
473- let bytes_read = io:: Read :: read ( & mut reader, & mut buffer) ?;
474- for byte in buffer[ 0 ..bytes_read] . iter ( ) {
475- err_buf. push_back ( * byte) ;
476- if err_buf. len ( ) > missing_main_err. len ( ) {
477- let byte = err_buf. pop_front ( ) . expect ( "buffer is not empty" ) ;
478- io:: Write :: write ( & mut io:: stderr ( ) , & [ byte] ) ?;
479- }
480- }
481- if missing_main_err == err_buf. make_contiguous ( ) {
482- eprintln_red ! ( "\n Exited with error: [E0601]" ) ;
483- eprintln_red ! (
484- "Your contract must be annotated with the `no_main` attribute.\n "
485- ) ;
486- eprintln_red ! ( "Examples how to do this:" ) ;
487- eprintln_red ! ( " - `#![cfg_attr(not(feature = \" std\" ), no_std, no_main)]`" ) ;
488- eprintln_red ! ( " - `#[no_main]`\n " ) ;
489- return Err ( anyhow:: anyhow!( "missing `no_main` attribute" ) )
490- }
491- if bytes_read == 0 {
492- // flush the remaining buffered bytes
493- io:: Write :: write ( & mut io:: stderr ( ) , err_buf. make_contiguous ( ) ) ?;
494- break
495- }
496- buffer = [ 0u8 ; 1 ] ;
497- }
498- Ok ( ( ) )
499469}
500470
501- /// Run linting steps which include `clippy` (mandatory) + `dylint` (optional).
471+ /// Run linting that involves two steps: `clippy` and `dylint`. Both are mandatory as
472+ /// they're part of the compilation process and implement security-critical features.
502473fn lint (
503- dylint : bool ,
474+ extra_lints : bool ,
504475 crate_metadata : & CrateMetadata ,
476+ target : & Target ,
505477 verbosity : & Verbosity ,
506478) -> Result < ( ) > {
507- // mandatory: Always run clippy.
508479 verbose_eprintln ! (
509480 verbosity,
510481 " {} {}" ,
@@ -513,16 +484,19 @@ fn lint(
513484 ) ;
514485 exec_cargo_clippy ( crate_metadata, * verbosity) ?;
515486
516- // optional: Dylint only on demand (for now).
517- if dylint {
487+ // TODO (jubnzv): Dylint needs a custom toolchain installed by the user. Currently,
488+ // it's required only for RiscV target. We're working on the toolchain integration
489+ // and will make this step mandatory for all targets in future releases.
490+ if extra_lints || matches ! ( target, Target :: RiscV ) {
518491 verbose_eprintln ! (
519492 verbosity,
520493 " {} {}" ,
521494 "[==]" . bold( ) ,
522495 "Checking ink! linting rules" . bright_green( ) . bold( )
523496 ) ;
524- exec_cargo_dylint ( crate_metadata, * verbosity) ?;
497+ exec_cargo_dylint ( extra_lints , crate_metadata, target , * verbosity) ?;
525498 }
499+
526500 Ok ( ( ) )
527501}
528502
@@ -537,7 +511,7 @@ fn exec_cargo_clippy(crate_metadata: &CrateMetadata, verbosity: Verbosity) -> Re
537511 "-Dclippy::arithmetic_side_effects" ,
538512 ] ;
539513 // we execute clippy with the plain manifest no temp dir required
540- invoke_cargo_and_scan_for_error ( util:: cargo_cmd (
514+ execute_cargo ( util:: cargo_cmd (
541515 "clippy" ,
542516 args,
543517 crate_metadata. manifest_path . directory ( ) ,
@@ -546,11 +520,25 @@ fn exec_cargo_clippy(crate_metadata: &CrateMetadata, verbosity: Verbosity) -> Re
546520 ) )
547521}
548522
523+ /// Returns a list of cargo options used for on-chain builds
524+ fn onchain_cargo_options ( target : & Target ) -> Vec < String > {
525+ vec ! [
526+ format!( "--target={}" , target. llvm_target( ) ) ,
527+ "-Zbuild-std=core,alloc" . to_owned( ) ,
528+ "--no-default-features" . to_owned( ) ,
529+ ]
530+ }
531+
549532/// Inject our custom lints into the manifest and execute `cargo dylint` .
550533///
551534/// We create a temporary folder, extract the linting driver there and run
552535/// `cargo dylint` with it.
553- fn exec_cargo_dylint ( crate_metadata : & CrateMetadata , verbosity : Verbosity ) -> Result < ( ) > {
536+ fn exec_cargo_dylint (
537+ extra_lints : bool ,
538+ crate_metadata : & CrateMetadata ,
539+ target : & Target ,
540+ verbosity : Verbosity ,
541+ ) -> Result < ( ) > {
554542 check_dylint_requirements ( crate_metadata. manifest_path . directory ( ) ) ?;
555543
556544 // `dylint` is verbose by default, it doesn't have a `--verbose` argument,
@@ -559,8 +547,20 @@ fn exec_cargo_dylint(crate_metadata: &CrateMetadata, verbosity: Verbosity) -> Re
559547 Verbosity :: Default | Verbosity :: Quiet => Verbosity :: Quiet ,
560548 } ;
561549
550+ let mut args = if extra_lints {
551+ vec ! [
552+ "--lib=ink_linting_mandatory" . to_owned( ) ,
553+ "--lib=ink_linting" . to_owned( ) ,
554+ ]
555+ } else {
556+ vec ! [ "--lib=ink_linting_mandatory" . to_owned( ) ]
557+ } ;
558+ args. push ( "--" . to_owned ( ) ) ;
559+ // Pass on-chain build options to ensure the linter expands all conditional `cfg_attr`
560+ // macros, as it does for the release build.
561+ args. extend ( onchain_cargo_options ( target) ) ;
562+
562563 let target_dir = & crate_metadata. target_directory . to_string_lossy ( ) ;
563- let args = vec ! [ "--lib=ink_linting" ] ;
564564 let env = vec ! [
565565 // We need to set the `CARGO_TARGET_DIR` environment variable in
566566 // case `cargo dylint` is invoked.
@@ -578,7 +578,7 @@ fn exec_cargo_dylint(crate_metadata: &CrateMetadata, verbosity: Verbosity) -> Re
578578
579579 Workspace :: new ( & crate_metadata. cargo_meta , & crate_metadata. root_package . id ) ?
580580 . with_root_package_manifest ( |manifest| {
581- manifest. with_dylint ( ) ?. with_empty_workspace ( ) ;
581+ manifest. with_dylint ( ) ?;
582582 Ok ( ( ) )
583583 } ) ?
584584 . using_temp ( |manifest_path| {
@@ -599,7 +599,8 @@ fn exec_cargo_dylint(crate_metadata: &CrateMetadata, verbosity: Verbosity) -> Re
599599/// Checks if all requirements for `dylint` are installed.
600600///
601601/// We require both `cargo-dylint` and `dylint-link` because the driver is being
602- /// built at runtime on demand.
602+ /// built at runtime on demand. These must be built using a custom version of the
603+ /// toolchain, as the linter utilizes the unstable rustc API.
603604///
604605/// This function takes a `_working_dir` which is only used for unit tests.
605606fn check_dylint_requirements ( _working_dir : Option < & Path > ) -> Result < ( ) > {
@@ -621,6 +622,29 @@ fn check_dylint_requirements(_working_dir: Option<&Path>) -> Result<()> {
621622 } )
622623 } ;
623624
625+ // Check if the required toolchain is present and is installed with `rustup`.
626+ if let Ok ( output) = Command :: new ( "rustup" ) . arg ( "toolchain" ) . arg ( "list" ) . output ( ) {
627+ anyhow:: ensure!(
628+ String :: from_utf8_lossy( & output. stdout) . contains( linting:: TOOLCHAIN_VERSION ) ,
629+ format!(
630+ "Toolchain `{0}` was not found!\n \
631+ This specific version is required to provide additional source code analysis.\n \n
632+ You can install it by executing `rustup install {0}`." ,
633+ linting:: TOOLCHAIN_VERSION ,
634+ )
635+ . to_string( )
636+ . bright_yellow( ) ) ;
637+ } else {
638+ anyhow:: bail!( format!(
639+ "Toolchain `{0}` was not found!\n \
640+ This specific version is required to provide additional source code analysis.\n \n
641+ Install `rustup` according to https://rustup.rs/ and then run: `rustup install {0}`." ,
642+ linting:: TOOLCHAIN_VERSION ,
643+ )
644+ . to_string( )
645+ . bright_yellow( ) ) ;
646+ }
647+
624648 // when testing this function we should never fall back to a `cargo` specified
625649 // in the env variable, as this would mess with the mocked binaries.
626650 #[ cfg( not( test) ) ]
@@ -793,7 +817,7 @@ pub fn execute(args: ExecuteArgs) -> Result<BuildResult> {
793817 build_artifact,
794818 unstable_flags,
795819 optimization_passes,
796- dylint ,
820+ extra_lints ,
797821 output_type,
798822 target,
799823 ..
@@ -838,7 +862,7 @@ pub fn execute(args: ExecuteArgs) -> Result<BuildResult> {
838862 let ( opt_result, metadata_result, dest_wasm) = match build_artifact {
839863 BuildArtifacts :: CheckOnly => {
840864 // Check basically means only running our linter without building.
841- lint ( * dylint , & crate_metadata, verbosity) ?;
865+ lint ( * extra_lints , & crate_metadata, target , verbosity) ?;
842866 ( None , None , None )
843867 }
844868 BuildArtifacts :: CodeOnly => {
@@ -912,7 +936,7 @@ fn local_build(
912936 network,
913937 unstable_flags,
914938 keep_debug_symbols,
915- dylint ,
939+ extra_lints ,
916940 skip_wasm_validation,
917941 target,
918942 max_memory_pages,
@@ -921,7 +945,7 @@ fn local_build(
921945
922946 // We always want to lint first so we don't suppress any warnings when a build is
923947 // skipped because of a matching fingerprint.
924- lint ( * dylint , crate_metadata, verbosity) ?;
948+ lint ( * extra_lints , crate_metadata, target , verbosity) ?;
925949
926950 let pre_fingerprint = Fingerprint :: new ( crate_metadata) ?;
927951
0 commit comments