<commit_before>//! Implements a command for uploading dSYM files.
use std::collections::BTreeSet;
use std::env;
use std::str::{self, FromStr};

use clap::{App, Arg, ArgMatches};
use console::style;
use failure::{bail, err_msg, Error};
use log::info;
use symbolic::common::DebugId;
use symbolic::debuginfo::FileFormat;

use crate::api::Api;
use crate::config::Config;
use crate::utils::args::{validate_id, ArgExt};
use crate::utils::dif::ObjectDifFeatures;
use crate::utils::dif_upload::{DifFormat, DifUpload};
use crate::utils::progress::{ProgressBar, ProgressStyle};
use crate::utils::system::QuietExit;
use crate::utils::xcode::{InfoPlist, MayDetach};

static DERIVED_DATA: &str = "Library/Developer/Xcode/DerivedData";

pub fn make_app<'a, 'b: 'a>(app: App<'a, 'b>) -> App<'a, 'b> {
    app.about("Upload debugging information files.")
        .org_project_args()
        .arg(
            Arg::with_name("paths")
                .value_name("PATH")
                .help("A path to search recursively for symbol files.")
                .multiple(true)
                .number_of_values(1)
                .index(1),
        )
        .arg(
            Arg::with_name("types")
                .long("type")
                .short("t")
                .value_name("TYPE")
                .multiple(true)
                .number_of_values(1)
                .possible_values(&[
                    "dsym",
                    "elf",
                    "breakpad",
                    "pdb",
                    "pe",
                    "sourcebundle",
                    "bcsymbolmap",
                ])
                .help(
                    "Only consider debug information files of the given \
                     type.  By default, all types are considered.",
                ),
        )
        .arg(
            Arg::with_name("no_unwind")
                .long("no-unwind")
                .alias("no-bin")
                .help(
                    "Do not scan for stack unwinding information. Specify \
                     this flag for builds with disabled FPO, or when \
                     stackwalking occurs on the device. This usually \
                     excludes executables and dynamic libraries. They might \
                     still be uploaded, if they contain additional \
                     processable information (see other flags).",
                ),
        )
        .arg(
            Arg::with_name("no_debug")
                .long("no-debug")
                .help(
                    "Do not scan for debugging information. This will \
                     usually exclude debug companion files. They might \
                     still be uploaded, if they contain additional \
                     processable information (see other flags).",
                )
                .conflicts_with("no_unwind"),
        )
        .arg(
            Arg::with_name("no_sources")
                .long("no-sources")
                .help(
                    "Do not scan for source information. This will \
                     usually exclude source bundle files. They might \
                     still be uploaded, if they contain additional \
                     processable information (see other flags).",
                )
                .conflicts_with("no_sources"),
        )
        .arg(
            Arg::with_name("ids")
                .value_name("ID")
                .long("id")
                .help("Search for specific debug identifiers.")
                .validator(validate_id)
                .multiple(true)
                .number_of_values(1),
        )
        .arg(
            Arg::with_name("require_all")
                .long("require-all")
                .help("Errors if not all identifiers specified with --id could be found."),
        )
        .arg(
            Arg::with_name("symbol_maps")
                .long("symbol-maps")
                .value_name("PATH")
                .help(
                    "Optional path to BCSymbolMap files which are used to \
                     resolve hidden symbols in dSYM files downloaded from \
                     iTunes Connect.  This requires the dsymutil tool to be \
                     available.",
                ),
        )
        .arg(
            Arg::with_name("derived_data")
                .long("derived-data")
                .help("Search for debug symbols in Xcode's derived data."),
        )
        .arg(
            Arg::with_name("no_zips")
                .long("no-zips")
                .help("Do not search in ZIP files."),
        )
        .arg(
            Arg::with_name("info_plist")
                .long("info-plist")
                .value_name("PATH")
                .help(
                    "Optional path to the Info.plist.{n}We will try to find this \
                     automatically if run from Xcode.  Providing this information \
                     will associate the debug symbols with a specific ITC application \
                     and build in Sentry.  Note that if you provide the plist \
                     explicitly it must already be processed.",
                ),
        )
        .arg(
            Arg::with_name("no_reprocessing")
                .long("no-reprocessing")
                .help("Do not trigger reprocessing after uploading."),
        )
        .arg(Arg::with_name("no_upload").long("no-upload").help(
            "Disable the actual upload.{n}This runs all steps for the \
             processing but does not trigger the upload (this also \
             automatically disables reprocessing.  This is useful if you \
             just want to verify the setup or skip the upload in tests.",
        ))
        .arg(
            Arg::with_name("force_foreground")
                .long("force-foreground")
                .help(
                    "Wait for the process to finish.{n}\
                     By default, the upload process will detach and continue in the \
                     background when triggered from Xcode.  When an error happens, \
                     a dialog is shown.  If this parameter is passed Xcode will wait \
                     for the process to finish before the build finishes and output \
                     will be shown in the Xcode build output.",
                ),
        )
        .arg(
            Arg::with_name("include_sources")
                .long("include-sources")
                .help(
                    "Include sources from the local file system and upload \
                     them as source bundles.",
                ),
        )
        .arg(Arg::with_name("wait").long("wait").help(
            "Wait for the server to fully process uploaded files. Errors \
             can only be displayed if --wait is specified, but this will \
             significantly slow down the upload process.",
        ))
        .arg(
            Arg::with_name("upload_symbol_maps")
                .long("upload-symbol-maps")
                .help(
                    "Upload any BCSymbolMap files found to allow Sentry to resolve \
                     hidden symbols, e.g. when it downloads dSYMs directly from App \
                     Store Connect or when you upload dSYMs without first resolving \
                     the hidden symbols using --symbol-maps.",
                )
                // NOTE(kamil): Hidden until we make it usable. We cannot remove it as it's already included in sentry-fastlane-plugin.
                // See: https://github.com/getsentry/sentry-cli/issues/1009#issuecomment-911516666
                .hidden(true),
        )
}

fn execute_internal(matches: &ArgMatches<'_>, legacy: bool) -> Result<(), Error> {
    let api = Api::current();
    let config = Config::current();
    let (org, project) = config.get_org_and_project(matches)?;

    let ids = matches
        .values_of("ids")
        .unwrap_or_default()
        .filter_map(|s| DebugId::from_str(s).ok());

    info!(
        "Issuing a command for Organization: {} Project: {}",
        org, project
    );

    // Build generic upload parameters
    let mut upload = DifUpload::new(org.clone(), project.clone());
    upload
        .wait(matches.is_present("wait"))
        .search_paths(matches.values_of("paths").unwrap_or_default())
        .allow_zips(!matches.is_present("no_zips"))
        .filter_ids(ids);

    if legacy {
        // Configure `upload-dsym` behavior (only dSYM files)
        upload
            .filter_format(DifFormat::Object(FileFormat::MachO))
            .filter_features(ObjectDifFeatures {
                debug: true,
                symtab: false,
                unwind: false,
                sources: false,
            });

        if !matches.is_present("paths") {
            if let Some(dsym_path) = env::var_os("DWARF_DSYM_FOLDER_PATH") {
                upload.search_path(dsym_path);
            }
        }
    } else {
        // Restrict symbol types, if specified by the user
        for ty in matches.values_of("types").unwrap_or_default() {
            match ty {
                "dsym" => upload.filter_format(DifFormat::Object(FileFormat::MachO)),
                "elf" => upload.filter_format(DifFormat::Object(FileFormat::Elf)),
                "breakpad" => upload.filter_format(DifFormat::Object(FileFormat::Breakpad)),
                "pdb" => upload.filter_format(DifFormat::Object(FileFormat::Pdb)),
                "pe" => upload.filter_format(DifFormat::Object(FileFormat::Pe)),
                "sourcebundle" => upload.filter_format(DifFormat::Object(FileFormat::SourceBundle)),
                "bcsymbolmap" => {
                    upload.filter_format(DifFormat::BcSymbolMap);
                    upload.filter_format(DifFormat::PList)
                }
                other => bail!("Unsupported type: {}", other),
            };
        }

        upload.filter_features(ObjectDifFeatures {
            // Allow stripped debug symbols. These are dSYMs, ELF binaries generated
            // with `objcopy --only-keep-debug` or Breakpad symbols. As a fallback,
            // we also upload all files with a public symbol table.
            debug: !matches.is_present("no_debug"),
            symtab: !matches.is_present("no_debug"),
            // Allow executables and dynamic/shared libraries, but not object files.
            // They are guaranteed to contain unwind info, for instance `eh_frame`,
            // and may optionally contain debugging information such as DWARF.
            unwind: !matches.is_present("no_unwind"),
            sources: !matches.is_present("no_sources"),
        });

        upload.include_sources(matches.is_present("include_sources"));
    }

    // Configure BCSymbolMap resolution, if possible
    if let Some(symbol_map) = matches.value_of("symbol_maps") {
        upload
            .symbol_map(symbol_map)
            .map_err(|_| err_msg("--symbol-maps requires Apple dsymutil to be available."))?;
    }

    // Add a path to XCode's DerivedData, if configured
    if matches.is_present("derived_data") {
        let derived_data = dirs::home_dir().map(|x| x.join(DERIVED_DATA));
        if let Some(path) = derived_data {
            if path.is_dir() {
                upload.search_path(path);
            }
        }
    }

    // Try to resolve the Info.plist either by path or from Xcode
    let info_plist = match matches.value_of("info_plist") {
        Some(path) => Some(InfoPlist::from_path(path)?),
        None => InfoPlist::discover_from_env()?,
    };

    if matches.is_present("no_upload") {
        println!("{} skipping upload.", style(">").dim());
        return Ok(());
    }

    MayDetach::wrap("Debug symbol upload", |handle| {
        // Optionally detach if run from Xcode
        if !matches.is_present("force_foreground") {
            handle.may_detach()?;
        }

        // Execute the upload
        let (uploaded, has_processing_errors) = upload.upload()?;

        // Associate the dSYMs with the Info.plist data, if available
        if let Some(ref info_plist) = info_plist {
            let progress_style = ProgressStyle::default_spinner()
                .template("{spinner} Associating dSYMs with {msg}...");

            let progress = ProgressBar::new_spinner();
            progress.enable_steady_tick(100);
            progress.set_style(progress_style);
            progress.set_message(&info_plist.to_string());

            let checksums = uploaded.iter().map(|dif| dif.checksum.clone()).collect();
            let response = api.associate_apple_dsyms(&org, &project, info_plist, checksums)?;
            progress.finish_and_clear();

            if let Some(association) = response {
                if association.associated_dsyms.is_empty() {
                    println!("{} No new debug symbols to associate.", style(">").dim());
                } else {
                    println!(
                        "{} Associated {} debug symbols with the build.",
                        style(">").dim(),
                        style(association.associated_dsyms.len()).yellow()
                    );
                }
            } else {
                info!("Server does not support dSYM associations. Ignoring.");
            }
        }

        // Trigger reprocessing only if requested by user
        if matches.is_present("no_reprocessing") {
            println!("{} skipped reprocessing", style(">").dim());
        } else if !api.trigger_reprocessing(&org, &project)? {
            println!("{} Server does not support reprocessing.", style(">").dim());
        }

        // Did we miss explicitly requested symbols?
        if matches.is_present("require_all") {
            let required_ids: BTreeSet<_> = matches
                .values_of("ids")
                .unwrap_or_default()
                .filter_map(|s| DebugId::from_str(s).ok())
                .collect();

            let found_ids = uploaded.into_iter().map(|dif| dif.id()).collect();
            let missing_ids: Vec<_> = required_ids.difference(&found_ids).collect();

            if !missing_ids.is_empty() {
                eprintln!();
                eprintln!("{}", style("Error: Some symbols could not be found!").red());
                eprintln!("The following symbols are still missing:");
                for id in missing_ids {
                    println!("  {}", id);
                }

                return Err(QuietExit(1).into());
            }
        }

        // report a non 0 status code if the server encountered issues.
        if has_processing_errors {
            eprintln!();
            eprintln!("{}", style("Error: some symbols did not process correctly"));
            return Err(QuietExit(1).into());
        }

        Ok(())
    })
}

