<commit_before>//! FIXME: write short doc here

use std::sync::Arc;

use hir_def::{path::path, resolver::HasResolver, AdtId, FunctionId};
use hir_expand::diagnostics::DiagnosticSink;
use ra_syntax::{ast, AstPtr};
use rustc_hash::FxHashSet;

use crate::{
    db::HirDatabase,
    diagnostics::{
        MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, MissingPatFields,
    },
    utils::variant_data,
    ApplicationTy, CallableDef, InferenceResult, Ty, TypeCtor,
    _match::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness},
};

pub use hir_def::{
    body::{
        scope::{ExprScopes, ScopeEntry, ScopeId},
        Body, BodySourceMap, ExprPtr, ExprSource, PatPtr, PatSource,
    },
    expr::{
        ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp,
        MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, UnaryOp,
    },
    src::HasSource,
    LocalFieldId, Lookup, VariantId,
};

pub struct ExprValidator<'a, 'b: 'a> {
    func: FunctionId,
    infer: Arc<InferenceResult>,
    sink: &'a mut DiagnosticSink<'b>,
}

impl<'a, 'b> ExprValidator<'a, 'b> {
    pub fn new(
        func: FunctionId,
        infer: Arc<InferenceResult>,
        sink: &'a mut DiagnosticSink<'b>,
    ) -> ExprValidator<'a, 'b> {
        ExprValidator { func, infer, sink }
    }

    pub fn validate_body(&mut self, db: &dyn HirDatabase) {
        let body = db.body(self.func.into());

        for (id, expr) in body.exprs.iter() {
            if let Some((variant_def, missed_fields, true)) =
                record_literal_missing_fields(db, &self.infer, id, expr)
            {
                self.create_record_literal_missing_fields_diagnostic(
                    id,
                    db,
                    variant_def,
                    missed_fields,
                );
            }

            match expr {
                Expr::Match { expr, arms } => {
                    self.validate_match(id, *expr, arms, db, self.infer.clone());
                }
                Expr::Call { .. } | Expr::MethodCall { .. } => {
                    self.validate_call(db, id, expr);
                }
                _ => {}
            }
        }
        for (id, pat) in body.pats.iter() {
            if let Some((variant_def, missed_fields, true)) =
                record_pattern_missing_fields(db, &self.infer, id, pat)
            {
                self.create_record_pattern_missing_fields_diagnostic(
                    id,
                    db,
                    variant_def,
                    missed_fields,
                );
            }
        }
        let body_expr = &body[body.body_expr];
        if let Expr::Block { tail: Some(t), .. } = body_expr {
            self.validate_results_in_tail_expr(body.body_expr, *t, db);
        }
    }

    fn create_record_literal_missing_fields_diagnostic(
        &mut self,
        id: ExprId,
        db: &dyn HirDatabase,
        variant_def: VariantId,
        missed_fields: Vec<LocalFieldId>,
    ) {
        // XXX: only look at source_map if we do have missing fields
        let (_, source_map) = db.body_with_source_map(self.func.into());

        if let Ok(source_ptr) = source_map.expr_syntax(id) {
            let root = source_ptr.file_syntax(db.upcast());
            if let ast::Expr::RecordLit(record_lit) = &source_ptr.value.to_node(&root) {
                if let Some(field_list) = record_lit.record_field_list() {
                    let variant_data = variant_data(db.upcast(), variant_def);
                    let missed_fields = missed_fields
                        .into_iter()
                        .map(|idx| variant_data.fields()[idx].name.clone())
                        .collect();
                    self.sink.push(MissingFields {
                        file: source_ptr.file_id,
                        field_list: AstPtr::new(&field_list),
                        missed_fields,
                    })
                }
            }
        }
    }

    fn create_record_pattern_missing_fields_diagnostic(
        &mut self,
        id: PatId,
        db: &dyn HirDatabase,
        variant_def: VariantId,
        missed_fields: Vec<LocalFieldId>,
    ) {
        // XXX: only look at source_map if we do have missing fields
        let (_, source_map) = db.body_with_source_map(self.func.into());

        if let Ok(source_ptr) = source_map.pat_syntax(id) {
            if let Some(expr) = source_ptr.value.as_ref().left() {
                let root = source_ptr.file_syntax(db.upcast());
                if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) {
                    if let Some(field_list) = record_pat.record_field_pat_list() {
                        let variant_data = variant_data(db.upcast(), variant_def);
                        let missed_fields = missed_fields
                            .into_iter()
                            .map(|idx| variant_data.fields()[idx].name.clone())
                            .collect();
                        self.sink.push(MissingPatFields {
                            file: source_ptr.file_id,
                            field_list: AstPtr::new(&field_list),
                            missed_fields,
                        })
                    }
                }
            }
        }
    }

    fn validate_call(&mut self, db: &dyn HirDatabase, call_id: ExprId, expr: &Expr) -> Option<()> {
        // Check that the number of arguments matches the number of parameters.
        let (callee, args) = match expr {
            Expr::Call { callee, args } => {
                let callee = &self.infer.type_of_expr[*callee];
                let (callable, _) = callee.as_callable()?;
                let callee = match callable {
                    CallableDef::FunctionId(func) => func,
                    _ => return None,
                };

                (callee, args.clone())
            }
            Expr::MethodCall { receiver, args, .. } => {
                let callee = self.infer.method_resolution(call_id)?;
                let mut args = args.clone();
                args.insert(0, *receiver);
                (callee, args)
            }
            _ => return None,
        };

        let loc = callee.lookup(db.upcast());
        let ast = loc.source(db.upcast());
        let params = ast.value.param_list()?;

        let mut param_count = params.params().count();
        if params.self_param().is_some() {
            param_count += 1;
        }
        let arg_count = args.len();

        if arg_count != param_count {
            let (_, source_map): (Arc<Body>, Arc<BodySourceMap>) =
                db.body_with_source_map(self.func.into());
            if let Ok(source_ptr) = source_map.expr_syntax(call_id) {
                self.sink.push(MismatchedArgCount {
                    file: source_ptr.file_id,
                    call_expr: source_ptr.value,
                    expected: param_count,
                    found: arg_count,
                });
            }
        }

        None
    }

    fn validate_match(
        &mut self,
        id: ExprId,
        match_expr: ExprId,
        arms: &[MatchArm],
        db: &dyn HirDatabase,
        infer: Arc<InferenceResult>,
    ) {
        let (body, source_map): (Arc<Body>, Arc<BodySourceMap>) =
            db.body_with_source_map(self.func.into());

        let match_expr_ty = match infer.type_of_expr.get(match_expr) {
            Some(ty) => ty,
            // If we can't resolve the type of the match expression
            // we cannot perform exhaustiveness checks.
            None => return,
        };

        let cx = MatchCheckCtx { match_expr, body, infer: infer.clone(), db };
        let pats = arms.iter().map(|arm| arm.pat);

        let mut seen = Matrix::empty();
        for pat in pats {
            if let Some(pat_ty) = infer.type_of_pat.get(pat) {
                // We only include patterns whose type matches the type
                // of the match expression. If we had a InvalidMatchArmPattern
                // diagnostic or similar we could raise that in an else
                // block here.
                //
                // When comparing the types, we also have to consider that rustc
                // will automatically de-reference the match expression type if
                // necessary.
                //
                // FIXME we should use the type checker for this.
                if pat_ty == match_expr_ty
                    || match_expr_ty
                        .as_reference()
                        .map(|(match_expr_ty, _)| match_expr_ty == pat_ty)
                        .unwrap_or(false)
                {
                    // If we had a NotUsefulMatchArm diagnostic, we could
                    // check the usefulness of each pattern as we added it
                    // to the matrix here.
                    let v = PatStack::from_pattern(pat);
                    seen.push(&cx, v);
                    continue;
                }
            }

            // If we can't resolve the type of a pattern, or the pattern type doesn't
            // fit the match expression, we skip this diagnostic. Skipping the entire
            // diagnostic rather than just not including this match arm is preferred
            // to avoid the chance of false positives.
            return;
        }

        match is_useful(&cx, &seen, &PatStack::from_wild()) {
            Ok(Usefulness::Useful) => (),
            // if a wildcard pattern is not useful, then all patterns are covered
            Ok(Usefulness::NotUseful) => return,
            // this path is for unimplemented checks, so we err on the side of not
            // reporting any errors
            _ => return,
        }

        if let Ok(source_ptr) = source_map.expr_syntax(id) {
            let root = source_ptr.file_syntax(db.upcast());
            if let ast::Expr::MatchExpr(match_expr) = &source_ptr.value.to_node(&root) {
                if let (Some(match_expr), Some(arms)) =
                    (match_expr.expr(), match_expr.match_arm_list())
                {
                    self.sink.push(MissingMatchArms {
                        file: source_ptr.file_id,
                        match_expr: AstPtr::new(&match_expr),
                        arms: AstPtr::new(&arms),
                    })
                }
            }
        }
    }

    fn validate_results_in_tail_expr(&mut self, body_id: ExprId, id: ExprId, db: &dyn HirDatabase) {
        // the mismatch will be on the whole block currently
        let mismatch = match self.infer.type_mismatch_for_expr(body_id) {
            Some(m) => m,
            None => return,
        };

        let core_result_path = path![core::result::Result];

        let resolver = self.func.resolver(db.upcast());
        let core_result_enum = match resolver.resolve_known_enum(db.upcast(), &core_result_path) {
            Some(it) => it,
            _ => return,
        };

        let core_result_ctor = TypeCtor::Adt(AdtId::EnumId(core_result_enum));
        let params = match &mismatch.expected {
            Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &core_result_ctor => {
                parameters
            }
            _ => return,
        };

        if params.len() == 2 && params[0] == mismatch.actual {
            let (_, source_map) = db.body_with_source_map(self.func.into());

            if let Ok(source_ptr) = source_map.expr_syntax(id) {
                self.sink
                    .push(MissingOkInTailExpr { file: source_ptr.file_id, expr: source_ptr.value });
            }
        }
    }
}

