<commit_before>// Copyright 2017 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.

use std::fs;
use std::env;
use std::path::PathBuf;
use std::process::Command;

use Mode;
use Compiler;
use builder::{Step, RunConfig, ShouldRun, Builder};
use util::{copy, exe, add_lib_path};
use compile::{self, libtest_stamp, libstd_stamp, librustc_stamp};
use native;
use channel::GitInfo;
use cache::Interned;

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
struct CleanTools {
    compiler: Compiler,
    target: Interned<String>,
    mode: Mode,
}

impl Step for CleanTools {
    type Output = ();

    fn should_run(run: ShouldRun) -> ShouldRun {
        run.never()
    }

    /// Build a tool in `src/tools`
    ///
    /// This will build the specified tool with the specified `host` compiler in
    /// `stage` into the normal cargo output directory.
    fn run(self, builder: &Builder) {
        let build = builder.build;
        let compiler = self.compiler;
        let target = self.target;
        let mode = self.mode;

        let stamp = match mode {
            Mode::Libstd => libstd_stamp(build, compiler, target),
            Mode::Libtest => libtest_stamp(build, compiler, target),
            Mode::Librustc => librustc_stamp(build, compiler, target),
            _ => panic!(),
        };
        let out_dir = build.cargo_out(compiler, Mode::Tool, target);
        build.clear_if_dirty(&out_dir, &stamp);
    }
}

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
struct ToolBuild {
    compiler: Compiler,
    target: Interned<String>,
    tool: &'static str,
    mode: Mode,
}

impl Step for ToolBuild {
    type Output = PathBuf;

    fn should_run(run: ShouldRun) -> ShouldRun {
        run.never()
    }

    /// Build a tool in `src/tools`
    ///
    /// This will build the specified tool with the specified `host` compiler in
    /// `stage` into the normal cargo output directory.
    fn run(self, builder: &Builder) -> PathBuf {
        let build = builder.build;
        let compiler = self.compiler;
        let target = self.target;
        let tool = self.tool;

        builder.ensure(CleanTools { compiler, target, mode: self.mode });
        match self.mode {
            Mode::Libstd => builder.ensure(compile::Std { compiler, target }),
            Mode::Libtest => builder.ensure(compile::Test { compiler, target }),
            Mode::Librustc => builder.ensure(compile::Rustc { compiler, target }),
            Mode::Tool => panic!("unexpected Mode::Tool for tool build")
        }

        let _folder = build.fold_output(|| format!("stage{}-{}", compiler.stage, tool));
        println!("Building stage{} tool {} ({})", compiler.stage, tool, target);

        let mut cargo = builder.cargo(compiler, Mode::Tool, target, "build");
        let dir = build.src.join("src/tools").join(tool);
        cargo.arg("--manifest-path").arg(dir.join("Cargo.toml"));

        // We don't want to build tools dynamically as they'll be running across
        // stages and such and it's just easier if they're not dynamically linked.
        cargo.env("RUSTC_NO_PREFER_DYNAMIC", "1");

        if let Some(dir) = build.openssl_install_dir(target) {
            cargo.env("OPENSSL_STATIC", "1");
            cargo.env("OPENSSL_DIR", dir);
            cargo.env("LIBZ_SYS_STATIC", "1");
        }

        cargo.env("CFG_RELEASE_CHANNEL", &build.config.channel);

        let info = GitInfo::new(&build.config, &dir);
        if let Some(sha) = info.sha() {
            cargo.env("CFG_COMMIT_HASH", sha);
        }
        if let Some(sha_short) = info.sha_short() {
            cargo.env("CFG_SHORT_COMMIT_HASH", sha_short);
        }
        if let Some(date) = info.commit_date() {
            cargo.env("CFG_COMMIT_DATE", date);
        }

        build.run(&mut cargo);
        build.cargo_out(compiler, Mode::Tool, target).join(exe(tool, &compiler.host))
    }
}

macro_rules! tool {
    ($($name:ident, $path:expr, $tool_name:expr, $mode:expr;)+) => {
        #[derive(Copy, Clone)]
        pub enum Tool {
            $(
                $name,
            )+
        }

        impl<'a> Builder<'a> {
            pub fn tool_exe(&self, tool: Tool) -> PathBuf {
                match tool {
                    $(Tool::$name =>
                        self.ensure($name {
                            compiler: self.compiler(0, self.build.build),
                            target: self.build.build,
                        }),
                    )+
                }
            }
        }

        $(
            #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
        pub struct $name {
            pub compiler: Compiler,
            pub target: Interned<String>,
        }

        impl Step for $name {
            type Output = PathBuf;

            fn should_run(run: ShouldRun) -> ShouldRun {
                run.path($path)
            }

            fn make_run(run: RunConfig) {
                run.builder.ensure($name {
                    compiler: run.builder.compiler(run.builder.top_stage, run.builder.build.build),
                    target: run.target,
                });
            }

            fn run(self, builder: &Builder) -> PathBuf {
                builder.ensure(ToolBuild {
                    compiler: self.compiler,
                    target: self.target,
                    tool: $tool_name,
                    mode: $mode,
                })
            }
        }
        )+
    }
}

tool!(
    Rustbook, "src/tools/rustbook", "rustbook", Mode::Librustc;
    ErrorIndex, "src/tools/error_index_generator", "error_index_generator", Mode::Librustc;
    UnstableBookGen, "src/tools/unstable-book-gen", "unstable-book-gen", Mode::Libstd;
    Tidy, "src/tools/tidy", "tidy", Mode::Libstd;
    Linkchecker, "src/tools/linkchecker", "linkchecker", Mode::Libstd;
    CargoTest, "src/tools/cargotest", "cargotest", Mode::Libstd;
    Compiletest, "src/tools/compiletest", "compiletest", Mode::Libtest;
    BuildManifest, "src/tools/build-manifest", "build-manifest", Mode::Librustc;
    RemoteTestClient, "src/tools/remote-test-client", "remote-test-client", Mode::Libstd;
    RustInstaller, "src/tools/rust-installer", "rust-installer", Mode::Libstd;
);

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct RemoteTestServer {
    pub compiler: Compiler,
    pub target: Interned<String>,
}

impl Step for RemoteTestServer {
    type Output = PathBuf;

    fn should_run(run: ShouldRun) -> ShouldRun {
        run.path("src/tools/remote-test-server")
    }

    fn make_run(run: RunConfig) {
        run.builder.ensure(RemoteTestServer {
            compiler: run.builder.compiler(run.builder.top_stage, run.builder.build.build),
            target: run.target,
        });
    }

    fn run(self, builder: &Builder) -> PathBuf {
        builder.ensure(ToolBuild {
            compiler: self.compiler,
            target: self.target,
            tool: "remote-test-server",
            mode: Mode::Libstd,
        })
    }
}

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct Rustdoc {
    pub target_compiler: Compiler,
}

impl Step for Rustdoc {
    type Output = PathBuf;
    const DEFAULT: bool = true;
    const ONLY_HOSTS: bool = true;

    fn should_run(run: ShouldRun) -> ShouldRun {
        run.path("src/tools/rustdoc")
    }

    fn make_run(run: RunConfig) {
        run.builder.ensure(Rustdoc {
            target_compiler: run.builder.compiler(run.builder.top_stage, run.host),
        });
    }

    fn run(self, builder: &Builder) -> PathBuf {
        let target_compiler = self.target_compiler;
        let build_compiler = if target_compiler.stage == 0 {
            builder.compiler(0, builder.build.build)
        } else {
            // Similar to `compile::Assemble`, build with the previous stage's compiler. Otherwise
            // we'd have stageN/bin/rustc and stageN/bin/rustdoc be effectively different stage
            // compilers, which isn't what we want.
            builder.compiler(target_compiler.stage - 1, builder.build.build)
        };

        let tool_rustdoc = builder.ensure(ToolBuild {
            compiler: build_compiler,
            target: target_compiler.host,
            tool: "rustdoc",
            mode: Mode::Librustc,
        });

        // don't create a stage0-sysroot/bin directory.
        if target_compiler.stage > 0 {
            let sysroot = builder.sysroot(target_compiler);
            let bindir = sysroot.join("bin");
            t!(fs::create_dir_all(&bindir));
            let bin_rustdoc = bindir.join(exe("rustdoc", &*target_compiler.host));
            let _ = fs::remove_file(&bin_rustdoc);
            copy(&tool_rustdoc, &bin_rustdoc);
            bin_rustdoc
        } else {
            tool_rustdoc
        }
    }
}

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct Cargo {
    pub compiler: Compiler,
    pub target: Interned<String>,
}

impl Step for Cargo {
    type Output = PathBuf;
    const DEFAULT: bool = true;
    const ONLY_HOSTS: bool = true;

    fn should_run(run: ShouldRun) -> ShouldRun {
        let builder = run.builder;
        run.path("src/tools/cargo").default_condition(builder.build.config.extended)
    }

    fn make_run(run: RunConfig) {
        run.builder.ensure(Cargo {
            compiler: run.builder.compiler(run.builder.top_stage, run.builder.build.build),
            target: run.target,
        });
    }

    fn run(self, builder: &Builder) -> PathBuf {
        builder.ensure(native::Openssl {
            target: self.target,
        });
        // Cargo depends on procedural macros, which requires a full host
        // compiler to be available, so we need to depend on that.
        builder.ensure(compile::Rustc {
            compiler: self.compiler,
            target: builder.build.build,
        });
        builder.ensure(ToolBuild {
            compiler: self.compiler,
            target: self.target,
            tool: "cargo",
            mode: Mode::Librustc,
        })
    }
}

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct Rls {
    pub compiler: Compiler,
    pub target: Interned<String>,
}