pub fn execute(matches: &ArgMatches<'_>) -> Result<(), Error> {
    execute_internal(matches, false)
}

pub fn execute_legacy(matches: &ArgMatches<'_>) -> Result<(), Error> {
    execute_internal(matches, true)
}
<commit_msg>docs: Clarify how --symbol-maps is used (#1080)<commit_after>//! Implements a command for uploading dSYM files.
use std::collections::BTreeSet;
use std::env;
use std::str::{self, FromStr};

use clap::{App, Arg, ArgMatches};
use console::style;
use failure::{bail, err_msg, Error};
use log::info;
use symbolic::common::DebugId;
use symbolic::debuginfo::FileFormat;

use crate::api::Api;
use crate::config::Config;
use crate::utils::args::{validate_id, ArgExt};
use crate::utils::dif::ObjectDifFeatures;
use crate::utils::dif_upload::{DifFormat, DifUpload};
use crate::utils::progress::{ProgressBar, ProgressStyle};
use crate::utils::system::QuietExit;
use crate::utils::xcode::{InfoPlist, MayDetach};

static DERIVED_DATA: &str = "Library/Developer/Xcode/DerivedData";

pub fn make_app<'a, 'b: 'a>(app: App<'a, 'b>) -> App<'a, 'b> {
    app.about("Upload debugging information files.")
        .org_project_args()
        .arg(
            Arg::with_name("paths")
                .value_name("PATH")
                .help("A path to search recursively for symbol files.")
                .multiple(true)
                .number_of_values(1)
                .index(1),
        )
        .arg(
            Arg::with_name("types")
                .long("type")
                .short("t")
                .value_name("TYPE")
                .multiple(true)
                .number_of_values(1)
                .possible_values(&[
                    "dsym",
                    "elf",
                    "breakpad",
                    "pdb",
                    "pe",
                    "sourcebundle",
                    "bcsymbolmap",
                ])
                .help(
                    "Only consider debug information files of the given \
                     type.  By default, all types are considered.",
                ),
        )
        .arg(
            Arg::with_name("no_unwind")
                .long("no-unwind")
                .alias("no-bin")
                .help(
                    "Do not scan for stack unwinding information. Specify \
                     this flag for builds with disabled FPO, or when \
                     stackwalking occurs on the device. This usually \
                     excludes executables and dynamic libraries. They might \
                     still be uploaded, if they contain additional \
                     processable information (see other flags).",
                ),
        )
        .arg(
            Arg::with_name("no_debug")
                .long("no-debug")
                .help(
                    "Do not scan for debugging information. This will \
                     usually exclude debug companion files. They might \
                     still be uploaded, if they contain additional \
                     processable information (see other flags).",
                )
                .conflicts_with("no_unwind"),
        )
        .arg(
            Arg::with_name("no_sources")
                .long("no-sources")
                .help(
                    "Do not scan for source information. This will \
                     usually exclude source bundle files. They might \
                     still be uploaded, if they contain additional \
                     processable information (see other flags).",
                )
                .conflicts_with("no_sources"),
        )
        .arg(
            Arg::with_name("ids")
                .value_name("ID")
                .long("id")
                .help("Search for specific debug identifiers.")
                .validator(validate_id)
                .multiple(true)
                .number_of_values(1),
        )
        .arg(
            Arg::with_name("require_all")
                .long("require-all")
                .help("Errors if not all identifiers specified with --id could be found."),
        )
        .arg(
            Arg::with_name("symbol_maps")
                .long("symbol-maps")
                .value_name("PATH")
                .help(
                    "Optional path to BCSymbolMap files which are used to \
                     resolve hidden symbols in dSYM files downloaded from \
                     iTunes Connect.  This requires the dsymutil tool to be \
                     available.  This should not be used when using the App \
                     Store Connect integration, the .bcsymbolmap files needed \
                     for the integration are uploaded without this option if \
                     they are found in the PATH searched for symbol files.",
                ),
        )
        .arg(
            Arg::with_name("derived_data")
                .long("derived-data")
                .help("Search for debug symbols in Xcode's derived data."),
        )
        .arg(
            Arg::with_name("no_zips")
                .long("no-zips")
                .help("Do not search in ZIP files."),
        )
        .arg(
            Arg::with_name("info_plist")
                .long("info-plist")
                .value_name("PATH")
                .help(
                    "Optional path to the Info.plist.{n}We will try to find this \
                     automatically if run from Xcode.  Providing this information \
                     will associate the debug symbols with a specific ITC application \
                     and build in Sentry.  Note that if you provide the plist \
                     explicitly it must already be processed.",
                ),
        )
        .arg(
            Arg::with_name("no_reprocessing")
                .long("no-reprocessing")
                .help("Do not trigger reprocessing after uploading."),
        )
        .arg(Arg::with_name("no_upload").long("no-upload").help(
            "Disable the actual upload.{n}This runs all steps for the \
             processing but does not trigger the upload (this also \
             automatically disables reprocessing.  This is useful if you \
             just want to verify the setup or skip the upload in tests.",
        ))
        .arg(
            Arg::with_name("force_foreground")
                .long("force-foreground")
                .help(
                    "Wait for the process to finish.{n}\
                     By default, the upload process will detach and continue in the \
                     background when triggered from Xcode.  When an error happens, \
                     a dialog is shown.  If this parameter is passed Xcode will wait \
                     for the process to finish before the build finishes and output \
                     will be shown in the Xcode build output.",
                ),
        )
        .arg(
            Arg::with_name("include_sources")
                .long("include-sources")
                .help(
                    "Include sources from the local file system and upload \
                     them as source bundles.",
                ),
        )
        .arg(Arg::with_name("wait").long("wait").help(
            "Wait for the server to fully process uploaded files. Errors \
             can only be displayed if --wait is specified, but this will \
             significantly slow down the upload process.",
        ))
        .arg(
            Arg::with_name("upload_symbol_maps")
                .long("upload-symbol-maps")
                .help(
                    "Upload any BCSymbolMap files found to allow Sentry to resolve \
                     hidden symbols, e.g. when it downloads dSYMs directly from App \
                     Store Connect or when you upload dSYMs without first resolving \
                     the hidden symbols using --symbol-maps.",
                )
                // NOTE(kamil): Hidden until we make it usable. We cannot remove it as it's already included in sentry-fastlane-plugin.
                // See: https://github.com/getsentry/sentry-cli/issues/1009#issuecomment-911516666
                .hidden(true),
        )
}