pub fn record_literal_missing_fields(
    db: &dyn HirDatabase,
    infer: &InferenceResult,
    id: ExprId,
    expr: &Expr,
) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
    let (fields, exhausitve) = match expr {
        Expr::RecordLit { path: _, fields, spread } => (fields, spread.is_none()),
        _ => return None,
    };

    let variant_def = infer.variant_resolution_for_expr(id)?;
    if let VariantId::UnionId(_) = variant_def {
        return None;
    }

    let variant_data = variant_data(db.upcast(), variant_def);

    let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
    let missed_fields: Vec<LocalFieldId> = variant_data
        .fields()
        .iter()
        .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
        .collect();
    if missed_fields.is_empty() {
        return None;
    }
    Some((variant_def, missed_fields, exhausitve))
}

pub fn record_pattern_missing_fields(
    db: &dyn HirDatabase,
    infer: &InferenceResult,
    id: PatId,
    pat: &Pat,
) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
    let (fields, exhaustive) = match pat {
        Pat::Record { path: _, args, ellipsis } => (args, !ellipsis),
        _ => return None,
    };

    let variant_def = infer.variant_resolution_for_pat(id)?;
    if let VariantId::UnionId(_) = variant_def {
        return None;
    }

    let variant_data = variant_data(db.upcast(), variant_def);

    let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
    let missed_fields: Vec<LocalFieldId> = variant_data
        .fields()
        .iter()
        .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
        .collect();
    if missed_fields.is_empty() {
        return None;
    }
    Some((variant_def, missed_fields, exhaustive))
}
<commit_msg>Don't emit diagnostic if there are type errors<commit_after>//! FIXME: write short doc here

use std::sync::Arc;

use hir_def::{path::path, resolver::HasResolver, AdtId, FunctionId};
use hir_expand::diagnostics::DiagnosticSink;
use ra_syntax::{ast, AstPtr};
use rustc_hash::FxHashSet;

use crate::{
    db::HirDatabase,
    diagnostics::{
        MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, MissingPatFields,
    },
    utils::variant_data,
    ApplicationTy, CallableDef, InferenceResult, Ty, TypeCtor,
    _match::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness},
};

pub use hir_def::{
    body::{
        scope::{ExprScopes, ScopeEntry, ScopeId},
        Body, BodySourceMap, ExprPtr, ExprSource, PatPtr, PatSource,
    },
    expr::{
        ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp,
        MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, UnaryOp,
    },
    src::HasSource,
    LocalFieldId, Lookup, VariantId,
};

pub struct ExprValidator<'a, 'b: 'a> {
    func: FunctionId,
    infer: Arc<InferenceResult>,
    sink: &'a mut DiagnosticSink<'b>,
}

impl<'a, 'b> ExprValidator<'a, 'b> {
    pub fn new(
        func: FunctionId,
        infer: Arc<InferenceResult>,
        sink: &'a mut DiagnosticSink<'b>,
    ) -> ExprValidator<'a, 'b> {
        ExprValidator { func, infer, sink }
    }

    pub fn validate_body(&mut self, db: &dyn HirDatabase) {
        let body = db.body(self.func.into());

        for (id, expr) in body.exprs.iter() {
            if let Some((variant_def, missed_fields, true)) =
                record_literal_missing_fields(db, &self.infer, id, expr)
            {
                self.create_record_literal_missing_fields_diagnostic(
                    id,
                    db,
                    variant_def,
                    missed_fields,
                );
            }

            match expr {
                Expr::Match { expr, arms } => {
                    self.validate_match(id, *expr, arms, db, self.infer.clone());
                }
                Expr::Call { .. } | Expr::MethodCall { .. } => {
                    self.validate_call(db, id, expr);
                }
                _ => {}
            }
        }
        for (id, pat) in body.pats.iter() {
            if let Some((variant_def, missed_fields, true)) =
                record_pattern_missing_fields(db, &self.infer, id, pat)
            {
                self.create_record_pattern_missing_fields_diagnostic(
                    id,
                    db,
                    variant_def,
                    missed_fields,
                );
            }
        }
        let body_expr = &body[body.body_expr];
        if let Expr::Block { tail: Some(t), .. } = body_expr {
            self.validate_results_in_tail_expr(body.body_expr, *t, db);
        }
    }

    fn create_record_literal_missing_fields_diagnostic(
        &mut self,
        id: ExprId,
        db: &dyn HirDatabase,
        variant_def: VariantId,
        missed_fields: Vec<LocalFieldId>,
    ) {
        // XXX: only look at source_map if we do have missing fields
        let (_, source_map) = db.body_with_source_map(self.func.into());

        if let Ok(source_ptr) = source_map.expr_syntax(id) {
            let root = source_ptr.file_syntax(db.upcast());
            if let ast::Expr::RecordLit(record_lit) = &source_ptr.value.to_node(&root) {
                if let Some(field_list) = record_lit.record_field_list() {
                    let variant_data = variant_data(db.upcast(), variant_def);
                    let missed_fields = missed_fields
                        .into_iter()
                        .map(|idx| variant_data.fields()[idx].name.clone())
                        .collect();
                    self.sink.push(MissingFields {
                        file: source_ptr.file_id,
                        field_list: AstPtr::new(&field_list),
                        missed_fields,
                    })
                }
            }
        }
    }

    fn create_record_pattern_missing_fields_diagnostic(
        &mut self,
        id: PatId,
        db: &dyn HirDatabase,
        variant_def: VariantId,
        missed_fields: Vec<LocalFieldId>,
    ) {
        // XXX: only look at source_map if we do have missing fields
        let (_, source_map) = db.body_with_source_map(self.func.into());

        if let Ok(source_ptr) = source_map.pat_syntax(id) {
            if let Some(expr) = source_ptr.value.as_ref().left() {
                let root = source_ptr.file_syntax(db.upcast());
                if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) {
                    if let Some(field_list) = record_pat.record_field_pat_list() {
                        let variant_data = variant_data(db.upcast(), variant_def);
                        let missed_fields = missed_fields
                            .into_iter()
                            .map(|idx| variant_data.fields()[idx].name.clone())
                            .collect();
                        self.sink.push(MissingPatFields {
                            file: source_ptr.file_id,
                            field_list: AstPtr::new(&field_list),
                            missed_fields,
                        })
                    }
                }
            }
        }
    }

    fn validate_call(&mut self, db: &dyn HirDatabase, call_id: ExprId, expr: &Expr) -> Option<()> {
        // Check that the number of arguments matches the number of parameters.

        // Due to shortcomings in the current type system implementation, only emit this diagnostic
        // if there are no type mismatches in the containing function.
        if self.infer.type_mismatches.iter().next().is_some() {
            return Some(());
        }

        let (callee, args) = match expr {
            Expr::Call { callee, args } => {
                let callee = &self.infer.type_of_expr[*callee];
                let (callable, _) = callee.as_callable()?;
                let callee = match callable {
                    CallableDef::FunctionId(func) => func,
                    _ => return None,
                };

                (callee, args.clone())
            }
            Expr::MethodCall { receiver, args, .. } => {
                let callee = self.infer.method_resolution(call_id)?;
                let mut args = args.clone();
                args.insert(0, *receiver);
                (callee, args)
            }
            _ => return None,
        };

        let loc = callee.lookup(db.upcast());
        let ast = loc.source(db.upcast());
        let params = ast.value.param_list()?;

        let mut param_count = params.params().count();
        if params.self_param().is_some() {
            param_count += 1;
        }
        let arg_count = args.len();

        if arg_count != param_count {
            let (_, source_map): (Arc<Body>, Arc<BodySourceMap>) =
                db.body_with_source_map(self.func.into());
            if let Ok(source_ptr) = source_map.expr_syntax(call_id) {
                self.sink.push(MismatchedArgCount {
                    file: source_ptr.file_id,
                    call_expr: source_ptr.value,
                    expected: param_count,
                    found: arg_count,
                });
            }
        }

        None
    }

    fn validate_match(
        &mut self,
        id: ExprId,
        match_expr: ExprId,
        arms: &[MatchArm],
        db: &dyn HirDatabase,
        infer: Arc<InferenceResult>,
    ) {
        let (body, source_map): (Arc<Body>, Arc<BodySourceMap>) =
            db.body_with_source_map(self.func.into());

        let match_expr_ty = match infer.type_of_expr.get(match_expr) {
            Some(ty) => ty,
            // If we can't resolve the type of the match expression
            // we cannot perform exhaustiveness checks.
            None => return,
        };

        let cx = MatchCheckCtx { match_expr, body, infer: infer.clone(), db };
        let pats = arms.iter().map(|arm| arm.pat);

        let mut seen = Matrix::empty();
        for pat in pats {
            if let Some(pat_ty) = infer.type_of_pat.get(pat) {
                // We only include patterns whose type matches the type
                // of the match expression. If we had a InvalidMatchArmPattern
                // diagnostic or similar we could raise that in an else
                // block here.
                //
                // When comparing the types, we also have to consider that rustc
                // will automatically de-reference the match expression type if
                // necessary.
                //
                // FIXME we should use the type checker for this.
                if pat_ty == match_expr_ty
                    || match_expr_ty
                        .as_reference()
                        .map(|(match_expr_ty, _)| match_expr_ty == pat_ty)
                        .unwrap_or(false)
                {
                    // If we had a NotUsefulMatchArm diagnostic, we could
                    // check the usefulness of each pattern as we added it
                    // to the matrix here.
                    let v = PatStack::from_pattern(pat);
                    seen.push(&cx, v);
                    continue;
                }
            }

            // If we can't resolve the type of a pattern, or the pattern type doesn't
            // fit the match expression, we skip this diagnostic. Skipping the entire
            // diagnostic rather than just not including this match arm is preferred
            // to avoid the chance of false positives.
            return;
        }

        match is_useful(&cx, &seen, &PatStack::from_wild()) {
            Ok(Usefulness::Useful) => (),
            // if a wildcard pattern is not useful, then all patterns are covered
            Ok(Usefulness::NotUseful) => return,
            // this path is for unimplemented checks, so we err on the side of not
            // reporting any errors
            _ => return,
        }

        if let Ok(source_ptr) = source_map.expr_syntax(id) {
            let root = source_ptr.file_syntax(db.upcast());
            if let ast::Expr::MatchExpr(match_expr) = &source_ptr.value.to_node(&root) {
                if let (Some(match_expr), Some(arms)) =
                    (match_expr.expr(), match_expr.match_arm_list())
                {
                    self.sink.push(MissingMatchArms {
                        file: source_ptr.file_id,
                        match_expr: AstPtr::new(&match_expr),
                        arms: AstPtr::new(&arms),
                    })
                }
            }
        }
    }

    fn validate_results_in_tail_expr(&mut self, body_id: ExprId, id: ExprId, db: &dyn HirDatabase) {
        // the mismatch will be on the whole block currently
        let mismatch = match self.infer.type_mismatch_for_expr(body_id) {
            Some(m) => m,
            None => return,
        };

        let core_result_path = path![core::result::Result];

        let resolver = self.func.resolver(db.upcast());
        let core_result_enum = match resolver.resolve_known_enum(db.upcast(), &core_result_path) {
            Some(it) => it,
            _ => return,
        };

        let core_result_ctor = TypeCtor::Adt(AdtId::EnumId(core_result_enum));
        let params = match &mismatch.expected {
            Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &core_result_ctor => {
                parameters
            }
            _ => return,
        };

        if params.len() == 2 && params[0] == mismatch.actual {
            let (_, source_map) = db.body_with_source_map(self.func.into());

            if let Ok(source_ptr) = source_map.expr_syntax(id) {
                self.sink
                    .push(MissingOkInTailExpr { file: source_ptr.file_id, expr: source_ptr.value });
            }
        }
    }
}