impl Step for Rls {
    type Output = PathBuf;
    const DEFAULT: bool = true;
    const ONLY_HOSTS: bool = true;

    fn should_run(run: ShouldRun) -> ShouldRun {
        let builder = run.builder;
        run.path("src/tools/rls").default_condition(builder.build.config.extended)
    }

    fn make_run(run: RunConfig) {
        run.builder.ensure(Rls {
            compiler: run.builder.compiler(run.builder.top_stage, run.builder.build.build),
            target: run.target,
        });
    }

    fn run(self, builder: &Builder) -> PathBuf {
        builder.ensure(native::Openssl {
            target: self.target,
        });
        // RLS depends on procedural macros, which requires a full host
        // compiler to be available, so we need to depend on that.
        builder.ensure(compile::Rustc {
            compiler: self.compiler,
            target: builder.build.build,
        });
        builder.ensure(ToolBuild {
            compiler: self.compiler,
            target: self.target,
            tool: "rls",
            mode: Mode::Librustc,
        })
    }
}

impl<'a> Builder<'a> {
    /// Get a `Command` which is ready to run `tool` in `stage` built for
    /// `host`.
    pub fn tool_cmd(&self, tool: Tool) -> Command {
        let mut cmd = Command::new(self.tool_exe(tool));
        let compiler = self.compiler(0, self.build.build);
        self.prepare_tool_cmd(compiler, &mut cmd);
        cmd
    }

    /// Prepares the `cmd` provided to be able to run the `compiler` provided.
    ///
    /// Notably this munges the dynamic library lookup path to point to the
    /// right location to run `compiler`.
    fn prepare_tool_cmd(&self, compiler: Compiler, cmd: &mut Command) {
        let host = &compiler.host;
        let mut paths: Vec<PathBuf> = vec![
            PathBuf::from(&self.sysroot_libdir(compiler, compiler.host)),
            self.cargo_out(compiler, Mode::Tool, *host).join("deps"),
        ];

        // On MSVC a tool may invoke a C compiler (e.g. compiletest in run-make
        // mode) and that C compiler may need some extra PATH modification. Do
        // so here.
        if compiler.host.contains("msvc") {
            let curpaths = env::var_os("PATH").unwrap_or_default();
            let curpaths = env::split_paths(&curpaths).collect::<Vec<_>>();
            for &(ref k, ref v) in self.cc[&compiler.host].0.env() {
                if k != "PATH" {
                    continue
                }
                for path in env::split_paths(v) {
                    if !curpaths.contains(&path) {
                        paths.push(path);
                    }
                }
            }
        }
        add_lib_path(paths, cmd);
    }
}
<commit_msg>Make the message for building rustdoc slightly nicer<commit_after>// Copyright 2017 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.

use std::fs;
use std::env;
use std::path::PathBuf;
use std::process::Command;

use Mode;
use Compiler;
use builder::{Step, RunConfig, ShouldRun, Builder};
use util::{copy, exe, add_lib_path};
use compile::{self, libtest_stamp, libstd_stamp, librustc_stamp};
use native;
use channel::GitInfo;
use cache::Interned;

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
struct CleanTools {
    compiler: Compiler,
    target: Interned<String>,
    mode: Mode,
}

impl Step for CleanTools {
    type Output = ();

    fn should_run(run: ShouldRun) -> ShouldRun {
        run.never()
    }

    /// Build a tool in `src/tools`
    ///
    /// This will build the specified tool with the specified `host` compiler in
    /// `stage` into the normal cargo output directory.
    fn run(self, builder: &Builder) {
        let build = builder.build;
        let compiler = self.compiler;
        let target = self.target;
        let mode = self.mode;

        let stamp = match mode {
            Mode::Libstd => libstd_stamp(build, compiler, target),
            Mode::Libtest => libtest_stamp(build, compiler, target),
            Mode::Librustc => librustc_stamp(build, compiler, target),
            _ => panic!(),
        };
        let out_dir = build.cargo_out(compiler, Mode::Tool, target);
        build.clear_if_dirty(&out_dir, &stamp);
    }
}

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
struct ToolBuild {
    compiler: Compiler,
    target: Interned<String>,
    tool: &'static str,
    mode: Mode,
}

impl Step for ToolBuild {
    type Output = PathBuf;

    fn should_run(run: ShouldRun) -> ShouldRun {
        run.never()
    }

    /// Build a tool in `src/tools`
    ///
    /// This will build the specified tool with the specified `host` compiler in
    /// `stage` into the normal cargo output directory.
    fn run(self, builder: &Builder) -> PathBuf {
        let build = builder.build;
        let compiler = self.compiler;
        let target = self.target;
        let tool = self.tool;

        builder.ensure(CleanTools { compiler, target, mode: self.mode });
        match self.mode {
            Mode::Libstd => builder.ensure(compile::Std { compiler, target }),
            Mode::Libtest => builder.ensure(compile::Test { compiler, target }),
            Mode::Librustc => builder.ensure(compile::Rustc { compiler, target }),
            Mode::Tool => panic!("unexpected Mode::Tool for tool build")
        }

        let _folder = build.fold_output(|| format!("stage{}-{}", compiler.stage, tool));
        println!("Building stage{} tool {} ({})", compiler.stage, tool, target);

        let mut cargo = prepare_tool_cargo(builder, compiler, target, tool);
        build.run(&mut cargo);
        build.cargo_out(compiler, Mode::Tool, target).join(exe(tool, &compiler.host))
    }
}

fn prepare_tool_cargo(
    builder: &Builder,
    compiler: Compiler,
    target: Interned<String>,
    tool: &'static str,
) -> Command {
    let build = builder.build;
    let mut cargo = builder.cargo(compiler, Mode::Tool, target, "build");
    let dir = build.src.join("src/tools").join(tool);
    cargo.arg("--manifest-path").arg(dir.join("Cargo.toml"));

    // We don't want to build tools dynamically as they'll be running across
    // stages and such and it's just easier if they're not dynamically linked.
    cargo.env("RUSTC_NO_PREFER_DYNAMIC", "1");

    if let Some(dir) = build.openssl_install_dir(target) {
        cargo.env("OPENSSL_STATIC", "1");
        cargo.env("OPENSSL_DIR", dir);
        cargo.env("LIBZ_SYS_STATIC", "1");
    }

    cargo.env("CFG_RELEASE_CHANNEL", &build.config.channel);

    let info = GitInfo::new(&build.config, &dir);
    if let Some(sha) = info.sha() {
        cargo.env("CFG_COMMIT_HASH", sha);
    }
    if let Some(sha_short) = info.sha_short() {
        cargo.env("CFG_SHORT_COMMIT_HASH", sha_short);
    }
    if let Some(date) = info.commit_date() {
        cargo.env("CFG_COMMIT_DATE", date);
    }
    cargo
}

macro_rules! tool {
    ($($name:ident, $path:expr, $tool_name:expr, $mode:expr;)+) => {
        #[derive(Copy, Clone)]
        pub enum Tool {
            $(
                $name,
            )+
        }

        impl<'a> Builder<'a> {
            pub fn tool_exe(&self, tool: Tool) -> PathBuf {
                match tool {
                    $(Tool::$name =>
                        self.ensure($name {
                            compiler: self.compiler(0, self.build.build),
                            target: self.build.build,
                        }),
                    )+
                }
            }
        }

        $(
            #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
        pub struct $name {
            pub compiler: Compiler,
            pub target: Interned<String>,
        }

        impl Step for $name {
            type Output = PathBuf;

            fn should_run(run: ShouldRun) -> ShouldRun {
                run.path($path)
            }

            fn make_run(run: RunConfig) {
                run.builder.ensure($name {
                    compiler: run.builder.compiler(run.builder.top_stage, run.builder.build.build),
                    target: run.target,
                });
            }

            fn run(self, builder: &Builder) -> PathBuf {
                builder.ensure(ToolBuild {
                    compiler: self.compiler,
                    target: self.target,
                    tool: $tool_name,
                    mode: $mode,
                })
            }
        }
        )+
    }
}

tool!(
    Rustbook, "src/tools/rustbook", "rustbook", Mode::Librustc;
    ErrorIndex, "src/tools/error_index_generator", "error_index_generator", Mode::Librustc;
    UnstableBookGen, "src/tools/unstable-book-gen", "unstable-book-gen", Mode::Libstd;
    Tidy, "src/tools/tidy", "tidy", Mode::Libstd;
    Linkchecker, "src/tools/linkchecker", "linkchecker", Mode::Libstd;
    CargoTest, "src/tools/cargotest", "cargotest", Mode::Libstd;
    Compiletest, "src/tools/compiletest", "compiletest", Mode::Libtest;
    BuildManifest, "src/tools/build-manifest", "build-manifest", Mode::Librustc;
    RemoteTestClient, "src/tools/remote-test-client", "remote-test-client", Mode::Libstd;
    RustInstaller, "src/tools/rust-installer", "rust-installer", Mode::Libstd;
);

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct RemoteTestServer {
    pub compiler: Compiler,
    pub target: Interned<String>,
}

impl Step for RemoteTestServer {
    type Output = PathBuf;

    fn should_run(run: ShouldRun) -> ShouldRun {
        run.path("src/tools/remote-test-server")
    }

    fn make_run(run: RunConfig) {
        run.builder.ensure(RemoteTestServer {
            compiler: run.builder.compiler(run.builder.top_stage, run.builder.build.build),
            target: run.target,
        });
    }