fn execute_internal(matches: &ArgMatches<'_>, legacy: bool) -> Result<(), Error> {
    let api = Api::current();
    let config = Config::current();
    let (org, project) = config.get_org_and_project(matches)?;

    let ids = matches
        .values_of("ids")
        .unwrap_or_default()
        .filter_map(|s| DebugId::from_str(s).ok());

    info!(
        "Issuing a command for Organization: {} Project: {}",
        org, project
    );

    // Build generic upload parameters
    let mut upload = DifUpload::new(org.clone(), project.clone());
    upload
        .wait(matches.is_present("wait"))
        .search_paths(matches.values_of("paths").unwrap_or_default())
        .allow_zips(!matches.is_present("no_zips"))
        .filter_ids(ids);

    if legacy {
        // Configure `upload-dsym` behavior (only dSYM files)
        upload
            .filter_format(DifFormat::Object(FileFormat::MachO))
            .filter_features(ObjectDifFeatures {
                debug: true,
                symtab: false,
                unwind: false,
                sources: false,
            });

        if !matches.is_present("paths") {
            if let Some(dsym_path) = env::var_os("DWARF_DSYM_FOLDER_PATH") {
                upload.search_path(dsym_path);
            }
        }
    } else {
        // Restrict symbol types, if specified by the user
        for ty in matches.values_of("types").unwrap_or_default() {
            match ty {
                "dsym" => upload.filter_format(DifFormat::Object(FileFormat::MachO)),
                "elf" => upload.filter_format(DifFormat::Object(FileFormat::Elf)),
                "breakpad" => upload.filter_format(DifFormat::Object(FileFormat::Breakpad)),
                "pdb" => upload.filter_format(DifFormat::Object(FileFormat::Pdb)),
                "pe" => upload.filter_format(DifFormat::Object(FileFormat::Pe)),
                "sourcebundle" => upload.filter_format(DifFormat::Object(FileFormat::SourceBundle)),
                "bcsymbolmap" => {
                    upload.filter_format(DifFormat::BcSymbolMap);
                    upload.filter_format(DifFormat::PList)
                }
                other => bail!("Unsupported type: {}", other),
            };
        }

        upload.filter_features(ObjectDifFeatures {
            // Allow stripped debug symbols. These are dSYMs, ELF binaries generated
            // with `objcopy --only-keep-debug` or Breakpad symbols. As a fallback,
            // we also upload all files with a public symbol table.
            debug: !matches.is_present("no_debug"),
            symtab: !matches.is_present("no_debug"),
            // Allow executables and dynamic/shared libraries, but not object files.
            // They are guaranteed to contain unwind info, for instance `eh_frame`,
            // and may optionally contain debugging information such as DWARF.
            unwind: !matches.is_present("no_unwind"),
            sources: !matches.is_present("no_sources"),
        });

        upload.include_sources(matches.is_present("include_sources"));
    }

    // Configure BCSymbolMap resolution, if possible
    if let Some(symbol_map) = matches.value_of("symbol_maps") {
        upload
            .symbol_map(symbol_map)
            .map_err(|_| err_msg("--symbol-maps requires Apple dsymutil to be available."))?;
    }

    // Add a path to XCode's DerivedData, if configured
    if matches.is_present("derived_data") {
        let derived_data = dirs::home_dir().map(|x| x.join(DERIVED_DATA));
        if let Some(path) = derived_data {
            if path.is_dir() {
                upload.search_path(path);
            }
        }
    }

    // Try to resolve the Info.plist either by path or from Xcode
    let info_plist = match matches.value_of("info_plist") {
        Some(path) => Some(InfoPlist::from_path(path)?),
        None => InfoPlist::discover_from_env()?,
    };

    if matches.is_present("no_upload") {
        println!("{} skipping upload.", style(">").dim());
        return Ok(());
    }

    MayDetach::wrap("Debug symbol upload", |handle| {
        // Optionally detach if run from Xcode
        if !matches.is_present("force_foreground") {
            handle.may_detach()?;
        }

        // Execute the upload
        let (uploaded, has_processing_errors) = upload.upload()?;

        // Associate the dSYMs with the Info.plist data, if available
        if let Some(ref info_plist) = info_plist {
            let progress_style = ProgressStyle::default_spinner()
                .template("{spinner} Associating dSYMs with {msg}...");

            let progress = ProgressBar::new_spinner();
            progress.enable_steady_tick(100);
            progress.set_style(progress_style);
            progress.set_message(&info_plist.to_string());

            let checksums = uploaded.iter().map(|dif| dif.checksum.clone()).collect();
            let response = api.associate_apple_dsyms(&org, &project, info_plist, checksums)?;
            progress.finish_and_clear();

            if let Some(association) = response {
                if association.associated_dsyms.is_empty() {
                    println!("{} No new debug symbols to associate.", style(">").dim());
                } else {
                    println!(
                        "{} Associated {} debug symbols with the build.",
                        style(">").dim(),
                        style(association.associated_dsyms.len()).yellow()
                    );
                }
            } else {
                info!("Server does not support dSYM associations. Ignoring.");
            }
        }

        // Trigger reprocessing only if requested by user
        if matches.is_present("no_reprocessing") {
            println!("{} skipped reprocessing", style(">").dim());
        } else if !api.trigger_reprocessing(&org, &project)? {
            println!("{} Server does not support reprocessing.", style(">").dim());
        }

        // Did we miss explicitly requested symbols?
        if matches.is_present("require_all") {
            let required_ids: BTreeSet<_> = matches
                .values_of("ids")
                .unwrap_or_default()
                .filter_map(|s| DebugId::from_str(s).ok())
                .collect();

            let found_ids = uploaded.into_iter().map(|dif| dif.id()).collect();
            let missing_ids: Vec<_> = required_ids.difference(&found_ids).collect();

            if !missing_ids.is_empty() {
                eprintln!();
                eprintln!("{}", style("Error: Some symbols could not be found!").red());
                eprintln!("The following symbols are still missing:");
                for id in missing_ids {
                    println!("  {}", id);
                }

                return Err(QuietExit(1).into());
            }
        }

        // report a non 0 status code if the server encountered issues.
        if has_processing_errors {
            eprintln!();
            eprintln!("{}", style("Error: some symbols did not process correctly"));
            return Err(QuietExit(1).into());
        }

        Ok(())
    })
}

pub fn execute(matches: &ArgMatches<'_>) -> Result<(), Error> {
    execute_internal(matches, false)
}

pub fn execute_legacy(matches: &ArgMatches<'_>) -> Result<(), Error> {
    execute_internal(matches, true)
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

pub type DOMString = ~str;
pub type StaticStringVec = &'static [&'static str];

pub fn null_str_as_empty(s: &Option<DOMString>) -> DOMString {
    // We don't use map_default because it would allocate ~"" even for Some.
    match *s {
        Some(ref s) => s.clone(),
        None => ~""
    }
}

pub fn null_str_as_empty_ref<'a>(s: &'a Option<DOMString>) -> &'a str {
    match *s {
        Some(ref s) => s.as_slice(),
        None => &'a ""
    }
}

pub fn is_whitespace(s: &str) -> bool {
    s.chars().all(|c| match c {
        '\u0020' | '\u0009' | '\u000D' | '\u000A' => true,
        _ => false
    })
}

/// A "space character" according to:
///
///     http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#
///     space-character
pub static HTML_SPACE_CHARACTERS: [char, ..5] = [
    '\u0020',
    '\u0009',
    '\u000a',
    '\u000c',
    '\u000d',
];

<commit_msg>Implement split_html_space_chars helper function<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::iter::Filter;
use std::str::CharSplits;

pub type DOMString = ~str;
pub type StaticCharVec = &'static [char];
pub type StaticStringVec = &'static [&'static str];

pub fn null_str_as_empty(s: &Option<DOMString>) -> DOMString {
    // We don't use map_default because it would allocate ~"" even for Some.
    match *s {
        Some(ref s) => s.clone(),
        None => ~""
    }
}

pub fn null_str_as_empty_ref<'a>(s: &'a Option<DOMString>) -> &'a str {
    match *s {
        Some(ref s) => s.as_slice(),
        None => &'a ""
    }
}

pub fn is_whitespace(s: &str) -> bool {
    s.chars().all(|c| match c {
        '\u0020' | '\u0009' | '\u000D' | '\u000A' => true,
        _ => false
    })
}

/// A "space character" according to:
///
///     http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#
///     space-character
pub static HTML_SPACE_CHARACTERS: StaticCharVec = &[
    '\u0020',
    '\u0009',
    '\u000a',
    '\u000c',
    '\u000d',
];

pub fn split_html_space_chars<'a>(s: &'a str) -> Filter<'a, &'a str, CharSplits<'a, StaticCharVec>> {
    s.split(HTML_SPACE_CHARACTERS).filter(|&split| !split.is_empty())
}
<|endoftext|>


<commit_before>//! A LinkedList that lets you insert elements from the front or the end of the list.
//! TBD: Add the ability to insert in-between a Node.
//! Lots of thanks to reem and Sharp for help with this from the #rust channel

use std::mem;

/// A Node is the basic unit in a LinkedList which stores the payload of type T.
#[derive(Debug,PartialEq)]
struct Node<T> {
    /// The payload of type T that is wrapped aorund a Node.
    pub payload: T,
    /// An Option of a boxed Node<T> that would point to the next Node<T>.
    next: Option<Box<Node<T>>>,
    /// An Option of a boxed Node<T> that would point to the previous Node<T>.
    prev: Option<Box<Node<T>>>,
}

impl<T> Node<T> {

    pub fn new(payload: T) -> Node<T> {
        //! Creates a instance of a `Node` with `next` and `prev` point to a Option::Node.
        Node::<T> {
            payload: payload,
            next: None,
            prev: None,
        }
    }

    pub fn payload(&mut self) -> Option<&T> {
        //! Access to a payload of Node.
        Some(&self.payload)
    }

    pub fn insert_after(&mut self, mut new: Box<Node<T>>) -> Option<Box<Node<T>>> {
        //! Takes a new boxed Node<T> and swaps it with any existing Node after the current one.
        //! We replace any Option::None or Option::Some with a new Option<Box<Node<T>>>
        mem::swap(&mut new.next, &mut self.next);
        mem::replace(&mut self.next, Some(new))
    }

    pub fn insert_before(&mut self, mut new: Box<Node<T>>) -> Option<Box<Node<T>>> {
        //! Takes a new boxed Node<T> and swaps it with any existing Node after the current one.
        //! We replace any Option::None or Option::Some with a new Option<Box<Node<T>>>
        mem::swap(&mut new.prev, &mut self.prev);
        mem::replace(&mut self.prev, Some(new))
    }

    pub fn next(&mut self) -> Option<&mut T> {
        //! Goes to the next Node and returns an Option to it.
        let Node {
            ref mut payload,
            ref mut prev,
            ref mut next,
        } = *self;

        match *next {
            Some(ref mut next) => {
                mem::swap(&mut next.payload, payload);

                let mut next = &mut **next;     // Cannot completely understand this bit yet.
                mem::swap(&mut next.next, &mut next.prev);

                mem::swap(&mut next.prev, prev);
            },
            None    => return None,
        }
        mem::swap(prev, next);
        Some(payload)
    }
}

/// A LinkedList is the struct that you would need to use to create a list of elements.
/// It contains a pointer to the first and last node in the list represented as `head and `tail`
/// respectively.
pub struct LinkedList<T> {
    /// First element in the list.
    head: Option<Box<Node<T>>>,
    /// Last element in the list.
    tail: Option<Box<Node<T>>>,
}

impl<T> LinkedList<T> {
    pub fn new() -> LinkedList<T> {
        /// Create a new LinkedList to contain a payloads with type T.
        LinkedList::<T> {
            head: None,
            tail: None,
        }
    }
}

#[test]
fn node_new() {
    let expected = Node {
        payload: "test",
        next: None,
        prev: None,
    };

    let node: Node<&str> = Node::new("test");

    assert_eq!(expected.payload,  node.payload);
    assert_eq!(expected.next,     node.next);
    assert_eq!(expected.prev,     node.prev);
}

#[test]
fn node_payload() {
    let node: Node<&str> = Node::new("test");
    assert_eq!(&"test", node.payload().unwrap());
}

#[test]
fn node_insert_after() {
    let mut node: Node<&str> = Node::new("one");
    node.insert_after(Box::new(Node::new("two")));
    assert_eq!("two", node.next.unwrap().payload);

}

#[test]
fn node_insert_before() {
    let mut node: Node<&str> = Node::new("two");
    node.insert_before(Box::new(Node::new("one")));
    assert_eq!("one", node.prev.unwrap().payload);
}

#[cfg(test)]
mod tests {
    use super::*;

}
<commit_msg>Added remove_before and remove_after for Node<commit_after>//! A LinkedList that lets you insert elements from the front or the end of the list.
//! TODO: Add the ability to insert in-between a Node.
//! Lots of thanks to reem and Sharp for help with this from the #rust channel

use std::mem;

/// A Node is the basic unit in a LinkedList which stores the payload of type T.
#[derive(Debug,PartialEq)]
struct Node<T> {
    /// The payload of type T that is wrapped aorund a Node.
    pub payload: T,
    /// An Option of a boxed Node<T> that would point to the next Node<T>.
    next: Option<Box<Node<T>>>,
    /// An Option of a boxed Node<T> that would point to the previous Node<T>.
    prev: Option<Box<Node<T>>>,
}