pub fn record_literal_missing_fields(
    db: &dyn HirDatabase,
    infer: &InferenceResult,
    id: ExprId,
    expr: &Expr,
) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
    let (fields, exhausitve) = match expr {
        Expr::RecordLit { path: _, fields, spread } => (fields, spread.is_none()),
        _ => return None,
    };

    let variant_def = infer.variant_resolution_for_expr(id)?;
    if let VariantId::UnionId(_) = variant_def {
        return None;
    }

    let variant_data = variant_data(db.upcast(), variant_def);

    let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
    let missed_fields: Vec<LocalFieldId> = variant_data
        .fields()
        .iter()
        .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
        .collect();
    if missed_fields.is_empty() {
        return None;
    }
    Some((variant_def, missed_fields, exhausitve))
}

pub fn record_pattern_missing_fields(
    db: &dyn HirDatabase,
    infer: &InferenceResult,
    id: PatId,
    pat: &Pat,
) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
    let (fields, exhaustive) = match pat {
        Pat::Record { path: _, args, ellipsis } => (args, !ellipsis),
        _ => return None,
    };

    let variant_def = infer.variant_resolution_for_pat(id)?;
    if let VariantId::UnionId(_) = variant_def {
        return None;
    }

    let variant_data = variant_data(db.upcast(), variant_def);

    let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
    let missed_fields: Vec<LocalFieldId> = variant_data
        .fields()
        .iter()
        .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
        .collect();
    if missed_fields.is_empty() {
        return None;
    }
    Some((variant_def, missed_fields, exhaustive))
}
<|endoftext|>


<commit_before>pub mod visit;

use rowan::TransparentNewType;

use crate::{SyntaxNode, TextRange, TextUnit, AstNode};

pub use rowan::LeafAtOffset;

pub fn find_leaf_at_offset(node: &SyntaxNode, offset: TextUnit) -> LeafAtOffset<&SyntaxNode> {
    match node.0.leaf_at_offset(offset) {
        LeafAtOffset::None => LeafAtOffset::None,
        LeafAtOffset::Single(n) => LeafAtOffset::Single(SyntaxNode::from_repr(n)),
        LeafAtOffset::Between(l, r) => {
            LeafAtOffset::Between(SyntaxNode::from_repr(l), SyntaxNode::from_repr(r))
        }
    }
}

pub fn find_node_at_offset<N: AstNode>(syntax: &SyntaxNode, offset: TextUnit) -> Option<&N> {
    find_leaf_at_offset(syntax, offset).find_map(|leaf| leaf.ancestors().find_map(N::cast))
}

pub fn find_covering_node(root: &SyntaxNode, range: TextRange) -> &SyntaxNode {
    SyntaxNode::from_repr(root.0.covering_node(range))
}

pub fn generate<T>(seed: Option<T>, step: impl Fn(&T) -> Option<T>) -> impl Iterator<Item = T> {
    ::itertools::unfold(seed, move |slot| {
        slot.take().map(|curr| {
            *slot = step(&curr);
            curr
        })
    })
}
<commit_msg>add comment<commit_after>pub mod visit;

use rowan::TransparentNewType;

use crate::{SyntaxNode, TextRange, TextUnit, AstNode};

pub use rowan::LeafAtOffset;

pub fn find_leaf_at_offset(node: &SyntaxNode, offset: TextUnit) -> LeafAtOffset<&SyntaxNode> {
    match node.0.leaf_at_offset(offset) {
        LeafAtOffset::None => LeafAtOffset::None,
        LeafAtOffset::Single(n) => LeafAtOffset::Single(SyntaxNode::from_repr(n)),
        LeafAtOffset::Between(l, r) => {
            LeafAtOffset::Between(SyntaxNode::from_repr(l), SyntaxNode::from_repr(r))
        }
    }
}

/// Finds a node of specific Ast type at offset. Note that this is slightly
/// impercise: if the cursor is strictly betwen two nodes of the desired type,
/// as in
///
/// ```no-run
/// struct Foo {}|struct Bar;
/// ```
///
/// then the left node will be silently prefered.
pub fn find_node_at_offset<N: AstNode>(syntax: &SyntaxNode, offset: TextUnit) -> Option<&N> {
    find_leaf_at_offset(syntax, offset).find_map(|leaf| leaf.ancestors().find_map(N::cast))
}

pub fn find_covering_node(root: &SyntaxNode, range: TextRange) -> &SyntaxNode {
    SyntaxNode::from_repr(root.0.covering_node(range))
}

pub fn generate<T>(seed: Option<T>, step: impl Fn(&T) -> Option<T>) -> impl Iterator<Item = T> {
    ::itertools::unfold(seed, move |slot| {
        slot.take().map(|curr| {
            *slot = step(&curr);
            curr
        })
    })
}
<|endoftext|>


<commit_before>mod data;

pub use self::data::AirportType;

use self::data::DataType;
use crate::path::FilePath;
use anyhow::{anyhow, Result};
use chrono::{Duration, NaiveDate, Utc};
use serde_derive::Serialize;
use std::borrow::Cow;
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::{self, BufWriter};
use std::path::{Path, PathBuf};

const BASE_URL: &str = "https://ourairports.com/data";

fn validated_dir() -> Result<PathBuf> {
    FilePath::LocalData.validated_subdir("airport_data/")
}

pub fn ensure_updated() -> Result<()> {
    let base_dir = validated_dir()?;
    let mut last_update = LastUpdate::load(&base_dir);

    if !last_update.needs_update() {
        return Ok(());
    }

    download_latest(&base_dir)?;
    last_update.set_to_today()?;

    Ok(())
}

fn download_latest<P>(dir: P) -> Result<()>
where
    P: AsRef<Path>,
{
    println!("updating airport data");

    for dtype in &DataType::ALL {
        download_file(dtype.filename(), &dir)?;
    }

    println!("finished updating airport data");
    Ok(())
}

fn download_file<S, P>(name: S, dir: P) -> Result<()>
where
    S: AsRef<str>,
    P: AsRef<Path>,
{
    let name = name.as_ref();
    let dir = dir.as_ref();

    println!(".. downloading {}", name);

    let path = dir.join(name);
    let backup = Backup::create(&path)?;

    let resp = attohttpc::get(format!("{}/{}", BASE_URL, name))
        .timeout(Duration::seconds(15).to_std().unwrap())
        .send()?;

    if !resp.is_success() {
        backup.restore()?;

        return Err(anyhow!(
            "received code {} while downloading {}",
            resp.status(),
            name
        ));
    }

    let file = match File::create(path) {
        Ok(file) => file,
        Err(err) => {
            backup.restore()?;
            return Err(err.into());
        }
    };

    let (_, _, mut reader) = resp.split();
    let mut writer = BufWriter::new(file);

    match io::copy(&mut reader, &mut writer) {
        Ok(_) => Ok(()),
        Err(err) => {
            backup.restore()?;
            Err(err.into())
        }
    }
}

struct LastUpdate(PathBuf);

impl LastUpdate {
    const DATE_FORMAT: &'static str = "%Y-%m-%d";

    fn load<P>(base_dir: P) -> Self
    where
        P: Into<PathBuf>,
    {
        let mut path = base_dir.into();
        path.push(".last_updated");

        Self(path)
    }

    fn needs_update(&self) -> bool {
        if !self.path().exists() {
            return true;
        }

        let updated_at = fs::read_to_string(self.path())
            .ok()
            .and_then(|updated_at| NaiveDate::parse_from_str(&updated_at, Self::DATE_FORMAT).ok());

        let updated_at = match updated_at {
            Some(updated_at) => updated_at,
            None => return true,
        };

        let today = Utc::now().naive_utc().date();

        if today - updated_at >= Duration::weeks(2) {
            return true;
        }

        false
    }

    fn set_to_today(&mut self) -> Result<()> {
        let today = Utc::now().naive_utc().date();
        fs::write(self.path(), today.format(Self::DATE_FORMAT).to_string())?;
        Ok(())
    }

    #[inline(always)]
    fn path(&self) -> &Path {
        &self.0
    }
}

enum Backup {
    Exists(PathBuf),
    DoesntExist,
}