    fn run(self, builder: &Builder) -> PathBuf {
        builder.ensure(ToolBuild {
            compiler: self.compiler,
            target: self.target,
            tool: "remote-test-server",
            mode: Mode::Libstd,
        })
    }
}

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct Rustdoc {
    pub target_compiler: Compiler,
}

impl Step for Rustdoc {
    type Output = PathBuf;
    const DEFAULT: bool = true;
    const ONLY_HOSTS: bool = true;

    fn should_run(run: ShouldRun) -> ShouldRun {
        run.path("src/tools/rustdoc")
    }

    fn make_run(run: RunConfig) {
        run.builder.ensure(Rustdoc {
            target_compiler: run.builder.compiler(run.builder.top_stage, run.host),
        });
    }

    fn run(self, builder: &Builder) -> PathBuf {
        let build = builder.build;
        let target_compiler = self.target_compiler;
        let target = target_compiler.host;
        let build_compiler = if target_compiler.stage == 0 {
            builder.compiler(0, builder.build.build)
        } else {
            // Similar to `compile::Assemble`, build with the previous stage's compiler. Otherwise
            // we'd have stageN/bin/rustc and stageN/bin/rustdoc be effectively different stage
            // compilers, which isn't what we want.
            builder.compiler(target_compiler.stage - 1, builder.build.build)
        };

        builder.ensure(CleanTools { compiler: build_compiler, target, mode: Mode::Librustc });
        builder.ensure(compile::Rustc { compiler: build_compiler, target });

        let _folder = build.fold_output(|| format!("stage{}-rustdoc", target_compiler.stage));
        println!("Building rustdoc for stage{} ({})", target_compiler.stage, target_compiler.host);

        let mut cargo = prepare_tool_cargo(builder, build_compiler, target, "rustdoc");
        build.run(&mut cargo);
        let tool_rustdoc = build.cargo_out(build_compiler, Mode::Tool, target)
            .join(exe("rustdoc", &target_compiler.host));

        // don't create a stage0-sysroot/bin directory.
        if target_compiler.stage > 0 {
            let sysroot = builder.sysroot(target_compiler);
            let bindir = sysroot.join("bin");
            t!(fs::create_dir_all(&bindir));
            let bin_rustdoc = bindir.join(exe("rustdoc", &*target_compiler.host));
            let _ = fs::remove_file(&bin_rustdoc);
            copy(&tool_rustdoc, &bin_rustdoc);
            bin_rustdoc
        } else {
            tool_rustdoc
        }
    }
}

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct Cargo {
    pub compiler: Compiler,
    pub target: Interned<String>,
}

impl Step for Cargo {
    type Output = PathBuf;
    const DEFAULT: bool = true;
    const ONLY_HOSTS: bool = true;

    fn should_run(run: ShouldRun) -> ShouldRun {
        let builder = run.builder;
        run.path("src/tools/cargo").default_condition(builder.build.config.extended)
    }

    fn make_run(run: RunConfig) {
        run.builder.ensure(Cargo {
            compiler: run.builder.compiler(run.builder.top_stage, run.builder.build.build),
            target: run.target,
        });
    }

    fn run(self, builder: &Builder) -> PathBuf {
        builder.ensure(native::Openssl {
            target: self.target,
        });
        // Cargo depends on procedural macros, which requires a full host
        // compiler to be available, so we need to depend on that.
        builder.ensure(compile::Rustc {
            compiler: self.compiler,
            target: builder.build.build,
        });
        builder.ensure(ToolBuild {
            compiler: self.compiler,
            target: self.target,
            tool: "cargo",
            mode: Mode::Librustc,
        })
    }
}

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct Rls {
    pub compiler: Compiler,
    pub target: Interned<String>,
}

impl Step for Rls {
    type Output = PathBuf;
    const DEFAULT: bool = true;
    const ONLY_HOSTS: bool = true;

    fn should_run(run: ShouldRun) -> ShouldRun {
        let builder = run.builder;
        run.path("src/tools/rls").default_condition(builder.build.config.extended)
    }

    fn make_run(run: RunConfig) {
        run.builder.ensure(Rls {
            compiler: run.builder.compiler(run.builder.top_stage, run.builder.build.build),
            target: run.target,
        });
    }

    fn run(self, builder: &Builder) -> PathBuf {
        builder.ensure(native::Openssl {
            target: self.target,
        });
        // RLS depends on procedural macros, which requires a full host
        // compiler to be available, so we need to depend on that.
        builder.ensure(compile::Rustc {
            compiler: self.compiler,
            target: builder.build.build,
        });
        builder.ensure(ToolBuild {
            compiler: self.compiler,
            target: self.target,
            tool: "rls",
            mode: Mode::Librustc,
        })
    }
}

impl<'a> Builder<'a> {
    /// Get a `Command` which is ready to run `tool` in `stage` built for
    /// `host`.
    pub fn tool_cmd(&self, tool: Tool) -> Command {
        let mut cmd = Command::new(self.tool_exe(tool));
        let compiler = self.compiler(0, self.build.build);
        self.prepare_tool_cmd(compiler, &mut cmd);
        cmd
    }

    /// Prepares the `cmd` provided to be able to run the `compiler` provided.
    ///
    /// Notably this munges the dynamic library lookup path to point to the
    /// right location to run `compiler`.
    fn prepare_tool_cmd(&self, compiler: Compiler, cmd: &mut Command) {
        let host = &compiler.host;
        let mut paths: Vec<PathBuf> = vec![
            PathBuf::from(&self.sysroot_libdir(compiler, compiler.host)),
            self.cargo_out(compiler, Mode::Tool, *host).join("deps"),
        ];

        // On MSVC a tool may invoke a C compiler (e.g. compiletest in run-make
        // mode) and that C compiler may need some extra PATH modification. Do
        // so here.
        if compiler.host.contains("msvc") {
            let curpaths = env::var_os("PATH").unwrap_or_default();
            let curpaths = env::split_paths(&curpaths).collect::<Vec<_>>();
            for &(ref k, ref v) in self.cc[&compiler.host].0.env() {
                if k != "PATH" {
                    continue
                }
                for path in env::split_paths(v) {
                    if !curpaths.contains(&path) {
                        paths.push(path);
                    }
                }
            }
        }
        add_lib_path(paths, cmd);
    }
}
<|endoftext|>


<commit_before>use std::io::stdio::{stdout, stderr};
use std::io::timer::sleep;
use std::os::getenv;
use std::default::Default;

use libc::pid_t;
use collections::treemap::TreeMap;
use argparse::{ArgumentParser, StoreFalse};

use super::super::env::{Environ, Container};
use super::super::options::env_options;
use super::super::run::internal_run;
use super::super::config::{Shell, Command};
use super::super::build::ensure_container;
use super::super::monitor::Monitor;
use super::super::utils::run::{write_sentinel, check_command_workdir};
use super::super::utils::run::{is_writeable, print_banner};
use super::super::linux::RunOptions;