impl<T> Node<T> {

    pub fn new(payload: T) -> Node<T> {
        //! Creates a instance of a `Node` with `next` and `prev` point to a Option::Node.
        Node::<T> {
            payload: payload,
            next: None,
            prev: None,
        }
    }

    pub fn payload(&mut self) -> Option<&T> {
        //! Access to a payload of Node.
        Some(&self.payload)
    }

    pub fn insert_after(&mut self, mut new: Box<Node<T>>) -> Option<Box<Node<T>>> {
        //! Takes a new boxed Node<T> and swaps it with any existing Node after the current one.
        //! We replace any Option::None or Option::Some with a new Option<Box<Node<T>>>
        mem::swap(&mut new.next, &mut self.next);
        mem::replace(&mut self.next, Some(new))
    }

    pub fn insert_before(&mut self, mut new: Box<Node<T>>) -> Option<Box<Node<T>>> {
        //! Takes a new boxed Node<T> and swaps it with any existing Node after the current one.
        //! We replace any Option::None or Option::Some with a new Option<Box<Node<T>>>
        mem::swap(&mut new.prev, &mut self.prev);
        mem::replace(&mut self.prev, Some(new))
    }

    /*pub fn remove_after(&mut self) -> Option<Box<Node<T>>> {
        //! Removes the Node after the current node.
        match self.next.take() {
            Some(mut next)  => {
                mem::swap(&mut self.next, &mut next.next);
                Some(next)
            },
            None => None,
        }
    }*/

    pub fn remove_before(&mut self) -> Option<Box<Node<T>>> {
        //! Removes the Node before the current node.
        match self.prev.take() {
            Some(mut prev)  => {
                mem::swap(&mut self.prev, &mut prev.prev);
                Some(prev)
            },
            None => None,
        }
    }

    pub fn next(&mut self) -> Option<&mut T> {
        //! Goes to the next Node and returns an Option to it.
        let Node {
            ref mut payload,
            ref mut prev,
            ref mut next,
        } = *self;

        match *next {
            Some(ref mut next) => {
                mem::swap(&mut next.payload, payload);

                let mut next = &mut **next;     // Cannot completely understand this bit yet.
                mem::swap(&mut next.next, &mut next.prev);

                mem::swap(&mut next.prev, prev);
            },
            None    => return None,
        }
        mem::swap(prev, next);
        Some(payload)
    }
}

/// A LinkedList is the struct that you would need to use to create a list of elements.
/// It contains a pointer to the first and last node in the list represented as `head and `tail`
/// respectively.
pub struct LinkedList<T> {
    /// First element in the list.
    head: Option<Box<Node<T>>>,
    /// Last element in the list.
    tail: Option<Box<Node<T>>>,
}

impl<T> LinkedList<T> {
    pub fn new() -> LinkedList<T> {
        //! Create a new LinkedList to contain a payloads with type T.
        LinkedList::<T> {
            head: None,
            tail: None,
        }
    }
}

#[test]
fn node_new() {
    let expected = Node {
        payload: "test",
        next: None,
        prev: None,
    };

    let node: Node<&str> = Node::new("test");

    assert_eq!(expected.payload,  node.payload);
    assert_eq!(expected.next,     node.next);
    assert_eq!(expected.prev,     node.prev);
}

#[test]
fn node_payload() {
    let node: Node<&str> = Node::new("test");
    assert_eq!(&"test", node.payload().unwrap());
}

#[test]
fn node_insert_after() {
    let mut node: Node<&str> = Node::new("one");
    node.insert_after(Box::new(Node::new("two")));
    assert_eq!("two", node.next.unwrap().payload);

}

#[test]
fn node_insert_before() {
    let mut node: Node<&str> = Node::new("two");
    node.insert_before(Box::new(Node::new("one")));
    assert_eq!("one", node.prev.unwrap().payload);
}

#[cfg(test)]
mod tests {
    use super::*;

}
<|endoftext|>


<commit_before>
use super::DocBuilder;
use {DocBuilderError, get_package, source_path, copy_dir, copy_doc_dir};
use db::{connect_db, add_package_into_database, add_build_into_database};
use cargo::core::Package;
use std::process::{Command, Output};
use std::path::PathBuf;

use regex::Regex;

type CommandResult = Result<String, String>;

#[derive(Debug)]
pub struct ChrootBuilderResult {
    pub output: String,
    pub build_success: bool,
    pub have_doc: bool,
    pub rustc_version: String,
    pub cratesfyi_version: String,
}

impl DocBuilder {
    /// Builds every package documentation in chroot environment
    pub fn build_world(&self) -> Result<(), DocBuilderError> {
        self.crates(|name, version| {
            if let Err(err) = self.build_package(name, version) {
                info!("Failed to build package {}-{}: {}", name, version, err);
            }
        })
    }

    /// Builds package documentation in chroot environment and adds into cratesfyi database
    pub fn build_package(&self, name: &str, version: &str) -> Result<(), DocBuilderError> {

        // TODO: Add skip option to check if we need to
        // skip package according to DocBuilderOptions

        info!("Building package {}-{}", name, version);

        // get_package (and cargo) is using semver, add '=' in front of version.
        let pkg = try!(get_package(name, Some(&format!("={}", version)[..])));
        let res = self.build_package_in_chroot(&pkg);

        // copy sources and documentation
        try!(self.copy_sources(&pkg));
        if res.have_doc {
            try!(self.copy_documentation(&pkg, &res.rustc_version));
        }

        // Database connection
        let conn = try!(connect_db());
        let release_id = try!(add_package_into_database(&conn, &pkg, &res));
        try!(add_build_into_database(&conn, &release_id, &res));

        // remove source and build directory after we are done
        try!(self.remove_build_dir(&pkg));

        Ok(())
    }

    /// Builds documentation of a package with cratesfyi in chroot environment
    fn build_package_in_chroot(&self, package: &Package) -> ChrootBuilderResult {
        let (rustc_version, cratesfyi_version) = self.get_versions();
        let cmd = format!("cratesfyi doc {} ={}",
                          package.manifest().name(),
                          package.manifest().version());
        match self.chroot_command(cmd) {
            Ok(o) => {
                ChrootBuilderResult {
                    output: o,
                    build_success: true,
                    have_doc: self.have_documentation(&package),
                    rustc_version: rustc_version,
                    cratesfyi_version: cratesfyi_version,
                }
            }
            Err(e) => {
                ChrootBuilderResult {
                    output: e,
                    build_success: false,
                    have_doc: false,
                    rustc_version: rustc_version,
                    cratesfyi_version: cratesfyi_version,
                }
            }
        }
    }

    /// Copies source files of a package into source_path
    fn copy_sources(&self, package: &Package) -> Result<(), DocBuilderError> {
        let destination = PathBuf::from(&self.options.sources_path).join(canonical_name(&package));
        // unwrap is safe here, this function will be always called after get_package
        match copy_dir(source_path(&package).unwrap(), &destination) {
            Ok(_) => Ok(()),
            Err(e) => Err(DocBuilderError::Io(e)),
        }
    }

    /// Copies documentation to destination directory
    fn copy_documentation(&self,
                          package: &Package,
                          rustc_version: &str)
                          -> Result<(), DocBuilderError> {
        let crate_doc_path = PathBuf::from(&self.options.chroot_path)
                                 .join("home")
                                 .join(&self.options.chroot_user)
                                 .join(canonical_name(&package));
        let destination = PathBuf::from(&self.options.destination).join(format!("{}/{}",
                          package.manifest().name(),
                          package.manifest().version()));
        copy_doc_dir(crate_doc_path,
                     destination,
                     parse_rustc_version(rustc_version).trim())
            .map_err(DocBuilderError::Io)
    }

    /// Removes build directory of a package
    fn remove_build_dir(&self, package: &Package) -> Result<(), DocBuilderError> {
        let _ = self.chroot_command(format!("rm -rf {}", canonical_name(&package)));
        Ok(())
    }

    /// Runs a command in a chroot environment
    fn chroot_command<T: AsRef<str>>(&self, cmd: T) -> CommandResult {
        command_result(Command::new("sudo")
                           .arg("lxc-attach")
                           .arg("-n")
                           .arg(&self.options.container_name)
                           .arg("--")
                           .arg("su")
                           .arg("-")
                           .arg(&self.options.chroot_user)
                           .arg("-c")
                           .arg(cmd.as_ref())
                           .output()
                           .unwrap())
    }

    /// Checks a package build directory to determine if package have docs
    ///
    /// This function is checking first target in targets to see if documentation exists for a
    /// crate. Package must be successfully built in chroot environment first.
    fn have_documentation(&self, package: &Package) -> bool {
        let crate_doc_path = PathBuf::from(&self.options.chroot_path)
                                 .join("home")
                                 .join(&self.options.chroot_user)
                                 .join(canonical_name(&package))
                                 .join("doc")
                                 .join(package.targets()[0].name().to_string());
        crate_doc_path.exists()
    }

    /// Gets rustc and cratesfyi version from chroot environment
    fn get_versions(&self) -> (String, String) {
        // It is safe to use expect here
        // chroot environment must always have rustc and cratesfyi installed
        (String::from(self.chroot_command("rustc --version")
                          .expect("Failed to get rustc version")
                          .trim()),
         String::from(self.chroot_command("cratesfyi --version")
                          .expect("Failed to get cratesfyi version")
                          .trim()))
    }
}

/// Simple function to capture command output
fn command_result(output: Output) -> CommandResult {
    let mut command_out = String::from_utf8_lossy(&output.stdout).into_owned();
    command_out.push_str(&String::from_utf8_lossy(&output.stderr).into_owned()[..]);
    match output.status.success() {
        true => Ok(command_out),
        false => Err(command_out),
    }
}

/// Returns canonical name of a package.
///
/// It's just package-version. All directory structure used in cratesfyi is
/// following this naming scheme.
fn canonical_name(package: &Package) -> String {
    format!("{}-{}",
            package.manifest().name(),
            package.manifest().version())
}

/// Parses rustc commit hash from rustc version string
fn parse_rustc_version<S: AsRef<str>>(version: S) -> String {
    let version_regex = Regex::new(r" ([\w-.]+) \((\w+) (\d+)-(\d+)-(\d+)\)").unwrap();
    let captures = version_regex.captures(version.as_ref()).expect("Failed to parse rustc version");

    format!("{}{}{}-{}-{}",
            captures.at(3).unwrap(),
            captures.at(4).unwrap(),
            captures.at(5).unwrap(),
            captures.at(1).unwrap(),
            captures.at(2).unwrap())
}

#[cfg(test)]
mod test {
    extern crate env_logger;
    use super::parse_rustc_version;
    use std::path::PathBuf;
    use {DocBuilder, DocBuilderOptions};