impl Backup {
    fn create<P>(file: P) -> Result<Self>
    where
        P: AsRef<Path>,
    {
        let file = file.as_ref();

        if !file.exists() {
            return Ok(Self::DoesntExist);
        }

        let extension = match file.extension() {
            Some(ext) => format!("{}.bak", ext.to_string_lossy()).into(),
            None => Cow::Borrowed(".bak"),
        };

        let mut backup_path = PathBuf::from(file);
        backup_path.set_extension(extension.as_ref());

        fs::rename(file, &backup_path)?;

        Ok(Self::Exists(backup_path))
    }

    fn restore(self) -> Result<()> {
        match self {
            Self::Exists(path) => {
                if !path.exists() {
                    return Ok(());
                }

                let mut original_path = path.clone();
                original_path.set_extension("");

                if original_path.exists() {
                    fs::remove_file(&original_path)?;
                }

                fs::rename(path, original_path)?;
                Ok(())
            }
            Self::DoesntExist => Ok(()),
        }
    }
}

#[derive(Debug, Serialize)]
pub struct Airport {
    pub icao: String,
    #[serde(skip_serializing)]
    pub class: AirportType,
    pub position: Position,
    pub runways: Vec<Runway>,
    pub frequencies: HashMap<FrequencyType, String>,
    #[serde(skip_serializing)]
    pub country_name: String,
}

impl Airport {
    const ICAO_MAX_LEN: usize = 4;

    pub fn load_all() -> Result<Vec<Self>> {
        let dir = validated_dir()?;

        let airports = data::Airport::from_dir(&dir)?;
        let mut runways = data::Runway::from_dir(&dir)?;
        let mut frequencies = data::Frequency::from_dir(&dir)?;
        let countries = data::Country::from_dir(&dir)?;

        let mut results = Vec::with_capacity(airports.len());

        for airport in airports {
            if airport.icao.len() > Self::ICAO_MAX_LEN {
                continue;
            }

            let runways = match runways.remove(&airport.id) {
                Some(runways) => runways,
                None => continue,
            };

            let frequencies = match frequencies.remove(&airport.id) {
                Some(frequencies) => frequencies,
                None => HashMap::new(),
            };

            let country = match countries.iter().find(|c| c.code == airport.country_code) {
                Some(country) => country,
                None => continue,
            };

            let result = Self {
                icao: airport.icao,
                class: airport.class,
                position: Position::new(airport.lat_deg, airport.lon_deg),
                runways: runways.into_iter().filter_map(Runway::from_data).collect(),
                frequencies: frequencies
                    .into_iter()
                    .map(|(freq_type, freq)| (freq_type.into(), freq.mhz))
                    .collect(),
                country_name: country.name.clone(),
            };

            results.push(result);
        }

        results.shrink_to_fit();
        results.sort_unstable_by(|x, y| x.icao.cmp(&y.icao));

        Ok(results)
    }
}

impl PartialEq for Airport {
    fn eq(&self, other: &Self) -> bool {
        self.icao == other.icao
    }
}

#[derive(Copy, Clone, Debug, Serialize)]
pub struct Position {
    #[serde(rename = "latitudeDeg")]
    pub latitude_deg: f32,
    #[serde(rename = "longitudeDeg")]
    pub longitude_deg: f32,
}

impl Position {
    #[inline(always)]
    pub fn new(latitude_deg: f32, longitude_deg: f32) -> Self {
        Self {
            latitude_deg,
            longitude_deg,
        }
    }
}

#[derive(Debug, Serialize)]
pub struct Runway {
    #[serde(rename = "lengthFT")]
    pub length_ft: Option<u32>,
    #[serde(rename = "widthFT")]
    pub width_ft: Option<u32>,
    #[serde(rename = "heMarker")]
    pub he_marker: Option<RunwayMarker>,
    #[serde(rename = "leMarker")]
    pub le_marker: Option<RunwayMarker>,
}

impl Runway {
    fn from_data(source: data::Runway) -> Option<Self> {
        let he_marker = match (source.le_ident, source.le_lat_deg, source.le_lon_deg) {
            (Some(name), Some(lat), Some(lon)) => Some(RunwayMarker::new(name, lat, lon)),
            _ => None,
        };

        let le_marker = match (source.he_ident, source.he_lat_deg, source.he_lon_deg) {
            (Some(name), Some(lat), Some(lon)) => Some(RunwayMarker::new(name, lat, lon)),
            _ => None,
        };

        Some(Self {
            length_ft: source.length_ft,
            width_ft: source.width_ft,
            he_marker,
            le_marker,
        })
    }
}

#[derive(Debug, Serialize)]
pub struct RunwayMarker {
    pub name: String,
    pub position: Position,
}

impl RunwayMarker {
    #[inline(always)]
    pub fn new(name: String, lat_deg: f32, lon_deg: f32) -> Self {
        Self {
            name,
            position: Position::new(lat_deg, lon_deg),
        }
    }
}

#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum FrequencyType {
    Atis,
    Arrival,
    Departure,
    #[serde(rename = "arrivalDeparture")]
    ArrivalDeparture,
    Ground,
    Tower,
    Unicom,
}

// TODO: return None when the source type is the Other variant
impl From<data::FrequencyType> for FrequencyType {
    fn from(source: data::FrequencyType) -> Self {
        use data::FrequencyType as DFT;

        match source {
            DFT::Atis => Self::Atis,
            DFT::Arrival => Self::Arrival,
            DFT::Departure => Self::Departure,
            DFT::ArrivalDeparture => Self::ArrivalDeparture,
            DFT::Ground => Self::Ground,
            DFT::Tower => Self::Tower,
            DFT::Unicom | DFT::Other => Self::Unicom,
        }
    }
}
<commit_msg>backend: Skip parsing of unknown radio frequency types<commit_after>mod data;

pub use self::data::AirportType;

use self::data::DataType;
use crate::path::FilePath;
use anyhow::{anyhow, Result};
use chrono::{Duration, NaiveDate, Utc};
use serde_derive::Serialize;
use std::borrow::Cow;
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::fs::{self, File};
use std::io::{self, BufWriter};
use std::path::{Path, PathBuf};

const BASE_URL: &str = "https://ourairports.com/data";

fn validated_dir() -> Result<PathBuf> {
    FilePath::LocalData.validated_subdir("airport_data/")
}

pub fn ensure_updated() -> Result<()> {
    let base_dir = validated_dir()?;
    let mut last_update = LastUpdate::load(&base_dir);

    if !last_update.needs_update() {
        return Ok(());
    }

    download_latest(&base_dir)?;
    last_update.set_to_today()?;

    Ok(())
}

fn download_latest<P>(dir: P) -> Result<()>
where
    P: AsRef<Path>,
{
    println!("updating airport data");

    for dtype in &DataType::ALL {
        download_file(dtype.filename(), &dir)?;
    }

    println!("finished updating airport data");
    Ok(())
}

fn download_file<S, P>(name: S, dir: P) -> Result<()>
where
    S: AsRef<str>,
    P: AsRef<Path>,
{
    let name = name.as_ref();
    let dir = dir.as_ref();

    println!(".. downloading {}", name);

    let path = dir.join(name);
    let backup = Backup::create(&path)?;

    let resp = attohttpc::get(format!("{}/{}", BASE_URL, name))
        .timeout(Duration::seconds(15).to_std().unwrap())
        .send()?;

    if !resp.is_success() {
        backup.restore()?;

        return Err(anyhow!(
            "received code {} while downloading {}",
            resp.status(),
            name
        ));
    }

    let file = match File::create(path) {
        Ok(file) => file,
        Err(err) => {
            backup.restore()?;
            return Err(err.into());
        }
    };

    let (_, _, mut reader) = resp.split();
    let mut writer = BufWriter::new(file);

    match io::copy(&mut reader, &mut writer) {
        Ok(_) => Ok(()),
        Err(err) => {
            backup.restore()?;
            Err(err.into())
        }
    }
}

struct LastUpdate(PathBuf);

impl LastUpdate {
    const DATE_FORMAT: &'static str = "%Y-%m-%d";

    fn load<P>(base_dir: P) -> Self
    where
        P: Into<PathBuf>,
    {
        let mut path = base_dir.into();
        path.push(".last_updated");

        Self(path)
    }

    fn needs_update(&self) -> bool {
        if !self.path().exists() {
            return true;
        }

        let updated_at = fs::read_to_string(self.path())
            .ok()
            .and_then(|updated_at| NaiveDate::parse_from_str(&updated_at, Self::DATE_FORMAT).ok());

        let updated_at = match updated_at {
            Some(updated_at) => updated_at,
            None => return true,
        };

        let today = Utc::now().naive_utc().date();

        if today - updated_at >= Duration::weeks(2) {
            return true;
        }

        false
    }

    fn set_to_today(&mut self) -> Result<()> {
        let today = Utc::now().naive_utc().date();
        fs::write(self.path(), today.format(Self::DATE_FORMAT).to_string())?;
        Ok(())
    }

    #[inline(always)]
    fn path(&self) -> &Path {
        &self.0
    }
}

enum Backup {
    Exists(PathBuf),
    DoesntExist,
}

impl Backup {
    fn create<P>(file: P) -> Result<Self>
    where
        P: AsRef<Path>,
    {
        let file = file.as_ref();

        if !file.exists() {
            return Ok(Self::DoesntExist);
        }

        let extension = match file.extension() {
            Some(ext) => format!("{}.bak", ext.to_string_lossy()).into(),
            None => Cow::Borrowed(".bak"),
        };

        let mut backup_path = PathBuf::from(file);
        backup_path.set_extension(extension.as_ref());

        fs::rename(file, &backup_path)?;

        Ok(Self::Exists(backup_path))
    }

    fn restore(self) -> Result<()> {
        match self {
            Self::Exists(path) => {
                if !path.exists() {
                    return Ok(());
                }

                let mut original_path = path.clone();
                original_path.set_extension("");

                if original_path.exists() {
                    fs::remove_file(&original_path)?;
                }

                fs::rename(path, original_path)?;
                Ok(())
            }
            Self::DoesntExist => Ok(()),
        }
    }
}