pub fn run_shell_command(env: &mut Environ, cmdname: &String,
    args: Vec<String>)
    -> Result<int, String>
{
    let has_arguments;
    let description;
    let mut command_workdir;
    {
        let command = env.config.commands.find(cmdname).unwrap();
        has_arguments = command.accepts_arguments;
        description = command.description.clone().unwrap_or("".to_string());
        command_workdir = command.work_dir.is_some();
    }
    let mut cmdargs;
    if has_arguments {
        //  All options forwarded to command (including --help and others)
        cmdargs = args;
        cmdargs.shift();  // Zeroth arg is a command
    } else {
        //  We can provide useful help in this case
        cmdargs = Vec::new();
        let mut ap = ArgumentParser::new();
        if description.len() > 0 {
            ap.set_description(description.as_slice());
        }
        if command_workdir {
            ap.refer(&mut command_workdir)
                .add_option(["--override-work-dir"], box StoreFalse,
                    "Do not obey `work-dir` parameter in command definition.
                     Use current working directory instead.");
        }
        env_options(env, &mut ap);
        match ap.parse(args, &mut stdout(), &mut stderr()) {
            Ok(()) => {}
            Err(0) => return Ok(0),
            Err(_) => return Ok(122),
        }
    }
    let command = env.config.commands.find(cmdname).unwrap();
    let cname = env.container.as_ref().or(command.container.as_ref()).unwrap();
    let mut container = try!(env.get_container(cname));
    try!(ensure_container(env, &mut container));

    let _sent = try!(write_sentinel(env, &mut container, command.write_mode));
    let work_dir = try!(check_command_workdir(env, command));

    if command.banner_delay == 0 {
        print_banner(&command.banner);
    }
    let mut monitor = Monitor::new(true);
    let pid = try!(exec_shell_command_args(env, &work_dir,
        command, &container, cmdargs));
    monitor.add("child".to_string(), pid);
    if command.banner_delay > 0 {
        sleep(command.banner_delay as u64 * 1000);
        print_banner(&command.banner);
    }
    monitor.wait_all();
    let result = monitor.get_status();
    if result == 0 {
        print_banner(&command.epilog);
    }
    return Ok(result);
}

pub fn exec_shell_command(env: &Environ, work_dir: &Path,
    command: &Command, container: &Container)
    -> Result<pid_t, String>
{
    return exec_shell_command_args(env, work_dir,
        command, container, Vec::new());
}

pub fn exec_shell_command_args(env: &Environ, work_dir: &Path,
    command: &Command, container: &Container, cmdargs: Vec<String>)
    -> Result<pid_t, String>
{
    let mut runenv = TreeMap::new();
    for (k, v) in command.environ.iter() {
        runenv.insert(k.clone(), v.clone());
    }
    for k in command.inherit_environ.iter() {
        match getenv(k.as_slice()) {
            Some(ref val) => { runenv.insert(k.clone(), val.clone()); }
            None => {}
        }
    }
    let mut argprefix: Vec<String> = Vec::new();
    argprefix.extend(container.shell.clone().move_iter());
    match command.execute {
        Shell(ref x) => argprefix.push(x.clone()),
        _ => unreachable!(),
    }
    let cmd = argprefix.shift().unwrap();
    let ropts = RunOptions {
        pid1mode: command.pid1mode,
        writeable: is_writeable(command.write_mode),
        .. Default::default()
        };
    return internal_run(env, container, ropts, command.resolv_conf,
        work_dir, cmd, (argprefix + cmdargs), runenv);
}
<commit_msg>Fix processing mounts for shell commands<commit_after>use std::io::stdio::{stdout, stderr};
use std::io::timer::sleep;
use std::os::getenv;
use std::default::Default;

use libc::pid_t;
use collections::treemap::TreeMap;
use argparse::{ArgumentParser, StoreFalse};

use super::super::env::{Environ, Container};
use super::super::options::env_options;
use super::super::run::internal_run;
use super::super::config::{Shell, Command};
use super::super::build::ensure_container;
use super::super::monitor::Monitor;
use super::super::utils::run::{write_sentinel, check_command_workdir};
use super::super::utils::run::{is_writeable, print_banner, container_volumes};
use super::super::linux::RunOptions;

pub fn run_shell_command(env: &mut Environ, cmdname: &String,
    args: Vec<String>)
    -> Result<int, String>
{
    let has_arguments;
    let description;
    let mut command_workdir;
    {
        let command = env.config.commands.find(cmdname).unwrap();
        has_arguments = command.accepts_arguments;
        description = command.description.clone().unwrap_or("".to_string());
        command_workdir = command.work_dir.is_some();
    }
    let mut cmdargs;
    if has_arguments {
        //  All options forwarded to command (including --help and others)
        cmdargs = args;
        cmdargs.shift();  // Zeroth arg is a command
    } else {
        //  We can provide useful help in this case
        cmdargs = Vec::new();
        let mut ap = ArgumentParser::new();
        if description.len() > 0 {
            ap.set_description(description.as_slice());
        }
        if command_workdir {
            ap.refer(&mut command_workdir)
                .add_option(["--override-work-dir"], box StoreFalse,
                    "Do not obey `work-dir` parameter in command definition.
                     Use current working directory instead.");
        }
        env_options(env, &mut ap);
        match ap.parse(args, &mut stdout(), &mut stderr()) {
            Ok(()) => {}
            Err(0) => return Ok(0),
            Err(_) => return Ok(122),
        }
    }
    let command = env.config.commands.find(cmdname).unwrap();
    let cname = env.container.as_ref().or(command.container.as_ref()).unwrap();
    let mut container = try!(env.get_container(cname));
    try!(ensure_container(env, &mut container));

    let _sent = try!(write_sentinel(env, &mut container, command.write_mode));
    let work_dir = try!(check_command_workdir(env, command));

    if command.banner_delay == 0 {
        print_banner(&command.banner);
    }
    let mut monitor = Monitor::new(true);
    let pid = try!(exec_shell_command_args(env, &work_dir,
        command, &container, cmdargs));
    monitor.add("child".to_string(), pid);
    if command.banner_delay > 0 {
        sleep(command.banner_delay as u64 * 1000);
        print_banner(&command.banner);
    }
    monitor.wait_all();
    let result = monitor.get_status();
    if result == 0 {
        print_banner(&command.epilog);
    }
    return Ok(result);
}

pub fn exec_shell_command(env: &Environ, work_dir: &Path,
    command: &Command, container: &Container)
    -> Result<pid_t, String>
{
    return exec_shell_command_args(env, work_dir,
        command, container, Vec::new());
}

pub fn exec_shell_command_args(env: &Environ, work_dir: &Path,
    command: &Command, container: &Container, cmdargs: Vec<String>)
    -> Result<pid_t, String>
{
    let mut runenv = TreeMap::new();
    for (k, v) in command.environ.iter() {
        runenv.insert(k.clone(), v.clone());
    }
    for k in command.inherit_environ.iter() {
        match getenv(k.as_slice()) {
            Some(ref val) => { runenv.insert(k.clone(), val.clone()); }
            None => {}
        }
    }
    let mut argprefix: Vec<String> = Vec::new();
    argprefix.extend(container.shell.clone().move_iter());
    match command.execute {
        Shell(ref x) => argprefix.push(x.clone()),
        _ => unreachable!(),
    }
    let cmd = argprefix.shift().unwrap();
    let ropts = RunOptions {
        pid1mode: command.pid1mode,
        writeable: is_writeable(command.write_mode),
        mounts: container_volumes(env, container),
        .. Default::default()
        };
    return internal_run(env, container, ropts, command.resolv_conf,
        work_dir, cmd, (argprefix + cmdargs), runenv);
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// 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
//
//    https://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 crate::ast_discoverer::Discoveries;
use crate::CppCodegenOptions;
use crate::{
    cxxbridge::CxxBridge, Error as EngineError, GeneratedCpp, IncludeCppEngine,
    RebuildDependencyRecorder,
};
use autocxx_parser::directives::SUBCLASS;
use autocxx_parser::{RustPath, Subclass, SubclassAttrs};
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use std::{collections::HashSet, fmt::Display, io::Read, path::PathBuf};
use std::{panic::UnwindSafe, path::Path, rc::Rc};
use syn::{token::Brace, Item, ItemMod, LitStr};

/// Errors which may occur when parsing a Rust source file to discover
/// and interpret include_cxx macros.
#[derive(Debug)]
pub enum ParseError {
    /// Unable to open the source file
    FileOpen(std::io::Error),
    /// The .rs file couldn't be read.
    FileRead(std::io::Error),
    /// The .rs file couldn't be parsed.
    Syntax(syn::Error),
    /// The include CPP macro could not be expanded into
    /// Rust bindings to C++, because of some problem during the conversion
    /// process. This could be anything from a C++ parsing error to some
    /// C++ feature that autocxx can't yet handle and isn't able to skip
    /// over. It could also cover errors in your syntax of the `include_cpp`
    /// macro or the directives inside.
    AutocxxCodegenError(EngineError),
    /// There are two or more `include_cpp` macros with the same
    /// mod name.
    ConflictingModNames,
    ZeroModsForDynamicDiscovery,
    MultipleModsForDynamicDiscovery,
    DiscoveredRustItemsWhenNotInAutoDiscover,
}

impl Display for ParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ParseError::FileOpen(err) => write!(f, "Unable to open file: {}", err)?,
            ParseError::FileRead(err) => write!(f, "Unable to read file: {}", err)?,
            ParseError::Syntax(err) => write!(f, "Syntax error parsing Rust file: {}", err)?,
            ParseError::AutocxxCodegenError(err) =>
                write!(f, "Unable to parse include_cpp! macro: {}", err)?,
            ParseError::ConflictingModNames =>
                write!(f, "There are two or more include_cpp! macros with the same output mod name. Use name!")?,
            ParseError::ZeroModsForDynamicDiscovery =>
                write!(f, "This file contains extra information to append to an include_cpp! but no such include_cpp! was found in this file.")?,
            ParseError::MultipleModsForDynamicDiscovery =>
                write!(f, "This file contains extra information to append to an include_cpp! but multiple such include_cpp! declarations were found in this file.")?,
            ParseError::DiscoveredRustItemsWhenNotInAutoDiscover =>
                write!(f, "This file contains extra information to append to an \"extern Rust\" but auto-discover was switched off.")?,
        }
        Ok(())
    }
}

/// Parse a Rust file, and spot any include_cpp macros within it.
pub fn parse_file<P1: AsRef<Path>>(
    rs_file: P1,
    auto_allowlist: bool,
) -> Result<ParsedFile, ParseError> {
    let mut source = String::new();
    let mut file = std::fs::File::open(rs_file).map_err(ParseError::FileOpen)?;
    file.read_to_string(&mut source)
        .map_err(ParseError::FileRead)?;
    proc_macro2::fallback::force();
    let source = syn::parse_file(&source).map_err(ParseError::Syntax)?;
    parse_file_contents(source, auto_allowlist)
}