    #[test]
    #[ignore]
    fn test_build_world() {
        let _ = env_logger::init();
        let options = DocBuilderOptions::from_prefix(PathBuf::from("../cratesfyi-prefix"));
        let docbuilder = DocBuilder::new(options);
        // This test is building WHOLE WORLD and may take forever
        assert!(docbuilder.build_world().is_ok());
    }

    #[test]
    #[ignore]
    fn test_build_package() {
        let _ = env_logger::init();
        let options = DocBuilderOptions::from_prefix(PathBuf::from("../cratesfyi-prefix"));
        let docbuilder = DocBuilder::new(options);
        let res = docbuilder.build_package("rand", "0.3.14");
        assert!(res.is_ok());
    }

    #[test]
    fn test_parse_rustc_version() {
        assert_eq!(parse_rustc_version("rustc 1.10.0-nightly (57ef01513 2016-05-23)"),
                   "20160523-1.10.0-nightly-57ef01513");
        assert_eq!(parse_rustc_version("cratesfyi 0.2.0 (ba9ae23 2016-05-26)"),
                   "20160526-0.2.0-ba9ae23");
    }
}
<commit_msg>Use subdir in source path<commit_after>
use super::DocBuilder;
use {DocBuilderError, get_package, source_path, copy_dir, copy_doc_dir};
use db::{connect_db, add_package_into_database, add_build_into_database};
use cargo::core::Package;
use std::process::{Command, Output};
use std::path::PathBuf;

use regex::Regex;

type CommandResult = Result<String, String>;

#[derive(Debug)]
pub struct ChrootBuilderResult {
    pub output: String,
    pub build_success: bool,
    pub have_doc: bool,
    pub rustc_version: String,
    pub cratesfyi_version: String,
}

impl DocBuilder {
    /// Builds every package documentation in chroot environment
    pub fn build_world(&self) -> Result<(), DocBuilderError> {
        self.crates(|name, version| {
            if let Err(err) = self.build_package(name, version) {
                info!("Failed to build package {}-{}: {}", name, version, err);
            }
        })
    }

    /// Builds package documentation in chroot environment and adds into cratesfyi database
    pub fn build_package(&self, name: &str, version: &str) -> Result<(), DocBuilderError> {

        // TODO: Add skip option to check if we need to
        // skip package according to DocBuilderOptions

        info!("Building package {}-{}", name, version);

        // get_package (and cargo) is using semver, add '=' in front of version.
        let pkg = try!(get_package(name, Some(&format!("={}", version)[..])));
        let res = self.build_package_in_chroot(&pkg);

        // copy sources and documentation
        try!(self.copy_sources(&pkg));
        if res.have_doc {
            try!(self.copy_documentation(&pkg, &res.rustc_version));
        }

        // Database connection
        let conn = try!(connect_db());
        let release_id = try!(add_package_into_database(&conn, &pkg, &res));
        try!(add_build_into_database(&conn, &release_id, &res));

        // remove source and build directory after we are done
        try!(self.remove_build_dir(&pkg));

        Ok(())
    }

    /// Builds documentation of a package with cratesfyi in chroot environment
    fn build_package_in_chroot(&self, package: &Package) -> ChrootBuilderResult {
        let (rustc_version, cratesfyi_version) = self.get_versions();
        let cmd = format!("cratesfyi doc {} ={}",
                          package.manifest().name(),
                          package.manifest().version());
        match self.chroot_command(cmd) {
            Ok(o) => {
                ChrootBuilderResult {
                    output: o,
                    build_success: true,
                    have_doc: self.have_documentation(&package),
                    rustc_version: rustc_version,
                    cratesfyi_version: cratesfyi_version,
                }
            }
            Err(e) => {
                ChrootBuilderResult {
                    output: e,
                    build_success: false,
                    have_doc: false,
                    rustc_version: rustc_version,
                    cratesfyi_version: cratesfyi_version,
                }
            }
        }
    }

    /// Copies source files of a package into source_path
    fn copy_sources(&self, package: &Package) -> Result<(), DocBuilderError> {
        let destination = PathBuf::from(&self.options.sources_path).join(format!("{}/{}",
                          package.manifest().name(),
                          package.manifest().version()));
        // unwrap is safe here, this function will be always called after get_package
        match copy_dir(source_path(&package).unwrap(), &destination) {
            Ok(_) => Ok(()),
            Err(e) => Err(DocBuilderError::Io(e)),
        }
    }

    /// Copies documentation to destination directory
    fn copy_documentation(&self,
                          package: &Package,
                          rustc_version: &str)
                          -> Result<(), DocBuilderError> {
        let crate_doc_path = PathBuf::from(&self.options.chroot_path)
                                 .join("home")
                                 .join(&self.options.chroot_user)
                                 .join(canonical_name(&package));
        let destination = PathBuf::from(&self.options.destination).join(format!("{}/{}",
                          package.manifest().name(),
                          package.manifest().version()));
        copy_doc_dir(crate_doc_path,
                     destination,
                     parse_rustc_version(rustc_version).trim())
            .map_err(DocBuilderError::Io)
    }

    /// Removes build directory of a package
    fn remove_build_dir(&self, package: &Package) -> Result<(), DocBuilderError> {
        let _ = self.chroot_command(format!("rm -rf {}", canonical_name(&package)));
        Ok(())
    }

    /// Runs a command in a chroot environment
    fn chroot_command<T: AsRef<str>>(&self, cmd: T) -> CommandResult {
        command_result(Command::new("sudo")
                           .arg("lxc-attach")
                           .arg("-n")
                           .arg(&self.options.container_name)
                           .arg("--")
                           .arg("su")
                           .arg("-")
                           .arg(&self.options.chroot_user)
                           .arg("-c")
                           .arg(cmd.as_ref())
                           .output()
                           .unwrap())
    }

    /// Checks a package build directory to determine if package have docs
    ///
    /// This function is checking first target in targets to see if documentation exists for a
    /// crate. Package must be successfully built in chroot environment first.
    fn have_documentation(&self, package: &Package) -> bool {
        let crate_doc_path = PathBuf::from(&self.options.chroot_path)
                                 .join("home")
                                 .join(&self.options.chroot_user)
                                 .join(canonical_name(&package))
                                 .join("doc")
                                 .join(package.targets()[0].name().to_string());
        crate_doc_path.exists()
    }

    /// Gets rustc and cratesfyi version from chroot environment
    fn get_versions(&self) -> (String, String) {
        // It is safe to use expect here
        // chroot environment must always have rustc and cratesfyi installed
        (String::from(self.chroot_command("rustc --version")
                          .expect("Failed to get rustc version")
                          .trim()),
         String::from(self.chroot_command("cratesfyi --version")
                          .expect("Failed to get cratesfyi version")
                          .trim()))
    }
}

/// Simple function to capture command output
fn command_result(output: Output) -> CommandResult {
    let mut command_out = String::from_utf8_lossy(&output.stdout).into_owned();
    command_out.push_str(&String::from_utf8_lossy(&output.stderr).into_owned()[..]);
    match output.status.success() {
        true => Ok(command_out),
        false => Err(command_out),
    }
}

/// Returns canonical name of a package.
///
/// It's just package-version. All directory structure used in cratesfyi is
/// following this naming scheme.
fn canonical_name(package: &Package) -> String {
    format!("{}-{}",
            package.manifest().name(),
            package.manifest().version())
}

/// Parses rustc commit hash from rustc version string
fn parse_rustc_version<S: AsRef<str>>(version: S) -> String {
    let version_regex = Regex::new(r" ([\w-.]+) \((\w+) (\d+)-(\d+)-(\d+)\)").unwrap();
    let captures = version_regex.captures(version.as_ref()).expect("Failed to parse rustc version");

    format!("{}{}{}-{}-{}",
            captures.at(3).unwrap(),
            captures.at(4).unwrap(),
            captures.at(5).unwrap(),
            captures.at(1).unwrap(),
            captures.at(2).unwrap())
}

#[cfg(test)]
mod test {
    extern crate env_logger;
    use super::parse_rustc_version;
    use std::path::PathBuf;
    use {DocBuilder, DocBuilderOptions};

    #[test]
    #[ignore]
    fn test_build_world() {
        let _ = env_logger::init();
        let options = DocBuilderOptions::from_prefix(PathBuf::from("../cratesfyi-prefix"));
        let docbuilder = DocBuilder::new(options);
        // This test is building WHOLE WORLD and may take forever
        assert!(docbuilder.build_world().is_ok());
    }

    #[test]
    #[ignore]
    fn test_build_package() {
        let _ = env_logger::init();
        let options = DocBuilderOptions::from_prefix(PathBuf::from("../cratesfyi-prefix"));
        let docbuilder = DocBuilder::new(options);
        let res = docbuilder.build_package("rand", "0.3.14");
        assert!(res.is_ok());
    }

    #[test]
    fn test_parse_rustc_version() {
        assert_eq!(parse_rustc_version("rustc 1.10.0-nightly (57ef01513 2016-05-23)"),
                   "20160523-1.10.0-nightly-57ef01513");
        assert_eq!(parse_rustc_version("cratesfyi 0.2.0 (ba9ae23 2016-05-26)"),
                   "20160526-0.2.0-ba9ae23");
    }
}
<|endoftext|>


<commit_before>//
// Copyright 2020 The Project Oak Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};