#[derive(Debug, Serialize)]
pub struct Airport {
    pub icao: String,
    #[serde(skip_serializing)]
    pub class: AirportType,
    pub position: Position,
    pub runways: Vec<Runway>,
    pub frequencies: HashMap<FrequencyType, String>,
    #[serde(skip_serializing)]
    pub country_name: String,
}

impl Airport {
    const ICAO_MAX_LEN: usize = 4;

    pub fn load_all() -> Result<Vec<Self>> {
        let dir = validated_dir()?;

        let airports = data::Airport::from_dir(&dir)?;
        let mut runways = data::Runway::from_dir(&dir)?;
        let mut frequencies = data::Frequency::from_dir(&dir)?;
        let countries = data::Country::from_dir(&dir)?;

        let mut results = Vec::with_capacity(airports.len());

        for airport in airports {
            if airport.icao.len() > Self::ICAO_MAX_LEN {
                continue;
            }

            let runways = match runways.remove(&airport.id) {
                Some(runways) => runways,
                None => continue,
            };

            let frequencies = frequencies
                .remove(&airport.id)
                .map(|freqs| {
                    freqs
                        .into_iter()
                        .filter_map(|(kind, freq)| {
                            kind.try_into().map(|kind| (kind, freq.mhz)).ok()
                        })
                        .collect()
                })
                .unwrap_or_else(HashMap::new);

            let country = match countries.iter().find(|c| c.code == airport.country_code) {
                Some(country) => country,
                None => continue,
            };

            let result = Self {
                icao: airport.icao,
                class: airport.class,
                position: Position::new(airport.lat_deg, airport.lon_deg),
                runways: runways.into_iter().filter_map(Runway::from_data).collect(),
                frequencies,
                country_name: country.name.clone(),
            };

            results.push(result);
        }

        results.shrink_to_fit();
        results.sort_unstable_by(|x, y| x.icao.cmp(&y.icao));

        Ok(results)
    }
}

impl PartialEq for Airport {
    fn eq(&self, other: &Self) -> bool {
        self.icao == other.icao
    }
}

#[derive(Copy, Clone, Debug, Serialize)]
pub struct Position {
    #[serde(rename = "latitudeDeg")]
    pub latitude_deg: f32,
    #[serde(rename = "longitudeDeg")]
    pub longitude_deg: f32,
}

impl Position {
    #[inline(always)]
    pub fn new(latitude_deg: f32, longitude_deg: f32) -> Self {
        Self {
            latitude_deg,
            longitude_deg,
        }
    }
}

#[derive(Debug, Serialize)]
pub struct Runway {
    #[serde(rename = "lengthFT")]
    pub length_ft: Option<u32>,
    #[serde(rename = "widthFT")]
    pub width_ft: Option<u32>,
    #[serde(rename = "heMarker")]
    pub he_marker: Option<RunwayMarker>,
    #[serde(rename = "leMarker")]
    pub le_marker: Option<RunwayMarker>,
}

impl Runway {
    fn from_data(source: data::Runway) -> Option<Self> {
        let he_marker = match (source.le_ident, source.le_lat_deg, source.le_lon_deg) {
            (Some(name), Some(lat), Some(lon)) => Some(RunwayMarker::new(name, lat, lon)),
            _ => None,
        };

        let le_marker = match (source.he_ident, source.he_lat_deg, source.he_lon_deg) {
            (Some(name), Some(lat), Some(lon)) => Some(RunwayMarker::new(name, lat, lon)),
            _ => None,
        };

        Some(Self {
            length_ft: source.length_ft,
            width_ft: source.width_ft,
            he_marker,
            le_marker,
        })
    }
}

#[derive(Debug, Serialize)]
pub struct RunwayMarker {
    pub name: String,
    pub position: Position,
}

impl RunwayMarker {
    #[inline(always)]
    pub fn new(name: String, lat_deg: f32, lon_deg: f32) -> Self {
        Self {
            name,
            position: Position::new(lat_deg, lon_deg),
        }
    }
}

#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum FrequencyType {
    Atis,
    Arrival,
    Departure,
    #[serde(rename = "arrivalDeparture")]
    ArrivalDeparture,
    Ground,
    Tower,
    Unicom,
}

impl TryFrom<data::FrequencyType> for FrequencyType {
    type Error = ();