fn parse_file_contents(source: syn::File, auto_allowlist: bool) -> Result<ParsedFile, ParseError> {
    #[derive(Default)]
    struct State {
        auto_allowlist: bool,
        results: Vec<Segment>,
        extra_superclasses: Vec<Subclass>,
        discoveries: Discoveries,
    }
    impl State {
        fn parse_item(&mut self, item: Item, mod_path: Option<RustPath>) -> Result<(), ParseError> {
            let result = match item {
                Item::Macro(mac)
                    if mac
                        .mac
                        .path
                        .segments
                        .last()
                        .map(|s| s.ident == "include_cpp")
                        .unwrap_or(false) =>
                {
                    Segment::Autocxx(
                        crate::IncludeCppEngine::new_from_syn(mac.mac.clone())
                            .map_err(ParseError::AutocxxCodegenError)?,
                    )
                }
                Item::Mod(itm)
                    if itm
                        .attrs
                        .iter()
                        .any(|attr| attr.path.to_token_stream().to_string() == "cxx :: bridge") =>
                {
                    Segment::Cxx(CxxBridge::from(itm))
                }
                Item::Mod(itm) => {
                    if let Some((brace, items)) = itm.content {
                        let mut mod_state = State {
                            auto_allowlist: self.auto_allowlist,
                            ..Default::default()
                        };
                        let mod_path = match &mod_path {
                            None => RustPath::new_from_ident(itm.ident.clone()),
                            Some(mod_path) => mod_path.append(itm.ident.clone()),
                        };
                        for item in items {
                            mod_state.parse_item(item, Some(mod_path.clone()))?
                        }
                        self.extra_superclasses.extend(mod_state.extra_superclasses);
                        self.discoveries.extend(mod_state.discoveries);
                        Segment::Mod(
                            mod_state.results,
                            (
                                brace,
                                ItemMod {
                                    content: None,
                                    ..itm
                                },
                            ),
                        )
                    } else {
                        Segment::Other(Item::Mod(itm))
                    }
                }
                Item::Struct(ref its) if self.auto_allowlist => {
                    let attrs = &its.attrs;
                    let is_superclass_attr = attrs.iter().find(|attr| {
                        attr.path
                            .segments
                            .last()
                            .map(|seg| seg.ident == "is_subclass" || seg.ident == SUBCLASS)
                            .unwrap_or(false)
                    });
                    if let Some(is_superclass_attr) = is_superclass_attr {
                        if !is_superclass_attr.tokens.is_empty() {
                            let subclass = its.ident.clone();
                            let args: SubclassAttrs = is_superclass_attr
                                .parse_args()
                                .map_err(ParseError::Syntax)?;
                            if let Some(superclass) = args.superclass {
                                self.extra_superclasses.push(Subclass {
                                    superclass,
                                    subclass,
                                })
                            }
                        }
                    }
                    self.discoveries.search_item(&item, mod_path);
                    Segment::Other(item)
                }
                _ => {
                    self.discoveries.search_item(&item, mod_path);
                    Segment::Other(item)
                }
            };
            self.results.push(result);
            Ok(())
        }
    }
    let mut state = State {
        auto_allowlist,
        ..Default::default()
    };
    for item in source.items {
        state.parse_item(item, None)?
    }
    let State {
        auto_allowlist,
        mut results,
        mut extra_superclasses,
        mut discoveries,
    } = state;
    if !auto_allowlist
        && (!discoveries.extern_rust_types.is_empty() || !discoveries.extern_rust_funs.is_empty())
    {
        return Err(ParseError::DiscoveredRustItemsWhenNotInAutoDiscover);
    }
    if !extra_superclasses.is_empty() || (auto_allowlist && !discoveries.is_empty()) {
        let mut autocxx_seg_iterator = results.iter_mut().filter_map(|seg| match seg {
            Segment::Autocxx(engine) => Some(engine),
            _ => None,
        });
        let our_seg = autocxx_seg_iterator.next();
        match our_seg {
            None => return Err(ParseError::ZeroModsForDynamicDiscovery),
            Some(engine) => {
                engine
                    .config_mut()
                    .subclasses
                    .append(&mut extra_superclasses);
                if auto_allowlist {
                    for cpp in discoveries.cpp_list {
                        engine
                            .config_mut()
                            .allowlist
                            .push(LitStr::new(&cpp, Span::call_site()))
                            .map_err(ParseError::Syntax)?;
                    }
                }
                engine
                    .config_mut()
                    .extern_rust_funs
                    .append(&mut discoveries.extern_rust_funs);
                engine
                    .config_mut()
                    .rust_types
                    .append(&mut discoveries.extern_rust_types);
            }
        }
        if autocxx_seg_iterator.next().is_some() {
            return Err(ParseError::MultipleModsForDynamicDiscovery);
        }
    }
    let autocxx_seg_iterator = results.iter_mut().filter_map(|seg| match seg {
        Segment::Autocxx(engine) => Some(engine),
        _ => None,
    });
    for seg in autocxx_seg_iterator {
        seg.config
            .confirm_complete(auto_allowlist)
            .map_err(ParseError::Syntax)?;
    }
    Ok(ParsedFile(results))
}

/// A Rust file parsed by autocxx. May contain zero or more autocxx 'engines',
/// i.e. the `IncludeCpp` class, corresponding to zero or more include_cpp
/// macros within this file. Also contains `syn::Item` structures for all
/// the rest of the Rust code, such that it can be reconstituted if necessary.
pub struct ParsedFile(Vec<Segment>);

#[allow(clippy::large_enum_variant)]
enum Segment {
    Autocxx(IncludeCppEngine),
    Cxx(CxxBridge),
    Mod(Vec<Segment>, (Brace, ItemMod)),
    Other(Item),
}

pub trait CppBuildable {
    fn generate_h_and_cxx(
        &self,
        cpp_codegen_options: &CppCodegenOptions,
    ) -> Result<GeneratedCpp, cxx_gen::Error>;
}

impl ParsedFile {
    /// Get all the autocxxes in this parsed file.
    pub fn get_rs_buildables(&self) -> impl Iterator<Item = &IncludeCppEngine> {
        fn do_get_rs_buildables(
            segments: &Vec<Segment>,
        ) -> impl Iterator<Item = &IncludeCppEngine> {
            segments
                .iter()
                .flat_map(|s| -> Box<dyn Iterator<Item = &IncludeCppEngine>> {
                    match s {
                        Segment::Autocxx(includecpp) => Box::new(std::iter::once(includecpp)),
                        Segment::Mod(segments, _) => Box::new(do_get_rs_buildables(segments)),
                        _ => Box::new(std::iter::empty()),
                    }
                })
        }

        do_get_rs_buildables(&self.0)
    }

    /// Get all items which can result in C++ code
    pub fn get_cpp_buildables(&self) -> impl Iterator<Item = &dyn CppBuildable> {
        fn do_get_cpp_buildables(
            segments: &Vec<Segment>,
        ) -> impl Iterator<Item = &dyn CppBuildable> {
            segments
                .iter()
                .flat_map(|s| -> Box<dyn Iterator<Item = &dyn CppBuildable>> {
                    match s {
                        Segment::Autocxx(includecpp) => {
                            Box::new(std::iter::once(includecpp as &dyn CppBuildable))
                        }
                        Segment::Cxx(cxxbridge) => {
                            Box::new(std::iter::once(cxxbridge as &dyn CppBuildable))
                        }
                        Segment::Mod(segments, _) => Box::new(do_get_cpp_buildables(segments)),
                        _ => Box::new(std::iter::empty()),
                    }
                })
        }

        do_get_cpp_buildables(&self.0)
    }

    fn get_autocxxes_mut(&mut self) -> impl Iterator<Item = &mut IncludeCppEngine> {
        fn do_get_autocxxes_mut(
            segments: &mut Vec<Segment>,
        ) -> impl Iterator<Item = &mut IncludeCppEngine> {
            segments
                .iter_mut()
                .flat_map(|s| -> Box<dyn Iterator<Item = &mut IncludeCppEngine>> {
                    match s {
                        Segment::Autocxx(includecpp) => Box::new(std::iter::once(includecpp)),
                        Segment::Mod(segments, _) => Box::new(do_get_autocxxes_mut(segments)),
                        _ => Box::new(std::iter::empty()),
                    }
                })
        }

        do_get_autocxxes_mut(&mut self.0)
    }

    pub fn include_dirs(&self) -> impl Iterator<Item = &PathBuf> {
        fn do_get_include_dirs(segments: &Vec<Segment>) -> impl Iterator<Item = &PathBuf> {
            segments
                .iter()
                .flat_map(|s| -> Box<dyn Iterator<Item = &PathBuf>> {
                    match s {
                        Segment::Autocxx(includecpp) => Box::new(includecpp.include_dirs()),
                        Segment::Mod(segments, _) => Box::new(do_get_include_dirs(segments)),
                        _ => Box::new(std::iter::empty()),
                    }
                })
        }

        do_get_include_dirs(&self.0)
    }

    pub fn resolve_all(
        &mut self,
        autocxx_inc: Vec<PathBuf>,
        extra_clang_args: &[&str],
        dep_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
        cpp_codegen_options: &CppCodegenOptions,
    ) -> Result<(), ParseError> {
        let mut mods_found = HashSet::new();
        let inner_dep_recorder: Option<Rc<dyn RebuildDependencyRecorder>> =
            dep_recorder.map(Rc::from);
        for include_cpp in self.get_autocxxes_mut() {
            #[allow(clippy::manual_map)] // because of dyn shenanigans
            let dep_recorder: Option<Box<dyn RebuildDependencyRecorder>> = match &inner_dep_recorder
            {
                None => None,
                Some(inner_dep_recorder) => Some(Box::new(CompositeDepRecorder::new(
                    inner_dep_recorder.clone(),
                ))),
            };
            if !mods_found.insert(include_cpp.get_mod_name()) {
                return Err(ParseError::ConflictingModNames);
            }
            include_cpp
                .generate(
                    autocxx_inc.clone(),
                    extra_clang_args,
                    dep_recorder,
                    cpp_codegen_options,
                )
                .map_err(ParseError::AutocxxCodegenError)?
        }
        Ok(())
    }
}

impl ToTokens for ParsedFile {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        for seg in &self.0 {
            seg.to_tokens(tokens)
        }
    }
}

impl ToTokens for Segment {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            Segment::Other(item) => item.to_tokens(tokens),
            Segment::Autocxx(autocxx) => {
                let these_tokens = autocxx.generate_rs();
                tokens.extend(these_tokens);
            }
            Segment::Cxx(cxxbridge) => cxxbridge.to_tokens(tokens),
            Segment::Mod(segments, (brace, itemmod)) => {
                let mod_items = segments
                    .iter()
                    .map(|segment| syn::parse2::<Item>(segment.to_token_stream()).unwrap())
                    .collect();
                let itemmod = ItemMod {
                    content: Some((*brace, mod_items)),
                    ..itemmod.clone()
                };
                Item::Mod(itemmod).to_tokens(tokens)
            }
        }
    }
}