/// Returns a [`TokenStream`] representing the specified Rust type.
///
/// This is necessary because in most cases the type will be a generated protobuf struct, but for
/// instance the `Empty` protobuf is represented as `()` by prost, which is not a valid identifier.
fn type_ident(type_: &str) -> TokenStream {
    if type_ == "()" {
        quote!(())
    } else {
        let t = format_ident!("{}", type_);
        quote!(#t)
    }
}

/// Returns the name to use to refer to the `oak` package.
///
/// When compiling from within the `oak` crate we need to use `crate` to refer to itself.
fn oak_package() -> TokenStream {
    if std::env::var("CARGO_PKG_NAME").unwrap() == "oak" {
        quote!(crate)
    } else {
        quote!(::oak)
    }
}

struct OakServiceGenerator;

impl prost_build::ServiceGenerator for OakServiceGenerator {
    fn generate(&mut self, service: prost_build::Service, out: &mut std::string::String) {
        let oak_package = oak_package();

        let service_name = format_ident!("{}", service.name);
        let service_methods = service.methods.iter().map(|method| {
            let method_name = format_ident!("{}", method.name);
            let input_type = type_ident(&method.input_type);
            let output_type = type_ident(&method.output_type);
            // TODO(#97): Better client-side streaming.
            match (method.client_streaming, method.server_streaming) {
                (false, false) => quote! {
                    fn #method_name(&mut self, req: #input_type) -> #oak_package::grpc::Result<#output_type>
                },
                (false, true) => quote! {
                    fn #method_name(&mut self, req: #input_type, writer: #oak_package::grpc::ChannelResponseWriter)
                },
                (true, false) => quote! {
                    fn #method_name(&mut self, reqs: Vec<#input_type>) -> #oak_package::grpc::Result<#output_type>
                },
                (true, true) => quote! {
                    fn #method_name(&mut self, reqs: Vec<#input_type>, writer: #oak_package::grpc::ChannelResponseWriter)
                },
            }
        });

        let dispatcher_name = format_ident!("{}Dispatcher", service.name);
        let dispatcher_methods = service.methods.iter().map(|method| {
            let method_name = format_ident!("{}", method.name);
            let method_name_string = format!("/{}.{}/{}", service.package, service.proto_name, method.proto_name);
            // Figure out which generic function applies.
            let handle_invocation = match (method.client_streaming, method.server_streaming) {
                (false, false) => quote! {
                    #oak_package::grpc::handle_req_rsp(|r| (self.0).#method_name(r), req, writer)
                },
                (false, true) => quote! {
                    #oak_package::grpc::handle_req_stream(|r, w| (self.0).#method_name(r, w), req, writer)
                },
                (true, false) => quote! {
                    #oak_package::grpc::handle_stream_rsp(|rr| (self.0).#method_name(rr), req, writer)
                },
                (true, true) => quote! {
                    #oak_package::grpc::handle_stream_stream(|rr, w| (self.0).#method_name(rr, w), req, writer)
                },
            };
            quote! {
                #method_name_string => #handle_invocation
            }
        });

        let client_name = format_ident!("{}Client", service.name);
        let client_methods = service.methods.iter().map(|method| {
            let method_name = format_ident!("{}", method.name);
            let input_type = type_ident(&method.input_type);
            let output_type = type_ident(&method.output_type);
            let method_name_string = format!("/{}.{}/{}", service.package, service.proto_name, method.proto_name);
            // TODO(#97): Support client-side streaming.
            #[allow(clippy::match_bool)]
            match method.server_streaming {
                false =>
                    quote! {
                        pub fn #method_name(&self, req: #input_type) -> #oak_package::grpc::Result<#output_type> {
                            #oak_package::grpc::invoke_grpc_method(#method_name_string, &req, &self.0.invocation_sender)
                        }
                    },
                true =>
                    // TODO(#592): ideally the return type for a streaming method would be
                    //   format!("oak::io::Receiver<{}>", self.output_message());
                    // This is not possible with the current io::Receiver's direct use of
                    // the underlying handle.
                    quote! {
                        pub fn #method_name(&self, req: #input_type) -> #oak_package::grpc::Result<#oak_package::io::Receiver<#oak_package::grpc::GrpcResponse>> {
                            #oak_package::grpc::invoke_grpc_method_stream(#method_name_string, &req, &self.0.invocation_sender)
                        }
                    },
            }
        });
        let gen = quote! {
            pub trait #service_name {
                #(#service_methods;)*
            }

            #[allow(dead_code)]
            pub struct #dispatcher_name<T: #service_name>(T);

            #[allow(dead_code)]
            impl <T: #service_name> #dispatcher_name<T> {
                pub fn new(node: T) -> #dispatcher_name<T> {
                    #dispatcher_name(node)
                }
            }

            #[allow(clippy::unit_arg)]
            impl <T: #service_name> #oak_package::grpc::ServerNode for #dispatcher_name<T> {
                fn invoke(&mut self, method: &str, req: &[u8], writer: #oak_package::grpc::ChannelResponseWriter) {
                    match method {
                        #(#dispatcher_methods,)*
                        _ => {
                            ::log::error!("unknown method name: {}", method);
                        }
                    }
                }
            }

            #[allow(dead_code)]
            pub struct #client_name(pub #oak_package::grpc::client::Client);

            #[allow(dead_code)]
            impl #client_name {
                #(#client_methods)*
            }
        };
        out.push_str("\n");
        // TODO(#832): Currently the generated output is squashed on a single line; consider passing
        // it through rustfmt, if it does not increase compile time too much.
        out.push_str(&format!("{}", gen));
        out.push_str("\n");
    }
}

struct AsyncServiceGenerator;