    fn try_from(value: data::FrequencyType) -> Result<Self, Self::Error> {
        use data::FrequencyType as DFT;

        match value {
            DFT::Atis => Ok(Self::Atis),
            DFT::Arrival => Ok(Self::Arrival),
            DFT::Departure => Ok(Self::Departure),
            DFT::ArrivalDeparture => Ok(Self::ArrivalDeparture),
            DFT::Ground => Ok(Self::Ground),
            DFT::Tower => Ok(Self::Tower),
            DFT::Unicom => Ok(Self::Unicom),
            DFT::Other => Err(()),
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Sanity checking performed by rustbuild before actually executing anything.
//!
//! This module contains the implementation of ensuring that the build
//! environment looks reasonable before progressing. This will verify that
//! various programs like git and python exist, along with ensuring that all C
//! compilers for cross-compiling are found.
//!
//! In theory if we get past this phase it's a bug if a build fails, but in
//! practice that's likely not true!

use std::collections::HashSet;
use std::env;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::process::Command;

use build_helper::output;

use Build;

pub fn check(build: &mut Build) {
    let mut checked = HashSet::new();
    let path = env::var_os("PATH").unwrap_or(OsString::new());
    // On Windows, quotes are invalid characters for filename paths, and if
    // one is present as part of the PATH then that can lead to the system
    // being unable to identify the files properly. See
    // https://github.com/rust-lang/rust/issues/34959 for more details.
    if cfg!(windows) {
        if path.to_string_lossy().contains("\"") {
            panic!("PATH contains invalid character '\"'");
        }
    }
    let have_cmd = |cmd: &OsStr| {
        for path in env::split_paths(&path) {
            let target = path.join(cmd);
            let mut cmd_alt = cmd.to_os_string();
            cmd_alt.push(".exe");
            if target.is_file() ||
               target.with_extension("exe").exists() ||
               target.join(cmd_alt).exists() {
                return Some(target);
            }
        }
        return None;
    };

    let mut need_cmd = |cmd: &OsStr| {
        if !checked.insert(cmd.to_owned()) {
            return
        }
        if have_cmd(cmd).is_none() {
            panic!("\n\ncouldn't find required command: {:?}\n\n", cmd);
        }
    };

    // If we've got a git directory we're gona need git to update
    // submodules and learn about various other aspects.
    if build.src_is_git {
        need_cmd("git".as_ref());
    }

    // We need cmake, but only if we're actually building LLVM
    for host in build.config.host.iter() {
        if let Some(config) = build.config.target_config.get(host) {
            if config.llvm_config.is_some() {
                continue
            }
        }
        need_cmd("cmake".as_ref());
        if build.config.ninja {
            // Some Linux distros rename `ninja` to `ninja-build`.
            // CMake can work with either binary name.
            if have_cmd("ninja-build".as_ref()).is_none() {
                need_cmd("ninja".as_ref());
            }
        }
        break
    }

    if build.config.python.is_none() {
        build.config.python = have_cmd("python2.7".as_ref());
    }
    if build.config.python.is_none() {
        build.config.python = have_cmd("python2".as_ref());
    }
    if build.config.python.is_none() {
        need_cmd("python".as_ref());
        build.config.python = Some("python".into());
    }
    need_cmd(build.config.python.as_ref().unwrap().as_ref());

    if let Some(ref s) = build.config.nodejs {
        need_cmd(s.as_ref());
    } else {
        // Look for the nodejs command, needed for emscripten testing
        if let Some(node) = have_cmd("node".as_ref()) {
            build.config.nodejs = Some(node);
        } else if let Some(node) = have_cmd("nodejs".as_ref()) {
            build.config.nodejs = Some(node);
        }
    }

    if let Some(ref gdb) = build.config.gdb {
        need_cmd(gdb.as_ref());
    } else {
        build.config.gdb = have_cmd("gdb".as_ref());
    }

    // We're gonna build some custom C code here and there, host triples
    // also build some C++ shims for LLVM so we need a C++ compiler.
    for target in build.config.target.iter() {
        // On emscripten we don't actually need the C compiler to just
        // build the target artifacts, only for testing. For the sake
        // of easier bot configuration, just skip detection.
        if target.contains("emscripten") {
            continue;
        }

        need_cmd(build.cc(target).as_ref());
        if let Some(ar) = build.ar(target) {
            need_cmd(ar.as_ref());
        }
    }
    for host in build.config.host.iter() {
        need_cmd(build.cxx(host).as_ref());
    }

    // The msvc hosts don't use jemalloc, turn it off globally to
    // avoid packaging the dummy liballoc_jemalloc on that platform.
    for host in build.config.host.iter() {
        if host.contains("msvc") {
            build.config.use_jemalloc = false;
        }
    }

    // Externally configured LLVM requires FileCheck to exist
    let filecheck = build.llvm_filecheck(&build.config.build);
    if !filecheck.starts_with(&build.out) && !filecheck.exists() && build.config.codegen_tests {
        panic!("FileCheck executable {:?} does not exist", filecheck);
    }

    for target in build.config.target.iter() {
        // Can't compile for iOS unless we're on macOS
        if target.contains("apple-ios") &&
           !build.config.build.contains("apple-darwin") {
            panic!("the iOS target is only supported on macOS");
        }

        // Make sure musl-root is valid if specified
        if target.contains("musl") && !target.contains("mips") {
            match build.musl_root(target) {
                Some(root) => {
                    if fs::metadata(root.join("lib/libc.a")).is_err() {
                        panic!("couldn't find libc.a in musl dir: {}",
                               root.join("lib").display());
                    }
                    if fs::metadata(root.join("lib/libunwind.a")).is_err() {
                        panic!("couldn't find libunwind.a in musl dir: {}",
                               root.join("lib").display());
                    }
                }
                None => {
                    panic!("when targeting MUSL either the rust.musl-root \
                            option or the target.$TARGET.musl-root option must \
                            be specified in config.toml")
                }
            }
        }

        if target.contains("msvc") {
            // There are three builds of cmake on windows: MSVC, MinGW, and
            // Cygwin. The Cygwin build does not have generators for Visual
            // Studio, so detect that here and error.
            let out = output(Command::new("cmake").arg("--help"));
            if !out.contains("Visual Studio") {
                panic!("
cmake does not support Visual Studio generators.

This is likely due to it being an msys/cygwin build of cmake,
rather than the required windows version, built using MinGW
or Visual Studio.

If you are building under msys2 try installing the mingw-w64-x86_64-cmake
package instead of cmake:

$ pacman -R cmake && pacman -S mingw-w64-x86_64-cmake
");
            }
        }
    }

    for host in build.flags.host.iter() {
        if !build.config.host.contains(host) {
            panic!("specified host `{}` is not in the ./configure list", host);
        }
    }
    for target in build.flags.target.iter() {
        if !build.config.target.contains(target) {
            panic!("specified target `{}` is not in the ./configure list",
                   target);
        }
    }

    let run = |cmd: &mut Command| {
        cmd.output().map(|output| {
            String::from_utf8_lossy(&output.stdout)
                   .lines().next().unwrap()
                   .to_string()
        })
    };
    build.lldb_version = run(Command::new("lldb").arg("--version")).ok();
    if build.lldb_version.is_some() {
        build.lldb_python_dir = run(Command::new("lldb").arg("-P")).ok();
    }

    if let Some(ref s) = build.config.ccache {
        need_cmd(s.as_ref());
    }
}
<commit_msg>Auto merge of #41713 - cuviper:cmake-sanitizers, r=alexcrichton<commit_after>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Sanity checking performed by rustbuild before actually executing anything.
//!
//! This module contains the implementation of ensuring that the build
//! environment looks reasonable before progressing. This will verify that
//! various programs like git and python exist, along with ensuring that all C
//! compilers for cross-compiling are found.
//!
//! In theory if we get past this phase it's a bug if a build fails, but in
//! practice that's likely not true!

use std::collections::HashSet;
use std::env;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::process::Command;

use build_helper::output;

use Build;

pub fn check(build: &mut Build) {
    let mut checked = HashSet::new();
    let path = env::var_os("PATH").unwrap_or(OsString::new());
    // On Windows, quotes are invalid characters for filename paths, and if
    // one is present as part of the PATH then that can lead to the system
    // being unable to identify the files properly. See
    // https://github.com/rust-lang/rust/issues/34959 for more details.
    if cfg!(windows) {
        if path.to_string_lossy().contains("\"") {
            panic!("PATH contains invalid character '\"'");
        }
    }
    let have_cmd = |cmd: &OsStr| {
        for path in env::split_paths(&path) {
            let target = path.join(cmd);
            let mut cmd_alt = cmd.to_os_string();
            cmd_alt.push(".exe");
            if target.is_file() ||
               target.with_extension("exe").exists() ||
               target.join(cmd_alt).exists() {
                return Some(target);
            }
        }
        return None;
    };

    let mut need_cmd = |cmd: &OsStr| {
        if !checked.insert(cmd.to_owned()) {
            return
        }
        if have_cmd(cmd).is_none() {
            panic!("\n\ncouldn't find required command: {:?}\n\n", cmd);
        }
    };

    // If we've got a git directory we're gona need git to update
    // submodules and learn about various other aspects.
    if build.src_is_git {
        need_cmd("git".as_ref());
    }

    // We need cmake, but only if we're actually building LLVM or sanitizers.
    let building_llvm = build.config.host.iter()
        .filter_map(|host| build.config.target_config.get(host))
        .any(|config| config.llvm_config.is_none());
    if building_llvm || build.config.sanitizers {
        need_cmd("cmake".as_ref());
    }

    // Ninja is currently only used for LLVM itself.
    if building_llvm && build.config.ninja {
        // Some Linux distros rename `ninja` to `ninja-build`.
        // CMake can work with either binary name.
        if have_cmd("ninja-build".as_ref()).is_none() {
            need_cmd("ninja".as_ref());
        }
    }

    if build.config.python.is_none() {
        build.config.python = have_cmd("python2.7".as_ref());
    }
    if build.config.python.is_none() {
        build.config.python = have_cmd("python2".as_ref());
    }
    if build.config.python.is_none() {
        need_cmd("python".as_ref());
        build.config.python = Some("python".into());
    }
    need_cmd(build.config.python.as_ref().unwrap().as_ref());

    if let Some(ref s) = build.config.nodejs {
        need_cmd(s.as_ref());
    } else {
        // Look for the nodejs command, needed for emscripten testing
        if let Some(node) = have_cmd("node".as_ref()) {
            build.config.nodejs = Some(node);
        } else if let Some(node) = have_cmd("nodejs".as_ref()) {
            build.config.nodejs = Some(node);
        }
    }

    if let Some(ref gdb) = build.config.gdb {
        need_cmd(gdb.as_ref());
    } else {
        build.config.gdb = have_cmd("gdb".as_ref());
    }

    // We're gonna build some custom C code here and there, host triples
    // also build some C++ shims for LLVM so we need a C++ compiler.
    for target in build.config.target.iter() {
        // On emscripten we don't actually need the C compiler to just
        // build the target artifacts, only for testing. For the sake
        // of easier bot configuration, just skip detection.
        if target.contains("emscripten") {
            continue;
        }

        need_cmd(build.cc(target).as_ref());
        if let Some(ar) = build.ar(target) {
            need_cmd(ar.as_ref());
        }
    }
    for host in build.config.host.iter() {
        need_cmd(build.cxx(host).as_ref());
    }

    // The msvc hosts don't use jemalloc, turn it off globally to
    // avoid packaging the dummy liballoc_jemalloc on that platform.
    for host in build.config.host.iter() {
        if host.contains("msvc") {
            build.config.use_jemalloc = false;
        }
    }

    // Externally configured LLVM requires FileCheck to exist
    let filecheck = build.llvm_filecheck(&build.config.build);
    if !filecheck.starts_with(&build.out) && !filecheck.exists() && build.config.codegen_tests {
        panic!("FileCheck executable {:?} does not exist", filecheck);
    }

    for target in build.config.target.iter() {
        // Can't compile for iOS unless we're on macOS
        if target.contains("apple-ios") &&
           !build.config.build.contains("apple-darwin") {
            panic!("the iOS target is only supported on macOS");
        }

        // Make sure musl-root is valid if specified
        if target.contains("musl") && !target.contains("mips") {
            match build.musl_root(target) {
                Some(root) => {
                    if fs::metadata(root.join("lib/libc.a")).is_err() {
                        panic!("couldn't find libc.a in musl dir: {}",
                               root.join("lib").display());
                    }
                    if fs::metadata(root.join("lib/libunwind.a")).is_err() {
                        panic!("couldn't find libunwind.a in musl dir: {}",
                               root.join("lib").display());
                    }
                }
                None => {
                    panic!("when targeting MUSL either the rust.musl-root \
                            option or the target.$TARGET.musl-root option must \
                            be specified in config.toml")
                }
            }
        }

        if target.contains("msvc") {
            // There are three builds of cmake on windows: MSVC, MinGW, and
            // Cygwin. The Cygwin build does not have generators for Visual
            // Studio, so detect that here and error.
            let out = output(Command::new("cmake").arg("--help"));
            if !out.contains("Visual Studio") {
                panic!("
cmake does not support Visual Studio generators.

This is likely due to it being an msys/cygwin build of cmake,
rather than the required windows version, built using MinGW
or Visual Studio.

If you are building under msys2 try installing the mingw-w64-x86_64-cmake
package instead of cmake:

$ pacman -R cmake && pacman -S mingw-w64-x86_64-cmake
");
            }
        }
    }

    for host in build.flags.host.iter() {
        if !build.config.host.contains(host) {
            panic!("specified host `{}` is not in the ./configure list", host);
        }
    }
    for target in build.flags.target.iter() {
        if !build.config.target.contains(target) {
            panic!("specified target `{}` is not in the ./configure list",
                   target);
        }
    }

    let run = |cmd: &mut Command| {
        cmd.output().map(|output| {
            String::from_utf8_lossy(&output.stdout)
                   .lines().next().unwrap()
                   .to_string()
        })
    };
    build.lldb_version = run(Command::new("lldb").arg("--version")).ok();
    if build.lldb_version.is_some() {
        build.lldb_python_dir = run(Command::new("lldb").arg("-P")).ok();
    }

    if let Some(ref s) = build.config.ccache {
        need_cmd(s.as_ref());
    }
}
<|endoftext|>


<commit_before>use std::fs::{self, File, OpenOptions};
use std::io;
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::{Display, Path, PathBuf};

use fs2::{lock_contended_error, FileExt};
#[allow(unused_imports)]
use libc;
use termcolor::Color::Cyan;

use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::paths;
use crate::util::Config;

#[derive(Debug)]
pub struct FileLock {
    f: Option<File>,
    path: PathBuf,
    state: State,
}

#[derive(PartialEq, Debug)]
enum State {
    Unlocked,
    Shared,
    Exclusive,
}

impl FileLock {
    /// Returns the underlying file handle of this lock.
    pub fn file(&self) -> &File {
        self.f.as_ref().unwrap()
    }

    /// Returns the underlying path that this lock points to.
    ///
    /// Note that special care must be taken to ensure that the path is not
    /// referenced outside the lifetime of this lock.
    pub fn path(&self) -> &Path {
        assert_ne!(self.state, State::Unlocked);
        &self.path
    }

    /// Returns the parent path containing this file
    pub fn parent(&self) -> &Path {
        assert_ne!(self.state, State::Unlocked);
        self.path.parent().unwrap()
    }

    /// Removes all sibling files to this locked file.
    ///
    /// This can be useful if a directory is locked with a sentinel file but it
    /// needs to be cleared out as it may be corrupt.
    pub fn remove_siblings(&self) -> CargoResult<()> {
        let path = self.path();
        for entry in path.parent().unwrap().read_dir()? {
            let entry = entry?;
            if Some(&entry.file_name()[..]) == path.file_name() {
                continue;
            }
            let kind = entry.file_type()?;
            if kind.is_dir() {
                paths::remove_dir_all(entry.path())?;
            } else {
                paths::remove_file(entry.path())?;
            }
        }
        Ok(())
    }
}

impl Read for FileLock {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.file().read(buf)
    }
}

impl Seek for FileLock {
    fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {
        self.file().seek(to)
    }
}

impl Write for FileLock {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.file().write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.file().flush()
    }
}

impl Drop for FileLock {
    fn drop(&mut self) {
        if self.state != State::Unlocked {
            if let Some(f) = self.f.take() {
                let _ = f.unlock();
            }
        }
    }
}

/// A "filesystem" is intended to be a globally shared, hence locked, resource
/// in Cargo.
///
/// The `Path` of a filesystem cannot be learned unless it's done in a locked
/// fashion, and otherwise functions on this structure are prepared to handle
/// concurrent invocations across multiple instances of Cargo.
#[derive(Clone, Debug)]
pub struct Filesystem {
    root: PathBuf,
}

impl Filesystem {
    /// Creates a new filesystem to be rooted at the given path.
    pub fn new(path: PathBuf) -> Filesystem {
        Filesystem { root: path }
    }

    /// Like `Path::join`, creates a new filesystem rooted at this filesystem
    /// joined with the given path.
    pub fn join<T: AsRef<Path>>(&self, other: T) -> Filesystem {
        Filesystem::new(self.root.join(other))
    }

    /// Like `Path::push`, pushes a new path component onto this filesystem.
    pub fn push<T: AsRef<Path>>(&mut self, other: T) {
        self.root.push(other);
    }

    /// Consumes this filesystem and returns the underlying `PathBuf`.
    ///
    /// Note that this is a relatively dangerous operation and should be used
    /// with great caution!.
    pub fn into_path_unlocked(self) -> PathBuf {
        self.root
    }

    /// Returns the underlying `Path`.
    ///
    /// Note that this is a relatively dangerous operation and should be used
    /// with great caution!.
    pub fn as_path_unlocked(&self) -> &Path {
        &self.root
    }

    /// Creates the directory pointed to by this filesystem.
    ///
    /// Handles errors where other Cargo processes are also attempting to
    /// concurrently create this directory.
    pub fn create_dir(&self) -> io::Result<()> {
        fs::create_dir_all(&self.root)
    }

    /// Returns an adaptor that can be used to print the path of this
    /// filesystem.
    pub fn display(&self) -> Display<'_> {
        self.root.display()
    }

    /// Opens exclusive access to a file, returning the locked version of a
    /// file.
    ///
    /// This function will create a file at `path` if it doesn't already exist
    /// (including intermediate directories), and then it will acquire an
    /// exclusive lock on `path`. If the process must block waiting for the
    /// lock, the `msg` is printed to `config`.
    ///
    /// The returned file can be accessed to look at the path and also has
    /// read/write access to the underlying file.
    pub fn open_rw<P>(&self, path: P, config: &Config, msg: &str) -> CargoResult<FileLock>
    where
        P: AsRef<Path>,
    {
        self.open(
            path.as_ref(),
            OpenOptions::new().read(true).write(true).create(true),
            State::Exclusive,
            config,
            msg,
        )
    }

    /// Opens shared access to a file, returning the locked version of a file.
    ///
    /// This function will fail if `path` doesn't already exist, but if it does
    /// then it will acquire a shared lock on `path`. If the process must block
    /// waiting for the lock, the `msg` is printed to `config`.
    ///
    /// The returned file can be accessed to look at the path and also has read
    /// access to the underlying file. Any writes to the file will return an
    /// error.
    pub fn open_ro<P>(&self, path: P, config: &Config, msg: &str) -> CargoResult<FileLock>
    where
        P: AsRef<Path>,
    {
        self.open(
            path.as_ref(),
            OpenOptions::new().read(true),
            State::Shared,
            config,
            msg,
        )
    }

    fn open(
        &self,
        path: &Path,
        opts: &OpenOptions,
        state: State,
        config: &Config,
        msg: &str,
    ) -> CargoResult<FileLock> {
        let path = self.root.join(path);

        // If we want an exclusive lock then if we fail because of NotFound it's
        // likely because an intermediate directory didn't exist, so try to
        // create the directory and then continue.
        let f = opts
            .open(&path)
            .or_else(|e| {
                if e.kind() == io::ErrorKind::NotFound && state == State::Exclusive {
                    fs::create_dir_all(path.parent().unwrap())?;
                    opts.open(&path)
                } else {
                    Err(e)
                }
            })
            .chain_err(|| format!("failed to open: {}", path.display()))?;
        match state {
            State::Exclusive => {
                acquire(config, msg, &path, &|| f.try_lock_exclusive(), &|| {
                    f.lock_exclusive()
                })?;
            }
            State::Shared => {
                acquire(config, msg, &path, &|| f.try_lock_shared(), &|| {
                    f.lock_shared()
                })?;
            }
            State::Unlocked => {}
        }
        Ok(FileLock {
            f: Some(f),
            path,
            state,
        })
    }
}

impl PartialEq<Path> for Filesystem {
    fn eq(&self, other: &Path) -> bool {
        self.root == other
    }
}

impl PartialEq<Filesystem> for Path {
    fn eq(&self, other: &Filesystem) -> bool {
        self == other.root
    }
}

/// Acquires a lock on a file in a "nice" manner.
///
/// Almost all long-running blocking actions in Cargo have a status message
/// associated with them as we're not sure how long they'll take. Whenever a
/// conflicted file lock happens, this is the case (we're not sure when the lock
/// will be released).
///
/// This function will acquire the lock on a `path`, printing out a nice message
/// to the console if we have to wait for it. It will first attempt to use `try`
/// to acquire a lock on the crate, and in the case of contention it will emit a
/// status message based on `msg` to `config`'s shell, and then use `block` to
/// block waiting to acquire a lock.
///
/// Returns an error if the lock could not be acquired or if any error other
/// than a contention error happens.
fn acquire(
    config: &Config,
    msg: &str,
    path: &Path,
    r#try: &dyn Fn() -> io::Result<()>,
    block: &dyn Fn() -> io::Result<()>,
) -> CargoResult<()> {
    // File locking on Unix is currently implemented via `flock`, which is known
    // to be broken on NFS. We could in theory just ignore errors that happen on
    // NFS, but apparently the failure mode [1] for `flock` on NFS is **blocking
    // forever**, even if the "non-blocking" flag is passed!
    //
    // As a result, we just skip all file locks entirely on NFS mounts. That
    // should avoid calling any `flock` functions at all, and it wouldn't work
    // there anyway.
    //
    // [1]: https://github.com/rust-lang/cargo/issues/2615
    if is_on_nfs_mount(path) {
        return Ok(());
    }

    match r#try() {
        Ok(()) => return Ok(()),

        // In addition to ignoring NFS which is commonly not working we also
        // just ignore locking on filesystems that look like they don't
        // implement file locking. We detect that here via the return value of
        // locking (e.g., inspecting errno).
        #[cfg(unix)]
        Err(ref e) if e.raw_os_error() == Some(libc::ENOTSUP) => return Ok(()),

        #[cfg(target_os = "linux")]
        Err(ref e) if e.raw_os_error() == Some(libc::ENOSYS) => return Ok(()),

        Err(e) => {
            if e.raw_os_error() != lock_contended_error().raw_os_error() {
                let e = failure::Error::from(e);
                let cx = format!("failed to lock file: {}", path.display());
                return Err(e.context(cx).into());
            }
        }
    }
    let msg = format!("waiting for file lock on {}", msg);
    config.shell().status_with_color("Blocking", &msg, Cyan)?;

    // We're about to block the current process and not really do anything
    // productive for what could possibly be a very long time. We could be
    // waiting, for example, on another Cargo to finish a download, finish an
    // entire build, etc. Since we're not doing anything productive we're not
    // making good use of our jobserver token, if we have one.
    //
    // This can typically come about if `cargo` is invoked from `make` (or some
    // other jobserver-providing system). In this situation it's actually best
    // if we release the token back to the original jobserver to let some other
    // cpu-hungry work continue to make progress. After we're done blocking
    // we'll block waiting to reacquire a token as we'll probably be doing cpu
    // hungry work ourselves.
    let jobserver = config.jobserver_from_env();
    if let Some(server) = jobserver {
        server.release_raw()?;
    }
    let result = block().chain_err(|| format!("failed to lock file: {}", path.display()));
    if let Some(server) = jobserver {
        server.acquire_raw()?;
    }
    return Ok(result?);

    #[cfg(all(target_os = "linux", not(target_env = "musl")))]
    fn is_on_nfs_mount(path: &Path) -> bool {
        use std::ffi::CString;
        use std::mem;
        use std::os::unix::prelude::*;

        let path = match CString::new(path.as_os_str().as_bytes()) {
            Ok(path) => path,
            Err(_) => return false,
        };

        unsafe {
            let mut buf: libc::statfs = mem::zeroed();
            let r = libc::statfs(path.as_ptr(), &mut buf);

            r == 0 && buf.f_type as u32 == libc::NFS_SUPER_MAGIC as u32
        }
    }

    #[cfg(any(not(target_os = "linux"), target_env = "musl"))]
    fn is_on_nfs_mount(_path: &Path) -> bool {
        false
    }
}
<commit_msg>Auto merge of #7201 - alexcrichton:revert-thing, r=ehuss<commit_after>use std::fs::{self, File, OpenOptions};
use std::io;
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::{Display, Path, PathBuf};

use fs2::{lock_contended_error, FileExt};
#[allow(unused_imports)]
use libc;
use termcolor::Color::Cyan;

use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::paths;
use crate::util::Config;

#[derive(Debug)]
pub struct FileLock {
    f: Option<File>,
    path: PathBuf,
    state: State,
}

#[derive(PartialEq, Debug)]
enum State {
    Unlocked,
    Shared,
    Exclusive,
}

impl FileLock {
    /// Returns the underlying file handle of this lock.
    pub fn file(&self) -> &File {
        self.f.as_ref().unwrap()
    }