/// Shenanigans required to share the same RebuildDependencyRecorder
/// with all of the include_cpp instances in this one file.
#[derive(Debug, Clone)]
struct CompositeDepRecorder(Rc<dyn RebuildDependencyRecorder>);

impl CompositeDepRecorder {
    fn new(inner: Rc<dyn RebuildDependencyRecorder>) -> Self {
        CompositeDepRecorder(inner)
    }
}

impl UnwindSafe for CompositeDepRecorder {}

impl RebuildDependencyRecorder for CompositeDepRecorder {
    fn record_header_file_dependency(&self, filename: &str) {
        self.0.record_header_file_dependency(filename);
    }
}
<commit_msg>clippy fixes<commit_after>// Copyright 2020 Google LLC
//
// 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
//
//    https://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 crate::ast_discoverer::Discoveries;
use crate::CppCodegenOptions;
use crate::{
    cxxbridge::CxxBridge, Error as EngineError, GeneratedCpp, IncludeCppEngine,
    RebuildDependencyRecorder,
};
use autocxx_parser::directives::SUBCLASS;
use autocxx_parser::{RustPath, Subclass, SubclassAttrs};
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use std::{collections::HashSet, fmt::Display, io::Read, path::PathBuf};
use std::{panic::UnwindSafe, path::Path, rc::Rc};
use syn::{token::Brace, Item, ItemMod, LitStr};

/// Errors which may occur when parsing a Rust source file to discover
/// and interpret include_cxx macros.
#[derive(Debug)]
pub enum ParseError {
    /// Unable to open the source file
    FileOpen(std::io::Error),
    /// The .rs file couldn't be read.
    FileRead(std::io::Error),
    /// The .rs file couldn't be parsed.
    Syntax(syn::Error),
    /// The include CPP macro could not be expanded into
    /// Rust bindings to C++, because of some problem during the conversion
    /// process. This could be anything from a C++ parsing error to some
    /// C++ feature that autocxx can't yet handle and isn't able to skip
    /// over. It could also cover errors in your syntax of the `include_cpp`
    /// macro or the directives inside.
    AutocxxCodegenError(EngineError),
    /// There are two or more `include_cpp` macros with the same
    /// mod name.
    ConflictingModNames,
    ZeroModsForDynamicDiscovery,
    MultipleModsForDynamicDiscovery,
    DiscoveredRustItemsWhenNotInAutoDiscover,
}

impl Display for ParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ParseError::FileOpen(err) => write!(f, "Unable to open file: {}", err)?,
            ParseError::FileRead(err) => write!(f, "Unable to read file: {}", err)?,
            ParseError::Syntax(err) => write!(f, "Syntax error parsing Rust file: {}", err)?,
            ParseError::AutocxxCodegenError(err) =>
                write!(f, "Unable to parse include_cpp! macro: {}", err)?,
            ParseError::ConflictingModNames =>
                write!(f, "There are two or more include_cpp! macros with the same output mod name. Use name!")?,
            ParseError::ZeroModsForDynamicDiscovery =>
                write!(f, "This file contains extra information to append to an include_cpp! but no such include_cpp! was found in this file.")?,
            ParseError::MultipleModsForDynamicDiscovery =>
                write!(f, "This file contains extra information to append to an include_cpp! but multiple such include_cpp! declarations were found in this file.")?,
            ParseError::DiscoveredRustItemsWhenNotInAutoDiscover =>
                write!(f, "This file contains extra information to append to an \"extern Rust\" but auto-discover was switched off.")?,
        }
        Ok(())
    }
}

/// Parse a Rust file, and spot any include_cpp macros within it.
pub fn parse_file<P1: AsRef<Path>>(
    rs_file: P1,
    auto_allowlist: bool,
) -> Result<ParsedFile, ParseError> {
    let mut source = String::new();
    let mut file = std::fs::File::open(rs_file).map_err(ParseError::FileOpen)?;
    file.read_to_string(&mut source)
        .map_err(ParseError::FileRead)?;
    proc_macro2::fallback::force();
    let source = syn::parse_file(&source).map_err(ParseError::Syntax)?;
    parse_file_contents(source, auto_allowlist)
}

fn parse_file_contents(source: syn::File, auto_allowlist: bool) -> Result<ParsedFile, ParseError> {
    #[derive(Default)]
    struct State {
        auto_allowlist: bool,
        results: Vec<Segment>,
        extra_superclasses: Vec<Subclass>,
        discoveries: Discoveries,
    }
    impl State {
        fn parse_item(&mut self, item: Item, mod_path: Option<RustPath>) -> Result<(), ParseError> {
            let result = match item {
                Item::Macro(mac)
                    if mac
                        .mac
                        .path
                        .segments
                        .last()
                        .map(|s| s.ident == "include_cpp")
                        .unwrap_or(false) =>
                {
                    Segment::Autocxx(
                        crate::IncludeCppEngine::new_from_syn(mac.mac)
                            .map_err(ParseError::AutocxxCodegenError)?,
                    )
                }
                Item::Mod(itm)
                    if itm
                        .attrs
                        .iter()
                        .any(|attr| attr.path.to_token_stream().to_string() == "cxx :: bridge") =>
                {
                    Segment::Cxx(CxxBridge::from(itm))
                }
                Item::Mod(itm) => {
                    if let Some((brace, items)) = itm.content {
                        let mut mod_state = State {
                            auto_allowlist: self.auto_allowlist,
                            ..Default::default()
                        };
                        let mod_path = match &mod_path {
                            None => RustPath::new_from_ident(itm.ident.clone()),
                            Some(mod_path) => mod_path.append(itm.ident.clone()),
                        };
                        for item in items {
                            mod_state.parse_item(item, Some(mod_path.clone()))?
                        }
                        self.extra_superclasses.extend(mod_state.extra_superclasses);
                        self.discoveries.extend(mod_state.discoveries);
                        Segment::Mod(
                            mod_state.results,
                            (
                                brace,
                                ItemMod {
                                    content: None,
                                    ..itm
                                },
                            ),
                        )
                    } else {
                        Segment::Other(Item::Mod(itm))
                    }
                }
                Item::Struct(ref its) if self.auto_allowlist => {
                    let attrs = &its.attrs;
                    let is_superclass_attr = attrs.iter().find(|attr| {
                        attr.path
                            .segments
                            .last()
                            .map(|seg| seg.ident == "is_subclass" || seg.ident == SUBCLASS)
                            .unwrap_or(false)
                    });
                    if let Some(is_superclass_attr) = is_superclass_attr {
                        if !is_superclass_attr.tokens.is_empty() {
                            let subclass = its.ident.clone();
                            let args: SubclassAttrs = is_superclass_attr
                                .parse_args()
                                .map_err(ParseError::Syntax)?;
                            if let Some(superclass) = args.superclass {
                                self.extra_superclasses.push(Subclass {
                                    superclass,
                                    subclass,
                                })
                            }
                        }
                    }
                    self.discoveries.search_item(&item, mod_path);
                    Segment::Other(item)
                }
                _ => {
                    self.discoveries.search_item(&item, mod_path);
                    Segment::Other(item)
                }
            };
            self.results.push(result);
            Ok(())
        }
    }
    let mut state = State {
        auto_allowlist,
        ..Default::default()
    };
    for item in source.items {
        state.parse_item(item, None)?
    }
    let State {
        auto_allowlist,
        mut results,
        mut extra_superclasses,
        mut discoveries,
    } = state;
    if !auto_allowlist
        && (!discoveries.extern_rust_types.is_empty() || !discoveries.extern_rust_funs.is_empty())
    {
        return Err(ParseError::DiscoveredRustItemsWhenNotInAutoDiscover);
    }
    if !extra_superclasses.is_empty() || (auto_allowlist && !discoveries.is_empty()) {
        let mut autocxx_seg_iterator = results.iter_mut().filter_map(|seg| match seg {
            Segment::Autocxx(engine) => Some(engine),
            _ => None,
        });
        let our_seg = autocxx_seg_iterator.next();
        match our_seg {
            None => return Err(ParseError::ZeroModsForDynamicDiscovery),
            Some(engine) => {
                engine
                    .config_mut()
                    .subclasses
                    .append(&mut extra_superclasses);
                if auto_allowlist {
                    for cpp in discoveries.cpp_list {
                        engine
                            .config_mut()
                            .allowlist
                            .push(LitStr::new(&cpp, Span::call_site()))
                            .map_err(ParseError::Syntax)?;
                    }
                }
                engine
                    .config_mut()
                    .extern_rust_funs
                    .append(&mut discoveries.extern_rust_funs);
                engine
                    .config_mut()
                    .rust_types
                    .append(&mut discoveries.extern_rust_types);
            }
        }
        if autocxx_seg_iterator.next().is_some() {
            return Err(ParseError::MultipleModsForDynamicDiscovery);
        }
    }
    let autocxx_seg_iterator = results.iter_mut().filter_map(|seg| match seg {
        Segment::Autocxx(engine) => Some(engine),
        _ => None,
    });
    for seg in autocxx_seg_iterator {
        seg.config
            .confirm_complete(auto_allowlist)
            .map_err(ParseError::Syntax)?;
    }
    Ok(ParsedFile(results))
}