impl prost_build::ServiceGenerator for AsyncServiceGenerator {
    fn generate(&mut self, service: prost_build::Service, out: &mut String) {
        let oak_package = oak_package();
        let name = format_ident!("{}", service.name);
        let service_enum = generate_service_enum(&name, &service);

        let method_matchers = service.methods.iter().map(|method| {
            let method_name = format_ident!("{}", method.proto_name);
            let variant = quote!(#name::#method_name);
            let input_type = type_ident(&method.input_type);
            let input = if method.client_streaming {
                quote!(requests.into_type::<#input_type>())
            } else {
                quote!(requests.into_type::<#input_type>().first().await?)
            };
            let writer = if method.server_streaming {
                quote!(MultiWriter)
            } else {
                quote!(OneshotWriter)
            };
            let output = quote!(oak_async::grpc::#writer::new(response_writer));
            let return_expr = quote!(#variant(#input, #output));
            let method_name_string = format!(
                "/{}.{}/{}",
                service.package, service.proto_name, method.proto_name
            );
            quote! {
                #method_name_string => Ok(#return_expr),
            }
        });
        let service_enum_impl = quote! {
            impl #name {
                #[allow(dead_code)]
                pub async fn from_invocation(invocation: #oak_package::grpc::Invocation) -> Result<#name, #oak_package::OakError> {
                    let ::oak_async::grpc::DecomposedInvocation { method, requests, response_writer } =
                    ::oak_async::grpc::DecomposedInvocation::from(invocation).await?;
                    match method.as_str() {
                        #(#method_matchers)*
                        _ => {
                            ::log::error!("unknown method name: {}", method);
                            Err(#oak_package::OakStatus::ErrInvalidArgs.into())
                        }
                    }
                }
            }
        };

        let gen = quote! {
            pub mod asynchronous {
                use super::*;
                #service_enum

                #service_enum_impl
            }
        };
        out.push_str(&format!("{}", gen));
        out.push_str("\n");

        // Eventually the sync code generation will be removed, but until then we also generate that
        // code.
        OakServiceGenerator.generate(service, out);
    }
}

fn generate_service_enum(enum_name: &Ident, service: &prost_build::Service) -> TokenStream {
    let variants = service.methods.iter().map(|method| {
        let variant = format_ident!("{}", method.proto_name);
        let input_type = type_ident(&method.input_type);
        let output_type = type_ident(&method.output_type);
        let input_type = if method.client_streaming {
            quote!(::oak_async::ChannelReadStream<#input_type>)
        } else {
            quote!(#input_type)
        };
        let output_type = if method.server_streaming {
            quote!(::oak_async::grpc::MultiWriter<#output_type>)
        } else {
            quote!(::oak_async::grpc::OneshotWriter<#output_type>)
        };
        quote!(#variant(#input_type, #output_type))
    });

    quote! {
        #[allow(dead_code)]
        pub enum #enum_name {
            #(#variants,)*
        }
    }
}

/// Options for generating Protocol buffer Rust types.
pub struct ProtoOptions {
    /// Generate Oak-specific service code for inter-node communication.
    ///
    /// Default: **true**.
    ///
    /// Generated code depends on the `oak` SDK crate.
    pub generate_services: bool,

    /// Automatically derive the `HandleVisit` trait from the Oak SDK for generated Protobuf types.
    /// If this is enabled, the generated types can contain handles and can be used to exchange
    /// handles with other nodes using inter-node communication over Protocol Buffers.
    ///
    /// Default: **true**.
    ///
    /// Generated code depends on the `oak` SDK crate.
    pub derive_handle_visit: bool,

    /// Enable service code generated for the async SDK.
    ///
    /// Default: **false**.
    ///
    /// The regular sync code will still be generated if this is enabled.
    ///
    /// This feature is currently experimental.
    pub experimental_async: bool,

    pub out_dir_override: Option<std::path::PathBuf>,
}

/// The default option values.
impl Default for ProtoOptions {
    fn default() -> ProtoOptions {
        ProtoOptions {
            generate_services: true,
            derive_handle_visit: true,
            experimental_async: false,
            out_dir_override: None,
        }
    }
}

/// Build Rust code corresponding to a set of protocol buffer message and service definitions,
/// emitting generated code to crate's `OUT_DIR`.  For gRPC service definitions, this
/// function generates Oak-specific code that is suitable for use inside an Oak Node (i.e.  *not*
/// code that is suitable for use in a normal application running on the host platform).
pub fn compile_protos<P>(inputs: &[P], includes: &[P])
where
    P: AsRef<std::path::Path>,
{
    compile_protos_with_options(inputs, includes, ProtoOptions::default());
}

/// Like `compile_protos`, but allows for configuring options through `ProtoOptions`.
pub fn compile_protos_with_options<P>(inputs: &[P], includes: &[P], options: ProtoOptions)
where
    P: AsRef<std::path::Path>,
{
    set_protoc_env_if_unset();

    for input in inputs {
        // Tell cargo to rerun this build script if the proto file has changed.
        // https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorerun-if-changedpath
        println!("cargo:rerun-if-changed={}", input.as_ref().display());
    }

    let mut prost_config = prost_build::Config::new();
    if options.generate_services {
        if options.experimental_async {
            // AsyncServiceGenerator calls OakServiceGenerator, so the sync code is either way.
            prost_config.service_generator(Box::new(AsyncServiceGenerator));
        } else {
            prost_config.service_generator(Box::new(OakServiceGenerator));
        }
    }
    if options.derive_handle_visit {
        prost_config
            // Auto-derive the HandleVisit trait
            .type_attribute(".", "#[derive(::oak_io::handle::HandleVisit)]")
            // Link relevant Oak protos to the appropirate oak_io and oak_services types.
            .extern_path(".oak.handle", "::oak_io::handle")
            .extern_path(".oak.encap", "::oak_services::proto::oak::encap");
    }
    if let Some(out_dir) = options.out_dir_override {
        prost_config.out_dir(out_dir);
    }
    prost_config
        // We require label-related types to be comparable and hashable so that they can be used in
        // hash-based collections.
        .type_attribute(".oak.label", "#[derive(Eq, Hash)]")
        .type_attribute(
            ".oak.label",
            "#[derive(serde::Deserialize, serde::Serialize)]",
        )
        .type_attribute(".oak.label", "#[serde(rename_all = \"camelCase\")]")
        .compile_protos(inputs, includes)
        .expect("could not run prost-build");
}

/// Options for building gRPC code.
#[derive(Default)]
pub struct CodegenOptions {
    /// Specify whether to build client related code.
    pub build_client: bool,
    /// Specify whether to build server related code.
    pub build_server: bool,
}

/// Generate gRPC code from Protobuf using `tonic` library.
pub fn generate_grpc_code(
    proto_path: &str,
    file_paths: &[&str],
    options: CodegenOptions,
) -> std::io::Result<()> {
    set_protoc_env_if_unset();

    // TODO(#1093): Move all proto generation to a common crate.
    let proto_path = std::path::Path::new(proto_path);
    let file_paths: Vec<std::path::PathBuf> = file_paths
        .iter()
        .map(|file_path| proto_path.join(file_path))
        .collect();

    // Tell cargo to rerun this build script if the proto file has changed.
    // https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorerun-if-changedpath
    for file_path in file_paths.iter() {
        println!("cargo:rerun-if-changed={}", file_path.display());
    }

    // Generate the normal (non-Oak) server and client code for the gRPC service,
    // along with the Rust types corresponding to the message definitions.
    tonic_build::configure()
        .build_client(options.build_client)
        .build_server(options.build_server)
        .compile(&file_paths, &[proto_path.to_path_buf()])
}

fn set_protoc_env_if_unset() {
    if std::env::var("PROTOC").is_err() {
        // Use the system protoc if no override is set, so prost-build does not try to use the
        // bundled one that we remove as part of the vendoring process.
        std::env::set_var("PROTOC", "protoc");
    }
}
<commit_msg>oak_utils: fix warnings<commit_after>//
// Copyright 2020 The Project Oak Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};

/// Returns a [`TokenStream`] representing the specified Rust type.
///
/// This is necessary because in most cases the type will be a generated protobuf struct, but for
/// instance the `Empty` protobuf is represented as `()` by prost, which is not a valid identifier.
fn type_ident(type_: &str) -> TokenStream {
    if type_ == "()" {
        quote!(())
    } else {
        let t = format_ident!("{}", type_);
        quote!(#t)
    }
}

/// Returns the name to use to refer to the `oak` package.
///
/// When compiling from within the `oak` crate we need to use `crate` to refer to itself.
fn oak_package() -> TokenStream {
    if std::env::var("CARGO_PKG_NAME").unwrap() == "oak" {
        quote!(crate)
    } else {
        quote!(::oak)
    }
}

struct OakServiceGenerator;

impl prost_build::ServiceGenerator for OakServiceGenerator {
    fn generate(&mut self, service: prost_build::Service, out: &mut std::string::String) {
        let oak_package = oak_package();

        let service_name = format_ident!("{}", service.name);
        let service_methods = service.methods.iter().map(|method| {
            let method_name = format_ident!("{}", method.name);
            let input_type = type_ident(&method.input_type);
            let output_type = type_ident(&method.output_type);
            // TODO(#97): Better client-side streaming.
            match (method.client_streaming, method.server_streaming) {
                (false, false) => quote! {
                    fn #method_name(&mut self, req: #input_type) -> #oak_package::grpc::Result<#output_type>
                },
                (false, true) => quote! {
                    fn #method_name(&mut self, req: #input_type, writer: #oak_package::grpc::ChannelResponseWriter)
                },
                (true, false) => quote! {
                    fn #method_name(&mut self, reqs: Vec<#input_type>) -> #oak_package::grpc::Result<#output_type>
                },
                (true, true) => quote! {
                    fn #method_name(&mut self, reqs: Vec<#input_type>, writer: #oak_package::grpc::ChannelResponseWriter)
                },
            }
        });

        let dispatcher_name = format_ident!("{}Dispatcher", service.name);
        let dispatcher_methods = service.methods.iter().map(|method| {
            let method_name = format_ident!("{}", method.name);
            let method_name_string = format!("/{}.{}/{}", service.package, service.proto_name, method.proto_name);
            // Figure out which generic function applies.
            let handle_invocation = match (method.client_streaming, method.server_streaming) {
                (false, false) => quote! {
                    #oak_package::grpc::handle_req_rsp(|r| (self.0).#method_name(r), req, writer)
                },
                (false, true) => quote! {
                    #oak_package::grpc::handle_req_stream(|r, w| (self.0).#method_name(r, w), req, writer)
                },
                (true, false) => quote! {
                    #oak_package::grpc::handle_stream_rsp(|rr| (self.0).#method_name(rr), req, writer)
                },
                (true, true) => quote! {
                    #oak_package::grpc::handle_stream_stream(|rr, w| (self.0).#method_name(rr, w), req, writer)
                },
            };
            quote! {
                #method_name_string => #handle_invocation
            }
        });

        let client_name = format_ident!("{}Client", service.name);
        let client_methods = service.methods.iter().map(|method| {
            let method_name = format_ident!("{}", method.name);
            let input_type = type_ident(&method.input_type);
            let output_type = type_ident(&method.output_type);
            let method_name_string = format!("/{}.{}/{}", service.package, service.proto_name, method.proto_name);
            // TODO(#97): Support client-side streaming.
            #[allow(clippy::match_bool)]
            match method.server_streaming {
                false =>
                    quote! {
                        pub fn #method_name(&self, req: #input_type) -> #oak_package::grpc::Result<#output_type> {
                            #oak_package::grpc::invoke_grpc_method(#method_name_string, &req, &self.0.invocation_sender)
                        }
                    },
                true =>
                    // TODO(#592): ideally the return type for a streaming method would be
                    //   format!("oak::io::Receiver<{}>", self.output_message());
                    // This is not possible with the current io::Receiver's direct use of
                    // the underlying handle.
                    quote! {
                        pub fn #method_name(&self, req: #input_type) -> #oak_package::grpc::Result<#oak_package::io::Receiver<#oak_package::grpc::GrpcResponse>> {
                            #oak_package::grpc::invoke_grpc_method_stream(#method_name_string, &req, &self.0.invocation_sender)
                        }
                    },
            }
        });
        let gen = quote! {
            pub trait #service_name {
                #(#service_methods;)*
            }

            #[allow(dead_code)]
            pub struct #dispatcher_name<T: #service_name>(T);

            #[allow(dead_code)]
            impl <T: #service_name> #dispatcher_name<T> {
                pub fn new(node: T) -> #dispatcher_name<T> {
                    #dispatcher_name(node)
                }
            }

            #[allow(clippy::unit_arg)]
            impl <T: #service_name> #oak_package::grpc::ServerNode for #dispatcher_name<T> {
                fn invoke(&mut self, method: &str, req: &[u8], writer: #oak_package::grpc::ChannelResponseWriter) {
                    match method {
                        #(#dispatcher_methods,)*
                        _ => {
                            ::log::error!("unknown method name: {}", method);
                        }
                    }
                }
            }

            #[allow(dead_code)]
            pub struct #client_name(pub #oak_package::grpc::client::Client);

            #[allow(dead_code)]
            impl #client_name {
                #(#client_methods)*
            }
        };
        out.push('\n');
        // TODO(#832): Currently the generated output is squashed on a single line; consider passing
        // it through rustfmt, if it does not increase compile time too much.
        out.push_str(&format!("{}", gen));
        out.push('\n');
    }
}

struct AsyncServiceGenerator;

impl prost_build::ServiceGenerator for AsyncServiceGenerator {
    fn generate(&mut self, service: prost_build::Service, out: &mut String) {
        let oak_package = oak_package();
        let name = format_ident!("{}", service.name);
        let service_enum = generate_service_enum(&name, &service);

        let method_matchers = service.methods.iter().map(|method| {
            let method_name = format_ident!("{}", method.proto_name);
            let variant = quote!(#name::#method_name);
            let input_type = type_ident(&method.input_type);
            let input = if method.client_streaming {
                quote!(requests.into_type::<#input_type>())
            } else {
                quote!(requests.into_type::<#input_type>().first().await?)
            };
            let writer = if method.server_streaming {
                quote!(MultiWriter)
            } else {
                quote!(OneshotWriter)
            };
            let output = quote!(oak_async::grpc::#writer::new(response_writer));
            let return_expr = quote!(#variant(#input, #output));
            let method_name_string = format!(
                "/{}.{}/{}",
                service.package, service.proto_name, method.proto_name
            );
            quote! {
                #method_name_string => Ok(#return_expr),
            }
        });
        let service_enum_impl = quote! {
            impl #name {
                #[allow(dead_code)]
                pub async fn from_invocation(invocation: #oak_package::grpc::Invocation) -> Result<#name, #oak_package::OakError> {
                    let ::oak_async::grpc::DecomposedInvocation { method, requests, response_writer } =
                    ::oak_async::grpc::DecomposedInvocation::from(invocation).await?;
                    match method.as_str() {
                        #(#method_matchers)*
                        _ => {
                            ::log::error!("unknown method name: {}", method);
                            Err(#oak_package::OakStatus::ErrInvalidArgs.into())
                        }
                    }
                }
            }
        };

        let gen = quote! {
            pub mod asynchronous {
                use super::*;
                #service_enum

                #service_enum_impl
            }
        };
        out.push_str(&format!("{}", gen));
        out.push('\n');

        // Eventually the sync code generation will be removed, but until then we also generate that
        // code.
        OakServiceGenerator.generate(service, out);
    }
}

fn generate_service_enum(enum_name: &Ident, service: &prost_build::Service) -> TokenStream {
    let variants = service.methods.iter().map(|method| {
        let variant = format_ident!("{}", method.proto_name);
        let input_type = type_ident(&method.input_type);
        let output_type = type_ident(&method.output_type);
        let input_type = if method.client_streaming {
            quote!(::oak_async::ChannelReadStream<#input_type>)
        } else {
            quote!(#input_type)
        };
        let output_type = if method.server_streaming {
            quote!(::oak_async::grpc::MultiWriter<#output_type>)
        } else {
            quote!(::oak_async::grpc::OneshotWriter<#output_type>)
        };
        quote!(#variant(#input_type, #output_type))
    });

    quote! {
        #[allow(dead_code)]
        pub enum #enum_name {
            #(#variants,)*
        }
    }
}

/// Options for generating Protocol buffer Rust types.
pub struct ProtoOptions {
    /// Generate Oak-specific service code for inter-node communication.
    ///
    /// Default: **true**.
    ///
    /// Generated code depends on the `oak` SDK crate.
    pub generate_services: bool,

    /// Automatically derive the `HandleVisit` trait from the Oak SDK for generated Protobuf types.
    /// If this is enabled, the generated types can contain handles and can be used to exchange
    /// handles with other nodes using inter-node communication over Protocol Buffers.
    ///
    /// Default: **true**.
    ///
    /// Generated code depends on the `oak` SDK crate.
    pub derive_handle_visit: bool,

    /// Enable service code generated for the async SDK.
    ///
    /// Default: **false**.
    ///
    /// The regular sync code will still be generated if this is enabled.
    ///
    /// This feature is currently experimental.
    pub experimental_async: bool,

    pub out_dir_override: Option<std::path::PathBuf>,
}

/// The default option values.
impl Default for ProtoOptions {
    fn default() -> ProtoOptions {
        ProtoOptions {
            generate_services: true,
            derive_handle_visit: true,
            experimental_async: false,
            out_dir_override: None,
        }
    }
}

/// Build Rust code corresponding to a set of protocol buffer message and service definitions,
/// emitting generated code to crate's `OUT_DIR`.  For gRPC service definitions, this
/// function generates Oak-specific code that is suitable for use inside an Oak Node (i.e.  *not*
/// code that is suitable for use in a normal application running on the host platform).
pub fn compile_protos<P>(inputs: &[P], includes: &[P])
where
    P: AsRef<std::path::Path>,
{
    compile_protos_with_options(inputs, includes, ProtoOptions::default());
}

/// Like `compile_protos`, but allows for configuring options through `ProtoOptions`.
pub fn compile_protos_with_options<P>(inputs: &[P], includes: &[P], options: ProtoOptions)
where
    P: AsRef<std::path::Path>,
{
    set_protoc_env_if_unset();

    for input in inputs {
        // Tell cargo to rerun this build script if the proto file has changed.
        // https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorerun-if-changedpath
        println!("cargo:rerun-if-changed={}", input.as_ref().display());
    }

    let mut prost_config = prost_build::Config::new();
    if options.generate_services {
        if options.experimental_async {
            // AsyncServiceGenerator calls OakServiceGenerator, so the sync code is either way.
            prost_config.service_generator(Box::new(AsyncServiceGenerator));
        } else {
            prost_config.service_generator(Box::new(OakServiceGenerator));
        }
    }
    if options.derive_handle_visit {
        prost_config
            // Auto-derive the HandleVisit trait
            .type_attribute(".", "#[derive(::oak_io::handle::HandleVisit)]")
            // Link relevant Oak protos to the appropirate oak_io and oak_services types.
            .extern_path(".oak.handle", "::oak_io::handle")
            .extern_path(".oak.encap", "::oak_services::proto::oak::encap");
    }
    if let Some(out_dir) = options.out_dir_override {
        prost_config.out_dir(out_dir);
    }
    prost_config
        // We require label-related types to be comparable and hashable so that they can be used in
        // hash-based collections.
        .type_attribute(".oak.label", "#[derive(Eq, Hash)]")
        .type_attribute(
            ".oak.label",
            "#[derive(serde::Deserialize, serde::Serialize)]",
        )
        .type_attribute(".oak.label", "#[serde(rename_all = \"camelCase\")]")
        .compile_protos(inputs, includes)
        .expect("could not run prost-build");
}

/// Options for building gRPC code.
#[derive(Default)]
pub struct CodegenOptions {
    /// Specify whether to build client related code.
    pub build_client: bool,
    /// Specify whether to build server related code.
    pub build_server: bool,
}

/// Generate gRPC code from Protobuf using `tonic` library.
pub fn generate_grpc_code(
    proto_path: &str,
    file_paths: &[&str],
    options: CodegenOptions,
) -> std::io::Result<()> {
    set_protoc_env_if_unset();

    // TODO(#1093): Move all proto generation to a common crate.
    let proto_path = std::path::Path::new(proto_path);
    let file_paths: Vec<std::path::PathBuf> = file_paths
        .iter()
        .map(|file_path| proto_path.join(file_path))
        .collect();

    // Tell cargo to rerun this build script if the proto file has changed.
    // https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorerun-if-changedpath
    for file_path in file_paths.iter() {
        println!("cargo:rerun-if-changed={}", file_path.display());
    }

    // Generate the normal (non-Oak) server and client code for the gRPC service,
    // along with the Rust types corresponding to the message definitions.
    tonic_build::configure()
        .build_client(options.build_client)
        .build_server(options.build_server)
        .compile(&file_paths, &[proto_path.to_path_buf()])
}

fn set_protoc_env_if_unset() {
    if std::env::var("PROTOC").is_err() {
        // Use the system protoc if no override is set, so prost-build does not try to use the
        // bundled one that we remove as part of the vendoring process.
        std::env::set_var("PROTOC", "protoc");
    }
}
<|endoftext|>


<commit_before>use core::str::FromStr;

use string::String;

use ffi::CString;

use cell::GlobalCellMutRef;

use hal::xen::store::{Result, Error};

use super::imp::{XenStoreImpl, RequestBuilder, XsdSockmsgType};

/// A transaction with the Xen Store
///
/// Note: the transaction is automatically ended on `Drop` if not already
/// down.
pub struct Transaction<'a> {
    ended: bool,
    tx_id: u32,
    imp: GlobalCellMutRef<'a, XenStoreImpl>,
}

impl<'a> Transaction<'a> {
    #[doc(hidden)]
    pub fn new(mut imp: GlobalCellMutRef<'a, XenStoreImpl>) -> Result<Transaction<'a>> {
        let tx_req = {
            RequestBuilder::new(0).set_msg_type(XsdSockmsgType::TransactionStart)
        };

        imp.send(tx_req).and_then(|data| {
            match String::from_utf8(data) {
                Err(..) => Err(Error::Conversion),
                Ok(s) => match u32::from_str(&s[..]) {
                    Err(..) => Err(Error::Conversion),
                    Ok(id) => Ok(Transaction {
                        ended: false,
                        tx_id: id,
                        imp: imp,
                    }),
                },
            }
        })
    }

    /// Read the value pointed by `key`
    pub fn read(&mut self, key: CString) -> Result<String> {
        let req = RequestBuilder::new(self.tx_id).set_msg_type(XsdSockmsgType::Read)
                                                 .append_data(key.as_bytes_with_nul());

        self.imp.send(req).and_then(|data| {
            match String::from_utf8(data) {
                Err(..) => Err(Error::Conversion),
                Ok(s) => Ok(s),
            }
        })
    }

    /// Write the value of `key`
    pub fn write(&mut self, key: CString, value: CString) -> Result<()> {
        let req = {
            RequestBuilder::new(self.tx_id).set_msg_type(XsdSockmsgType::Write)
                                           .append_data(key.as_bytes_with_nul())
                                           .append_data(value.as_bytes_with_nul())
        };

        self.imp.send(req).map(|_| ())
    }

    fn _end(&mut self, data: CString) -> Result<()> {
        let req = {
            RequestBuilder::new(self.tx_id).set_msg_type(XsdSockmsgType::TransactionEnd)
                                           .append_data(data.as_bytes_with_nul())
        };

        self.imp.send(req).and_then(|data| {
            match String::from_utf8(data) {
                Err(..) => Err(Error::Conversion),
                Ok(s) => {
                    match s.as_ref() {
                        "OK" => {
                            self.ended = true;
                            Ok(())
                        }
                        _ => Err(Error::Unknown),
                    }
                }
            }
        })
    }

    /// End the transaction
    pub fn end(&mut self) -> Result<()> {
        self._end(CString::new("T").unwrap())
    }

    /// Abort the transaction
    pub fn abort(&mut self) -> Result<()> {
        self._end(CString::new("F").unwrap())
    }
}

impl<'a> Drop for Transaction<'a> {
    fn drop(&mut self) {
        if !self.ended {
            self.end().unwrap();
        }
    }
}
<commit_msg>xen: Xen Store transaction does now abort on Drop<commit_after>use core::str::FromStr;

use string::String;

use ffi::CString;

use cell::GlobalCellMutRef;

use hal::xen::store::{Result, Error};

use super::imp::{XenStoreImpl, RequestBuilder, XsdSockmsgType};

/// A transaction with the Xen Store
///
/// Note: the transaction is automatically ended on `Drop` if not already
/// down.
pub struct Transaction<'a> {
    ended: bool,
    tx_id: u32,
    imp: GlobalCellMutRef<'a, XenStoreImpl>,
}

impl<'a> Transaction<'a> {
    #[doc(hidden)]
    pub fn new(mut imp: GlobalCellMutRef<'a, XenStoreImpl>) -> Result<Transaction<'a>> {
        let tx_req = {
            RequestBuilder::new(0).set_msg_type(XsdSockmsgType::TransactionStart)
        };

        imp.send(tx_req).and_then(|data| {
            match String::from_utf8(data) {
                Err(..) => Err(Error::Conversion),
                Ok(s) => match u32::from_str(&s[..]) {
                    Err(..) => Err(Error::Conversion),
                    Ok(id) => Ok(Transaction {
                        ended: false,
                        tx_id: id,
                        imp: imp,
                    }),
                },
            }
        })
    }

    /// Read the value pointed by `key`
    pub fn read(&mut self, key: CString) -> Result<String> {
        let req = RequestBuilder::new(self.tx_id).set_msg_type(XsdSockmsgType::Read)
                                                 .append_data(key.as_bytes_with_nul());

        self.imp.send(req).and_then(|data| {
            match String::from_utf8(data) {
                Err(..) => Err(Error::Conversion),
                Ok(s) => Ok(s),
            }
        })
    }

    /// Write the value of `key`
    pub fn write(&mut self, key: CString, value: CString) -> Result<()> {
        let req = {
            RequestBuilder::new(self.tx_id).set_msg_type(XsdSockmsgType::Write)
                                           .append_data(key.as_bytes_with_nul())
                                           .append_data(value.as_bytes_with_nul())
        };

        self.imp.send(req).map(|_| ())
    }

    fn _end(&mut self, data: CString) -> Result<()> {
        let req = {
            RequestBuilder::new(self.tx_id).set_msg_type(XsdSockmsgType::TransactionEnd)
                                           .append_data(data.as_bytes_with_nul())
        };

        self.imp.send(req).and_then(|data| {
            match String::from_utf8(data) {
                Err(..) => Err(Error::Conversion),
                Ok(s) => {
                    match s.as_ref() {
                        "OK" => {
                            self.ended = true;
                            Ok(())
                        }
                        _ => Err(Error::Unknown),
                    }
                }
            }
        })
    }

    /// End the transaction
    pub fn end(&mut self) -> Result<()> {
        self._end(CString::new("T").unwrap())
    }

    /// Abort the transaction
    pub fn abort(&mut self) -> Result<()> {
        self._end(CString::new("F").unwrap())
    }
}

impl<'a> Drop for Transaction<'a> {
    fn drop(&mut self) {
        if !self.ended {
            self.abort().unwrap();
        }
    }
}
<|endoftext|>