    /// Returns the underlying path that this lock points to.
    ///
    /// Note that special care must be taken to ensure that the path is not
    /// referenced outside the lifetime of this lock.
    pub fn path(&self) -> &Path {
        assert_ne!(self.state, State::Unlocked);
        &self.path
    }

    /// Returns the parent path containing this file
    pub fn parent(&self) -> &Path {
        assert_ne!(self.state, State::Unlocked);
        self.path.parent().unwrap()
    }

    /// Removes all sibling files to this locked file.
    ///
    /// This can be useful if a directory is locked with a sentinel file but it
    /// needs to be cleared out as it may be corrupt.
    pub fn remove_siblings(&self) -> CargoResult<()> {
        let path = self.path();
        for entry in path.parent().unwrap().read_dir()? {
            let entry = entry?;
            if Some(&entry.file_name()[..]) == path.file_name() {
                continue;
            }
            let kind = entry.file_type()?;
            if kind.is_dir() {
                paths::remove_dir_all(entry.path())?;
            } else {
                paths::remove_file(entry.path())?;
            }
        }
        Ok(())
    }
}

impl Read for FileLock {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.file().read(buf)
    }
}

impl Seek for FileLock {
    fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {
        self.file().seek(to)
    }
}

impl Write for FileLock {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.file().write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.file().flush()
    }
}