/// A Rust file parsed by autocxx. May contain zero or more autocxx 'engines',
/// i.e. the `IncludeCpp` class, corresponding to zero or more include_cpp
/// macros within this file. Also contains `syn::Item` structures for all
/// the rest of the Rust code, such that it can be reconstituted if necessary.
pub struct ParsedFile(Vec<Segment>);

#[allow(clippy::large_enum_variant)]
enum Segment {
    Autocxx(IncludeCppEngine),
    Cxx(CxxBridge),
    Mod(Vec<Segment>, (Brace, ItemMod)),
    Other(Item),
}

pub trait CppBuildable {
    fn generate_h_and_cxx(
        &self,
        cpp_codegen_options: &CppCodegenOptions,
    ) -> Result<GeneratedCpp, cxx_gen::Error>;
}

impl ParsedFile {
    /// Get all the autocxxes in this parsed file.
    pub fn get_rs_buildables(&self) -> impl Iterator<Item = &IncludeCppEngine> {
        fn do_get_rs_buildables(segments: &[Segment]) -> impl Iterator<Item = &IncludeCppEngine> {
            segments
                .iter()
                .flat_map(|s| -> Box<dyn Iterator<Item = &IncludeCppEngine>> {
                    match s {
                        Segment::Autocxx(includecpp) => Box::new(std::iter::once(includecpp)),
                        Segment::Mod(segments, _) => Box::new(do_get_rs_buildables(segments)),
                        _ => Box::new(std::iter::empty()),
                    }
                })
        }

        do_get_rs_buildables(&self.0)
    }

    /// Get all items which can result in C++ code
    pub fn get_cpp_buildables(&self) -> impl Iterator<Item = &dyn CppBuildable> {
        fn do_get_cpp_buildables(segments: &[Segment]) -> impl Iterator<Item = &dyn CppBuildable> {
            segments
                .iter()
                .flat_map(|s| -> Box<dyn Iterator<Item = &dyn CppBuildable>> {
                    match s {
                        Segment::Autocxx(includecpp) => {
                            Box::new(std::iter::once(includecpp as &dyn CppBuildable))
                        }
                        Segment::Cxx(cxxbridge) => {
                            Box::new(std::iter::once(cxxbridge as &dyn CppBuildable))
                        }
                        Segment::Mod(segments, _) => Box::new(do_get_cpp_buildables(segments)),
                        _ => Box::new(std::iter::empty()),
                    }
                })
        }

        do_get_cpp_buildables(&self.0)
    }

    fn get_autocxxes_mut(&mut self) -> impl Iterator<Item = &mut IncludeCppEngine> {
        fn do_get_autocxxes_mut(
            segments: &mut [Segment],
        ) -> impl Iterator<Item = &mut IncludeCppEngine> {
            segments
                .iter_mut()
                .flat_map(|s| -> Box<dyn Iterator<Item = &mut IncludeCppEngine>> {
                    match s {
                        Segment::Autocxx(includecpp) => Box::new(std::iter::once(includecpp)),
                        Segment::Mod(segments, _) => Box::new(do_get_autocxxes_mut(segments)),
                        _ => Box::new(std::iter::empty()),
                    }
                })
        }

        do_get_autocxxes_mut(&mut self.0)
    }

    pub fn include_dirs(&self) -> impl Iterator<Item = &PathBuf> {
        fn do_get_include_dirs(segments: &[Segment]) -> impl Iterator<Item = &PathBuf> {
            segments
                .iter()
                .flat_map(|s| -> Box<dyn Iterator<Item = &PathBuf>> {
                    match s {
                        Segment::Autocxx(includecpp) => Box::new(includecpp.include_dirs()),
                        Segment::Mod(segments, _) => Box::new(do_get_include_dirs(segments)),
                        _ => Box::new(std::iter::empty()),
                    }
                })
        }

        do_get_include_dirs(&self.0)
    }

    pub fn resolve_all(
        &mut self,
        autocxx_inc: Vec<PathBuf>,
        extra_clang_args: &[&str],
        dep_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
        cpp_codegen_options: &CppCodegenOptions,
    ) -> Result<(), ParseError> {
        let mut mods_found = HashSet::new();
        let inner_dep_recorder: Option<Rc<dyn RebuildDependencyRecorder>> =
            dep_recorder.map(Rc::from);
        for include_cpp in self.get_autocxxes_mut() {
            #[allow(clippy::manual_map)] // because of dyn shenanigans
            let dep_recorder: Option<Box<dyn RebuildDependencyRecorder>> = match &inner_dep_recorder
            {
                None => None,
                Some(inner_dep_recorder) => Some(Box::new(CompositeDepRecorder::new(
                    inner_dep_recorder.clone(),
                ))),
            };
            if !mods_found.insert(include_cpp.get_mod_name()) {
                return Err(ParseError::ConflictingModNames);
            }
            include_cpp
                .generate(
                    autocxx_inc.clone(),
                    extra_clang_args,
                    dep_recorder,
                    cpp_codegen_options,
                )
                .map_err(ParseError::AutocxxCodegenError)?
        }
        Ok(())
    }
}

impl ToTokens for ParsedFile {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        for seg in &self.0 {
            seg.to_tokens(tokens)
        }
    }
}

impl ToTokens for Segment {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            Segment::Other(item) => item.to_tokens(tokens),
            Segment::Autocxx(autocxx) => {
                let these_tokens = autocxx.generate_rs();
                tokens.extend(these_tokens);
            }
            Segment::Cxx(cxxbridge) => cxxbridge.to_tokens(tokens),
            Segment::Mod(segments, (brace, itemmod)) => {
                let mod_items = segments
                    .iter()
                    .map(|segment| syn::parse2::<Item>(segment.to_token_stream()).unwrap())
                    .collect();
                let itemmod = ItemMod {
                    content: Some((*brace, mod_items)),
                    ..itemmod.clone()
                };
                Item::Mod(itemmod).to_tokens(tokens)
            }
        }
    }
}

/// Shenanigans required to share the same RebuildDependencyRecorder
/// with all of the include_cpp instances in this one file.
#[derive(Debug, Clone)]
struct CompositeDepRecorder(Rc<dyn RebuildDependencyRecorder>);

impl CompositeDepRecorder {
    fn new(inner: Rc<dyn RebuildDependencyRecorder>) -> Self {
        CompositeDepRecorder(inner)
    }
}

impl UnwindSafe for CompositeDepRecorder {}

impl RebuildDependencyRecorder for CompositeDepRecorder {
    fn record_header_file_dependency(&self, filename: &str) {
        self.0.record_header_file_dependency(filename);
    }
}
<|endoftext|>


<commit_before>use std::cmp;
use std::collections::HashMap;
use std::hash::Hash;
use std::marker::PhantomData;

use generate::decompose::IntoVertices;
use generate::topology::{Arity, Topological};

pub trait Indexer<T, K>
where
    T: IntoVertices + Topological,
{
    fn index<F>(&mut self, vertex: T::Vertex, f: F) -> (usize, Option<T::Vertex>)
    where
        F: Fn(&T::Vertex) -> &K;
}

pub struct HashIndexer<T, K>
where
    T: IntoVertices + Topological,
    K: Clone + Eq + Hash,
{
    hash: HashMap<K, usize>,
    n: usize,
    phantom: PhantomData<T>,
}

impl<T, K> HashIndexer<T, K>
where
    T: IntoVertices + Topological,
    K: Clone + Eq + Hash,
{
    fn new() -> Self {
        HashIndexer {
            hash: HashMap::new(),
            n: 0,
            phantom: PhantomData,
        }
    }
}

impl<T, K> Default for HashIndexer<T, K>
where
    T: IntoVertices + Topological,
    K: Clone + Eq + Hash,
{
    fn default() -> Self {
        HashIndexer::new()
    }
}

impl<T, K> Indexer<T, K> for HashIndexer<T, K>
where
    T: IntoVertices + Topological,
    K: Clone + Eq + Hash,
{
    fn index<F>(&mut self, input: T::Vertex, f: F) -> (usize, Option<T::Vertex>)
    where
        F: Fn(&T::Vertex) -> &K,
    {
        let mut vertex = None;
        let mut n = self.n;
        let index = self.hash.entry(f(&input).clone()).or_insert_with(|| {
            vertex = Some(input);
            let m = n;
            n += 1;
            m
        });
        self.n = n;
        (*index, vertex)
    }
}

pub struct LruIndexer<T, K>
where
    T: IntoVertices + Topological,
    K: Clone + PartialEq,
{
    lru: Vec<(K, usize)>,
    capacity: usize,
    n: usize,
    phantom: PhantomData<T>,
}

impl<T, K> LruIndexer<T, K>
where
    T: IntoVertices + Topological,
    K: Clone + PartialEq,
{
    pub fn new() -> Self {
        LruIndexer::with_capacity(16)
    }

    pub fn with_capacity(capacity: usize) -> Self {
        let capacity = cmp::max(1, capacity);
        LruIndexer {
            lru: Vec::with_capacity(capacity),
            capacity: capacity,
            n: 0,
            phantom: PhantomData,
        }
    }

    fn find(&self, key: &K) -> Option<(usize, usize)> {
        self.lru
            .iter()
            .enumerate()
            .find(|&(_, ref entry)| entry.0 == *key)
            .map(|(index, ref entry)| (index, entry.1))
    }
}

impl<T, K> Default for LruIndexer<T, K>
where
    T: IntoVertices + Topological,
    K: Clone + PartialEq,
{
    fn default() -> Self {
        LruIndexer::new()
    }
}