impl Drop for FileLock {
    fn drop(&mut self) {
        if self.state != State::Unlocked {
            if let Some(f) = self.f.take() {
                let _ = f.unlock();
            }
        }
    }
}

/// A "filesystem" is intended to be a globally shared, hence locked, resource
/// in Cargo.
///
/// The `Path` of a filesystem cannot be learned unless it's done in a locked
/// fashion, and otherwise functions on this structure are prepared to handle
/// concurrent invocations across multiple instances of Cargo.
#[derive(Clone, Debug)]
pub struct Filesystem {
    root: PathBuf,
}

impl Filesystem {
    /// Creates a new filesystem to be rooted at the given path.
    pub fn new(path: PathBuf) -> Filesystem {
        Filesystem { root: path }
    }

    /// Like `Path::join`, creates a new filesystem rooted at this filesystem
    /// joined with the given path.
    pub fn join<T: AsRef<Path>>(&self, other: T) -> Filesystem {
        Filesystem::new(self.root.join(other))
    }

    /// Like `Path::push`, pushes a new path component onto this filesystem.
    pub fn push<T: AsRef<Path>>(&mut self, other: T) {
        self.root.push(other);
    }

    /// Consumes this filesystem and returns the underlying `PathBuf`.
    ///
    /// Note that this is a relatively dangerous operation and should be used
    /// with great caution!.
    pub fn into_path_unlocked(self) -> PathBuf {
        self.root
    }

    /// Returns the underlying `Path`.
    ///
    /// Note that this is a relatively dangerous operation and should be used
    /// with great caution!.
    pub fn as_path_unlocked(&self) -> &Path {
        &self.root
    }

    /// Creates the directory pointed to by this filesystem.
    ///
    /// Handles errors where other Cargo processes are also attempting to
    /// concurrently create this directory.
    pub fn create_dir(&self) -> io::Result<()> {
        fs::create_dir_all(&self.root)
    }

    /// Returns an adaptor that can be used to print the path of this
    /// filesystem.
    pub fn display(&self) -> Display<'_> {
        self.root.display()
    }

    /// Opens exclusive access to a file, returning the locked version of a
    /// file.
    ///
    /// This function will create a file at `path` if it doesn't already exist
    /// (including intermediate directories), and then it will acquire an
    /// exclusive lock on `path`. If the process must block waiting for the
    /// lock, the `msg` is printed to `config`.
    ///
    /// The returned file can be accessed to look at the path and also has
    /// read/write access to the underlying file.
    pub fn open_rw<P>(&self, path: P, config: &Config, msg: &str) -> CargoResult<FileLock>
    where
        P: AsRef<Path>,
    {
        self.open(
            path.as_ref(),
            OpenOptions::new().read(true).write(true).create(true),
            State::Exclusive,
            config,
            msg,
        )
    }

    /// Opens shared access to a file, returning the locked version of a file.
    ///
    /// This function will fail if `path` doesn't already exist, but if it does
    /// then it will acquire a shared lock on `path`. If the process must block
    /// waiting for the lock, the `msg` is printed to `config`.
    ///
    /// The returned file can be accessed to look at the path and also has read
    /// access to the underlying file. Any writes to the file will return an
    /// error.
    pub fn open_ro<P>(&self, path: P, config: &Config, msg: &str) -> CargoResult<FileLock>
    where
        P: AsRef<Path>,
    {
        self.open(
            path.as_ref(),
            OpenOptions::new().read(true),
            State::Shared,
            config,
            msg,
        )
    }

    fn open(
        &self,
        path: &Path,
        opts: &OpenOptions,
        state: State,
        config: &Config,
        msg: &str,
    ) -> CargoResult<FileLock> {
        let path = self.root.join(path);

        // If we want an exclusive lock then if we fail because of NotFound it's
        // likely because an intermediate directory didn't exist, so try to
        // create the directory and then continue.
        let f = opts
            .open(&path)
            .or_else(|e| {
                if e.kind() == io::ErrorKind::NotFound && state == State::Exclusive {
                    fs::create_dir_all(path.parent().unwrap())?;
                    opts.open(&path)
                } else {
                    Err(e)
                }
            })
            .chain_err(|| format!("failed to open: {}", path.display()))?;
        match state {
            State::Exclusive => {
                acquire(config, msg, &path, &|| f.try_lock_exclusive(), &|| {
                    f.lock_exclusive()
                })?;
            }
            State::Shared => {
                acquire(config, msg, &path, &|| f.try_lock_shared(), &|| {
                    f.lock_shared()
                })?;
            }
            State::Unlocked => {}
        }
        Ok(FileLock {
            f: Some(f),
            path,
            state,
        })
    }
}

impl PartialEq<Path> for Filesystem {
    fn eq(&self, other: &Path) -> bool {
        self.root == other
    }
}

impl PartialEq<Filesystem> for Path {
    fn eq(&self, other: &Filesystem) -> bool {
        self == other.root
    }
}

/// Acquires a lock on a file in a "nice" manner.
///
/// Almost all long-running blocking actions in Cargo have a status message
/// associated with them as we're not sure how long they'll take. Whenever a
/// conflicted file lock happens, this is the case (we're not sure when the lock
/// will be released).
///
/// This function will acquire the lock on a `path`, printing out a nice message
/// to the console if we have to wait for it. It will first attempt to use `try`
/// to acquire a lock on the crate, and in the case of contention it will emit a
/// status message based on `msg` to `config`'s shell, and then use `block` to
/// block waiting to acquire a lock.
///
/// Returns an error if the lock could not be acquired or if any error other
/// than a contention error happens.
fn acquire(
    config: &Config,
    msg: &str,
    path: &Path,
    r#try: &dyn Fn() -> io::Result<()>,
    block: &dyn Fn() -> io::Result<()>,
) -> CargoResult<()> {
    // File locking on Unix is currently implemented via `flock`, which is known
    // to be broken on NFS. We could in theory just ignore errors that happen on
    // NFS, but apparently the failure mode [1] for `flock` on NFS is **blocking
    // forever**, even if the "non-blocking" flag is passed!
    //
    // As a result, we just skip all file locks entirely on NFS mounts. That
    // should avoid calling any `flock` functions at all, and it wouldn't work
    // there anyway.
    //
    // [1]: https://github.com/rust-lang/cargo/issues/2615
    if is_on_nfs_mount(path) {
        return Ok(());
    }

    match r#try() {
        Ok(()) => return Ok(()),

        // In addition to ignoring NFS which is commonly not working we also
        // just ignore locking on filesystems that look like they don't
        // implement file locking. We detect that here via the return value of
        // locking (e.g., inspecting errno).
        #[cfg(unix)]
        Err(ref e) if e.raw_os_error() == Some(libc::ENOTSUP) => return Ok(()),

        #[cfg(target_os = "linux")]
        Err(ref e) if e.raw_os_error() == Some(libc::ENOSYS) => return Ok(()),

        Err(e) => {
            if e.raw_os_error() != lock_contended_error().raw_os_error() {
                let e = failure::Error::from(e);
                let cx = format!("failed to lock file: {}", path.display());
                return Err(e.context(cx).into());
            }
        }
    }
    let msg = format!("waiting for file lock on {}", msg);
    config.shell().status_with_color("Blocking", &msg, Cyan)?;

    block().chain_err(|| format!("failed to lock file: {}", path.display()))?;
    return Ok(());

    #[cfg(all(target_os = "linux", not(target_env = "musl")))]
    fn is_on_nfs_mount(path: &Path) -> bool {
        use std::ffi::CString;
        use std::mem;
        use std::os::unix::prelude::*;

        let path = match CString::new(path.as_os_str().as_bytes()) {
            Ok(path) => path,
            Err(_) => return false,
        };

        unsafe {
            let mut buf: libc::statfs = mem::zeroed();
            let r = libc::statfs(path.as_ptr(), &mut buf);

            r == 0 && buf.f_type as u32 == libc::NFS_SUPER_MAGIC as u32
        }
    }

    #[cfg(any(not(target_os = "linux"), target_env = "musl"))]
    fn is_on_nfs_mount(_path: &Path) -> bool {
        false
    }
}
<|endoftext|>