impl<T, K> Indexer<T, K> for LruIndexer<T, K>
where
    T: IntoVertices + Topological,
    K: Clone + PartialEq,
{
    fn index<F>(&mut self, input: T::Vertex, f: F) -> (usize, Option<T::Vertex>)
    where
        F: Fn(&T::Vertex) -> &K,
    {
        let mut vertex = None;
        let key = f(&input).clone();
        let index = if let Some(entry) = self.find(&key) {
            let vertex = self.lru.remove(entry.0);
            self.lru.push(vertex);
            entry.1
        }
        else {
            vertex = Some(input);
            let m = self.n;
            self.n += 1;
            if self.lru.len() >= self.capacity {
                self.lru.remove(0);
            }
            self.lru.push((key, m));
            m
        };
        (index, vertex)
    }
}

pub trait IndexVertices<T>: Sized
where
    T: Arity + IntoVertices + Topological,
{
    fn index_vertices_with_key<N, K, F>(self, indexer: N, f: F) -> (Vec<usize>, Vec<T::Vertex>)
    where
        N: Indexer<T, K>,
        F: Fn(&T::Vertex) -> &K;

    fn index_vertices<N>(self, indexer: N) -> (Vec<usize>, Vec<T::Vertex>)
    where
        N: Indexer<T, T::Vertex>,
    {
        self.index_vertices_with_key::<N, T::Vertex, _>(indexer, |vertex| vertex)
    }
}

impl<T, I> IndexVertices<T> for I
where
    I: Iterator<Item = T>,
    T: Arity + IntoVertices + Topological,
{
    fn index_vertices_with_key<N, K, F>(self, mut indexer: N, f: F) -> (Vec<usize>, Vec<T::Vertex>)
    where
        N: Indexer<T, K>,
        F: Fn(&T::Vertex) -> &K,
    {
        let mut indeces = Vec::new();
        let mut vertices = Vec::new();
        for topology in self {
            for vertex in topology.into_vertices() {
                let (index, vertex) = indexer.index(vertex, &f);
                indeces.push(index);
                if let Some(vertex) = vertex {
                    vertices.push(vertex);
                }
            }
        }
        (indeces, vertices)
    }
}

pub trait FromIndexer<P, Q>
where
    P: IntoVertices + Topological,
    Q: IntoVertices + Topological<Vertex = P::Vertex>,
{
    fn from_indexer<I, N>(input: I, indexer: N) -> Self
    where
        I: IntoIterator<Item = P>,
        N: Indexer<Q, P::Vertex>;
}

pub trait CollectWithIndexer<P, Q>
where
    P: IntoVertices + Topological,
    Q: IntoVertices + Topological<Vertex = P::Vertex>,
{
    fn collect_with_indexer<T, N>(self, indexer: N) -> T
    where
        T: FromIndexer<P, Q>,
        N: Indexer<Q, P::Vertex>;
}

impl<P, Q, I> CollectWithIndexer<P, Q> for I
where
    I: Iterator<Item = P>,
    P: IntoVertices + Topological,
    Q: IntoVertices + Topological<Vertex = P::Vertex>,
{
    fn collect_with_indexer<T, N>(self, indexer: N) -> T
    where
        T: FromIndexer<P, Q>,
        N: Indexer<Q, P::Vertex>,
    {
        T::from_indexer(self, indexer)
    }
}
<commit_msg>Expose `new` for `HashIndexer`.<commit_after>use std::cmp;
use std::collections::HashMap;
use std::hash::Hash;
use std::marker::PhantomData;

use generate::decompose::IntoVertices;
use generate::topology::{Arity, Topological};

pub trait Indexer<T, K>
where
    T: IntoVertices + Topological,
{
    fn index<F>(&mut self, vertex: T::Vertex, f: F) -> (usize, Option<T::Vertex>)
    where
        F: Fn(&T::Vertex) -> &K;
}

pub struct HashIndexer<T, K>
where
    T: IntoVertices + Topological,
    K: Clone + Eq + Hash,
{
    hash: HashMap<K, usize>,
    n: usize,
    phantom: PhantomData<T>,
}

impl<T, K> HashIndexer<T, K>
where
    T: IntoVertices + Topological,
    K: Clone + Eq + Hash,
{
    pub fn new() -> Self {
        HashIndexer {
            hash: HashMap::new(),
            n: 0,
            phantom: PhantomData,
        }
    }
}

impl<T, K> Default for HashIndexer<T, K>
where
    T: IntoVertices + Topological,
    K: Clone + Eq + Hash,
{
    fn default() -> Self {
        HashIndexer::new()
    }
}

impl<T, K> Indexer<T, K> for HashIndexer<T, K>
where
    T: IntoVertices + Topological,
    K: Clone + Eq + Hash,
{
    fn index<F>(&mut self, input: T::Vertex, f: F) -> (usize, Option<T::Vertex>)
    where
        F: Fn(&T::Vertex) -> &K,
    {
        let mut vertex = None;
        let mut n = self.n;
        let index = self.hash.entry(f(&input).clone()).or_insert_with(|| {
            vertex = Some(input);
            let m = n;
            n += 1;
            m
        });
        self.n = n;
        (*index, vertex)
    }
}

pub struct LruIndexer<T, K>
where
    T: IntoVertices + Topological,
    K: Clone + PartialEq,
{
    lru: Vec<(K, usize)>,
    capacity: usize,
    n: usize,
    phantom: PhantomData<T>,
}

impl<T, K> LruIndexer<T, K>
where
    T: IntoVertices + Topological,
    K: Clone + PartialEq,
{
    pub fn new() -> Self {
        LruIndexer::with_capacity(16)
    }

    pub fn with_capacity(capacity: usize) -> Self {
        let capacity = cmp::max(1, capacity);
        LruIndexer {
            lru: Vec::with_capacity(capacity),
            capacity: capacity,
            n: 0,
            phantom: PhantomData,
        }
    }

    fn find(&self, key: &K) -> Option<(usize, usize)> {
        self.lru
            .iter()
            .enumerate()
            .find(|&(_, ref entry)| entry.0 == *key)
            .map(|(index, ref entry)| (index, entry.1))
    }
}

impl<T, K> Default for LruIndexer<T, K>
where
    T: IntoVertices + Topological,
    K: Clone + PartialEq,
{
    fn default() -> Self {
        LruIndexer::new()
    }
}

impl<T, K> Indexer<T, K> for LruIndexer<T, K>
where
    T: IntoVertices + Topological,
    K: Clone + PartialEq,
{
    fn index<F>(&mut self, input: T::Vertex, f: F) -> (usize, Option<T::Vertex>)
    where
        F: Fn(&T::Vertex) -> &K,
    {
        let mut vertex = None;
        let key = f(&input).clone();
        let index = if let Some(entry) = self.find(&key) {
            let vertex = self.lru.remove(entry.0);
            self.lru.push(vertex);
            entry.1
        }
        else {
            vertex = Some(input);
            let m = self.n;
            self.n += 1;
            if self.lru.len() >= self.capacity {
                self.lru.remove(0);
            }
            self.lru.push((key, m));
            m
        };
        (index, vertex)
    }
}

pub trait IndexVertices<T>: Sized
where
    T: Arity + IntoVertices + Topological,
{
    fn index_vertices_with_key<N, K, F>(self, indexer: N, f: F) -> (Vec<usize>, Vec<T::Vertex>)
    where
        N: Indexer<T, K>,
        F: Fn(&T::Vertex) -> &K;

    fn index_vertices<N>(self, indexer: N) -> (Vec<usize>, Vec<T::Vertex>)
    where
        N: Indexer<T, T::Vertex>,
    {
        self.index_vertices_with_key::<N, T::Vertex, _>(indexer, |vertex| vertex)
    }
}

impl<T, I> IndexVertices<T> for I
where
    I: Iterator<Item = T>,
    T: Arity + IntoVertices + Topological,
{
    fn index_vertices_with_key<N, K, F>(self, mut indexer: N, f: F) -> (Vec<usize>, Vec<T::Vertex>)
    where
        N: Indexer<T, K>,
        F: Fn(&T::Vertex) -> &K,
    {
        let mut indeces = Vec::new();
        let mut vertices = Vec::new();
        for topology in self {
            for vertex in topology.into_vertices() {
                let (index, vertex) = indexer.index(vertex, &f);
                indeces.push(index);
                if let Some(vertex) = vertex {
                    vertices.push(vertex);
                }
            }
        }
        (indeces, vertices)
    }
}

pub trait FromIndexer<P, Q>
where
    P: IntoVertices + Topological,
    Q: IntoVertices + Topological<Vertex = P::Vertex>,
{
    fn from_indexer<I, N>(input: I, indexer: N) -> Self
    where
        I: IntoIterator<Item = P>,
        N: Indexer<Q, P::Vertex>;
}

pub trait CollectWithIndexer<P, Q>
where
    P: IntoVertices + Topological,
    Q: IntoVertices + Topological<Vertex = P::Vertex>,
{
    fn collect_with_indexer<T, N>(self, indexer: N) -> T
    where
        T: FromIndexer<P, Q>,
        N: Indexer<Q, P::Vertex>;
}

impl<P, Q, I> CollectWithIndexer<P, Q> for I
where
    I: Iterator<Item = P>,
    P: IntoVertices + Topological,
    Q: IntoVertices + Topological<Vertex = P::Vertex>,
{
    fn collect_with_indexer<T, N>(self, indexer: N) -> T
    where
        T: FromIndexer<P, Q>,
        N: Indexer<Q, P::Vertex>,
    {
        T::from_indexer(self, indexer)
    }
}
<|endoftext|>