<commit_before>// Copyright (C) 2018 O.S. Systems Sofware LTDA
//
// SPDX-License-Identifier: Apache-2.0

use chrono::Duration;
use derive_more::{Deref, DerefMut, Display, Error, From};
use sdk::api::info::settings as api;
use slog_scope::{debug, error};
use std::{fs, io, path::Path};

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug, Display, Error, From)]
pub enum Error {
    Io(io::Error),
    Deserialize(toml::de::Error),
    Serialize(toml::ser::Error),

    #[display(fmt = "invalid setting for polling interval, it cannot be less than 60 seconds")]
    TooSmallPollingInterval,
    #[display(fmt = "invalid setting for server address, it must use the protocol prefix")]
    ServerAddressWithoutProtocol,

    #[cfg(feature = "v1-parsing")]
    #[display(fmt = "parsing error: toml: {}, ini: {}", _0, _1)]
    V1Parsing(toml::de::Error, serde_ini::de::Error),
}

#[derive(Clone, Debug, Deref, DerefMut, PartialEq)]
pub struct Settings(pub api::Settings);

impl Default for Settings {
    fn default() -> Self {
        Settings(api::Settings {
            polling: api::Polling { interval: Duration::days(1), enabled: true },
            storage: api::Storage {
                read_only: false,
                runtime_settings: "/var/lib/updatehub/runtime_settings.conf".into(),
            },
            update: api::Update {
                download_dir: "/tmp/updatehub".into(),
                supported_install_modes: [
                    "dry-run", "copy", "flash", "imxkobs", "raw", "tarball", "ubifs",
                ]
                .iter()
                .map(|i| (*i).to_string())
                .collect(),
            },
            network: api::Network {
                server_address: "https://api.updatehub.io".to_string(),
                listen_socket: "localhost:8080".to_string(),
            },
            firmware: api::Firmware { metadata: "/usr/share/updatehub".into() },
        })
    }
}

impl Settings {
    /// Loads the settings from the filesystem. If
    /// `/etc/updatehub.conf` does not exists, it uses the default
    /// settings.
    pub fn load(path: &Path) -> Result<Self> {
        if path.exists() {
            debug!("loading system settings from {:?}", path);
            Ok(Self::parse(&fs::read_to_string(path)?)?)
        } else {
            debug!("system settings file {:?} does not exists, using default settings", path);
            Ok(Self::default())
        }
    }

    // This parses the configuration file, taking into account the
    // needed validations for all fields, and returns either `Self` or
    // `Err`.
    fn parse(content: &str) -> Result<Self> {
        let res = toml::from_str::<api::Settings>(content);
        let res = res.or_else(|e| v1_parse(content, e));
        let settings = Settings(res?);

        if settings.polling.interval < Duration::seconds(60) {
            error!("invalid setting for polling interval, it cannot be less than 60 seconds");
            return Err(Error::TooSmallPollingInterval);
        }

        if !&settings.network.server_address.starts_with("http://")
            && !&settings.network.server_address.starts_with("https://")
        {
            error!("invalid setting for server address, it must use the protocol prefix");
            return Err(Error::ServerAddressWithoutProtocol);
        }

        Ok(settings)
    }
}

#[cfg(feature = "v1-parsing")]
fn v1_parse(content: &str, toml_err: toml::de::Error) -> Result<api::Settings> {
    use serde::Deserialize;
    use std::path::PathBuf;

    mod deserialize {
        use chrono::Duration;
        use serde::{de, Deserialize, Deserializer};

        pub(crate) fn duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
        where
            D: Deserializer<'de>,
        {
            use ms_converter::ms;

            let s = String::deserialize(deserializer)?;
            Ok(Duration::milliseconds(ms(&s).map_err(de::Error::custom)?))
        }

        pub fn boolean<'de, D>(deserializer: D) -> Result<bool, D::Error>
        where
            D: Deserializer<'de>,
        {
            use std::str::FromStr;

            let s = String::deserialize(deserializer)?;
            bool::from_str(&s).map_err(de::Error::custom)
        }

        pub fn string_list<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
        where
            D: Deserializer<'de>,
        {
            Ok(String::deserialize(deserializer)?
                .split(',')
                .map(std::string::ToString::to_string)
                .collect())
        }
    }

    #[derive(Deserialize)]
    #[serde(rename_all = "PascalCase")]
    struct Settings {
        #[serde(default)]
        firmware: Firmware,
        network: Network,
        polling: Polling,
        storage: Storage,
        update: Update,
    }

    #[derive(Deserialize)]
    #[serde(rename_all = "PascalCase")]
    struct Polling {
        #[serde(deserialize_with = "deserialize::duration")]
        interval: Duration,
        #[serde(deserialize_with = "deserialize::boolean")]
        enabled: bool,
    }

    #[derive(Deserialize)]
    #[serde(rename_all = "PascalCase")]
    struct Storage {
        #[serde(default)]
        #[serde(deserialize_with = "deserialize::boolean")]
        read_only: bool,
        runtime_settings_path: String,
    }

    #[derive(Deserialize)]
    #[serde(rename_all = "PascalCase")]
    struct Update {
        download_dir: PathBuf,
        #[serde(deserialize_with = "deserialize::string_list")]
        supported_install_modes: Vec<String>,
    }

    #[derive(Deserialize)]
    #[serde(rename_all = "PascalCase")]
    struct Network {
        server_address: String,
        #[serde(default)]
        listen_socket: String,
    }

    #[derive(Deserialize)]
    #[serde(rename_all = "PascalCase")]
    #[serde(default)]
    struct Firmware {
        metadata_path: PathBuf,
    }

    impl Default for Polling {
        fn default() -> Self {
            Self { interval: Duration::days(1), enabled: true }
        }
    }

    impl Default for Storage {
        fn default() -> Self {
            Self {
                read_only: false,
                runtime_settings_path: "/var/lib/updatehub/runtime_settings.conf".into(),
            }
        }
    }

    impl Default for Update {
        fn default() -> Self {
            Self {
                download_dir: "/tmp/updatehub".into(),
                supported_install_modes: [
                    "dry-run", "copy", "flash", "imxkobs", "raw", "tarball", "ubifs",
                ]
                .iter()
                .map(|i| i.to_string())
                .collect(),
            }
        }
    }

    impl Default for Network {
        fn default() -> Self {
            Self {
                server_address: "https://api.updatehub.io".to_string(),
                listen_socket: "localhost:8080".to_string(),
            }
        }
    }

    impl Default for Firmware {
        fn default() -> Self {
            Self { metadata_path: "/usr/share/updatehub".into() }
        }
    }

    let old_settings = match serde_ini::de::from_str::<Settings>(content) {
        Ok(s) => s,
        Err(err) => return Err(Error::V1Parsing(toml_err, err)),
    };

    Ok(api::Settings {
        firmware: api::Firmware { metadata: old_settings.firmware.metadata_path },
        network: api::Network {
            server_address: old_settings.network.server_address,
            listen_socket: old_settings.network.listen_socket,
        },
        polling: api::Polling {
            interval: old_settings.polling.interval,
            enabled: old_settings.polling.enabled,
        },
        storage: api::Storage {
            read_only: old_settings.storage.read_only,
            runtime_settings: old_settings.storage.runtime_settings_path.into(),
        },
        update: api::Update {
            download_dir: old_settings.update.download_dir,
            supported_install_modes: old_settings.update.supported_install_modes,
        },
    })
}

#[cfg(not(feature = "v1-parsing"))]
#[inline]
fn v1_parse(_: &str, e: toml::de::Error) -> Result<api::Settings> {
    Err(e.into())
}

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

    #[test]
    fn basic_config() {
        let sample = r#"
[network]
server_address="https://api.updatehub.io"
listen_socket="localhost:8080"

[storage]
read_only = false
runtime_settings="/data/updatehub/state.data"

[polling]
enabled=true
interval="60s"

[update]
download_dir="/tmp/updatehub"
supported_install_modes=["copy", "tarball"]

[firmware]
metadata="/usr/share/updatehub"
"#;
        let expected = Settings(api::Settings {
            polling: api::Polling { interval: Duration::minutes(1), enabled: true },
            storage: api::Storage {
                read_only: false,
                runtime_settings: "/data/updatehub/state.data".into(),
            },
            update: api::Update {
                download_dir: "/tmp/updatehub".into(),
                supported_install_modes: ["copy", "tarball"]
                    .iter()
                    .map(|i| (*i).to_string())
                    .collect(),
            },
            network: api::Network {
                server_address: "https://api.updatehub.io".to_string(),
                listen_socket: "localhost:8080".to_string(),
            },
            firmware: api::Firmware { metadata: "/usr/share/updatehub".into() },
        });
        assert_eq!(Settings::parse(sample).unwrap(), expected);
    }

    #[test]
    fn invalid_polling_interval() {
        let sample = r#"
[network]
server_address="https://api.updatehub.io"
listen_socket="localhost:8080"

[storage]
read_only = false
runtime_settings="/data/updatehub/state.data"

[polling]
enabled=true
interval="59s"

[update]
download_dir="/tmp/updatehub"
supported_install_modes=["copy", "tarball"]

[firmware]
metadata="/usr/share/updatehub"
"#;
        assert!(Settings::parse(sample).is_err());
    }

    #[test]
    fn invalid_network_server_address() {
        let sample = r#"
[network]
server_address="api.updatehub.io"
listen_socket="localhost:8080"

[storage]
read_only = false
runtime_settings="/data/updatehub/state.data"

[polling]
enabled=true
interval=60s

[update]
download_dir="/tmp/updatehub"
supported_install_modes=["copy", "tarball"]

[firmware]
metadata="/usr/share/updatehub"
"#;

        assert!(Settings::parse(sample).is_err());
    }

    #[test]
    fn default() {
        let mut settings = Settings::default();
        settings.network.server_address = "https://api.updatehub.io".to_string();

        let expected = Settings(api::Settings {
            polling: api::Polling { interval: Duration::days(1), enabled: true },
            storage: api::Storage {
                read_only: false,
                runtime_settings: "/var/lib/updatehub/runtime_settings.conf".into(),
            },
            update: api::Update {
                download_dir: "/tmp/updatehub".into(),
                supported_install_modes: [
                    "dry-run", "copy", "flash", "imxkobs", "raw", "tarball", "ubifs",
                ]
                .iter()
                .map(|i| i.to_string())
                .collect(),
            },
            network: api::Network {
                server_address: "https://api.updatehub.io".to_string(),
                listen_socket: "localhost:8080".to_string(),
            },
            firmware: api::Firmware { metadata: "/usr/share/updatehub".into() },
        });

        assert_eq!(Some(settings), Some(expected));
    }

    #[cfg(feature = "v1-parsing")]
    #[test]
    fn v1_parsing() {
        let sample = r"
[Polling]
Interval=60s
Enabled=false

[Storage]
RuntimeSettingsPath=/run/updatehub/state

[Update]
DownloadDir=/tmp/download
SupportedInstallModes=mode1,mode2

[Network]
ServerAddress=http://localhost
ListenSocket=localhost:8313
";

        let expected = Settings(api::Settings {
            polling: api::Polling { interval: Duration::minutes(1), enabled: false },
            storage: api::Storage {
                read_only: false,
                runtime_settings: "/run/updatehub/state".into(),
            },
            update: api::Update {
                download_dir: "/tmp/download".into(),
                supported_install_modes: ["mode1", "mode2"].iter().map(|i| i.to_string()).collect(),
            },
            network: api::Network {
                server_address: "http://localhost".to_string(),
                listen_socket: "localhost:8313".to_string(),
            },
            firmware: api::Firmware { metadata: "/usr/share/updatehub".into() },
        });

        assert_eq!(Settings::parse(sample).unwrap(), expected);
    }
}
<commit_msg>updatehub: settings: small refactor to simplify code<commit_after>// Copyright (C) 2018 O.S. Systems Sofware LTDA
//
// SPDX-License-Identifier: Apache-2.0

use chrono::Duration;
use derive_more::{Deref, DerefMut, Display, Error, From};
use sdk::api::info::settings as api;
use slog_scope::{debug, error};
use std::{fs, io, path::Path};

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug, Display, Error, From)]
pub enum Error {
    Io(io::Error),
    Deserialize(toml::de::Error),
    Serialize(toml::ser::Error),

    #[display(fmt = "invalid setting for polling interval, it cannot be less than 60 seconds")]
    TooSmallPollingInterval,
    #[display(fmt = "invalid setting for server address, it must use the protocol prefix")]
    ServerAddressWithoutProtocol,

    #[cfg(feature = "v1-parsing")]
    #[display(fmt = "parsing error: toml: {}, ini: {}", _0, _1)]
    V1Parsing(toml::de::Error, serde_ini::de::Error),
}

#[derive(Clone, Debug, Deref, DerefMut, PartialEq)]
pub struct Settings(pub api::Settings);

impl Default for Settings {
    fn default() -> Self {
        Settings(api::Settings {
            polling: api::Polling { interval: Duration::days(1), enabled: true },
            storage: api::Storage {
                read_only: false,
                runtime_settings: "/var/lib/updatehub/runtime_settings.conf".into(),
            },
            update: api::Update {
                download_dir: "/tmp/updatehub".into(),
                supported_install_modes: [
                    "dry-run", "copy", "flash", "imxkobs", "raw", "tarball", "ubifs",
                ]
                .iter()
                .map(|i| (*i).to_string())
                .collect(),
            },
            network: api::Network {
                server_address: "https://api.updatehub.io".to_string(),
                listen_socket: "localhost:8080".to_string(),
            },
            firmware: api::Firmware { metadata: "/usr/share/updatehub".into() },
        })
    }
}

impl Settings {
    /// Loads the settings from the filesystem. If
    /// `/etc/updatehub.conf` does not exists, it uses the default
    /// settings.
    pub fn load(path: &Path) -> Result<Self> {
        if path.exists() {
            debug!("loading system settings from {:?}", path);
            Ok(Self::parse(&fs::read_to_string(path)?)?)
        } else {
            debug!("system settings file {:?} does not exists, using default settings", path);
            Ok(Self::default())
        }
    }

    // This parses the configuration file, taking into account the
    // needed validations for all fields, and returns either `Self` or
    // `Err`.
    fn parse(content: &str) -> Result<Self> {
        let settings = Settings(toml::from_str(content).or_else(|e| v1_parse(content, e))?);

        if settings.polling.interval < Duration::seconds(60) {
            error!("invalid setting for polling interval, it cannot be less than 60 seconds");
            return Err(Error::TooSmallPollingInterval);
        }

        if !&settings.network.server_address.starts_with("http://")
            && !&settings.network.server_address.starts_with("https://")
        {
            error!("invalid setting for server address, it must use the protocol prefix");
            return Err(Error::ServerAddressWithoutProtocol);
        }

        Ok(settings)
    }
}

#[cfg(feature = "v1-parsing")]
fn v1_parse(content: &str, toml_err: toml::de::Error) -> Result<api::Settings> {
    use serde::Deserialize;
    use std::path::PathBuf;

    mod deserialize {
        use chrono::Duration;
        use serde::{de, Deserialize, Deserializer};

        pub(crate) fn duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
        where
            D: Deserializer<'de>,
        {
            use ms_converter::ms;

            let s = String::deserialize(deserializer)?;
            Ok(Duration::milliseconds(ms(&s).map_err(de::Error::custom)?))
        }

        pub fn boolean<'de, D>(deserializer: D) -> Result<bool, D::Error>
        where
            D: Deserializer<'de>,
        {
            use std::str::FromStr;

            let s = String::deserialize(deserializer)?;
            bool::from_str(&s).map_err(de::Error::custom)
        }

        pub fn string_list<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
        where
            D: Deserializer<'de>,
        {
            Ok(String::deserialize(deserializer)?
                .split(',')
                .map(std::string::ToString::to_string)
                .collect())
        }
    }

    #[derive(Deserialize)]
    #[serde(rename_all = "PascalCase")]
    struct Settings {
        #[serde(default)]
        firmware: Firmware,
        network: Network,
        polling: Polling,
        storage: Storage,
        update: Update,
    }

    #[derive(Deserialize)]
    #[serde(rename_all = "PascalCase")]
    struct Polling {
        #[serde(deserialize_with = "deserialize::duration")]
        interval: Duration,
        #[serde(deserialize_with = "deserialize::boolean")]
        enabled: bool,
    }

    #[derive(Deserialize)]
    #[serde(rename_all = "PascalCase")]
    struct Storage {
        #[serde(default)]
        #[serde(deserialize_with = "deserialize::boolean")]
        read_only: bool,
        runtime_settings_path: String,
    }

    #[derive(Deserialize)]
    #[serde(rename_all = "PascalCase")]
    struct Update {
        download_dir: PathBuf,
        #[serde(deserialize_with = "deserialize::string_list")]
        supported_install_modes: Vec<String>,
    }

    #[derive(Deserialize)]
    #[serde(rename_all = "PascalCase")]
    struct Network {
        server_address: String,
        #[serde(default)]
        listen_socket: String,
    }

    #[derive(Deserialize)]
    #[serde(rename_all = "PascalCase")]
    #[serde(default)]
    struct Firmware {
        metadata_path: PathBuf,
    }

    impl Default for Polling {
        fn default() -> Self {
            Self { interval: Duration::days(1), enabled: true }
        }
    }

    impl Default for Storage {
        fn default() -> Self {
            Self {
                read_only: false,
                runtime_settings_path: "/var/lib/updatehub/runtime_settings.conf".into(),
            }
        }
    }

    impl Default for Update {
        fn default() -> Self {
            Self {
                download_dir: "/tmp/updatehub".into(),
                supported_install_modes: [
                    "dry-run", "copy", "flash", "imxkobs", "raw", "tarball", "ubifs",
                ]
                .iter()
                .map(|i| i.to_string())
                .collect(),
            }
        }
    }

    impl Default for Network {
        fn default() -> Self {
            Self {
                server_address: "https://api.updatehub.io".to_string(),
                listen_socket: "localhost:8080".to_string(),
            }
        }
    }

    impl Default for Firmware {
        fn default() -> Self {
            Self { metadata_path: "/usr/share/updatehub".into() }
        }
    }

    let old_settings = serde_ini::de::from_str::<Settings>(content)
        .map_err(|ini_err| Error::V1Parsing(toml_err, ini_err))?;

    Ok(api::Settings {
        firmware: api::Firmware { metadata: old_settings.firmware.metadata_path },
        network: api::Network {
            server_address: old_settings.network.server_address,
            listen_socket: old_settings.network.listen_socket,
        },
        polling: api::Polling {
            interval: old_settings.polling.interval,
            enabled: old_settings.polling.enabled,
        },
        storage: api::Storage {
            read_only: old_settings.storage.read_only,
            runtime_settings: old_settings.storage.runtime_settings_path.into(),
        },
        update: api::Update {
            download_dir: old_settings.update.download_dir,
            supported_install_modes: old_settings.update.supported_install_modes,
        },
    })
}

#[cfg(not(feature = "v1-parsing"))]
#[inline]
fn v1_parse(_: &str, e: toml::de::Error) -> Result<api::Settings> {
    Err(e.into())
}

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

    #[test]
    fn basic_config() {
        let sample = r#"
[network]
server_address="https://api.updatehub.io"
listen_socket="localhost:8080"

[storage]
read_only = false
runtime_settings="/data/updatehub/state.data"

[polling]
enabled=true
interval="60s"

[update]
download_dir="/tmp/updatehub"
supported_install_modes=["copy", "tarball"]

[firmware]
metadata="/usr/share/updatehub"
"#;
        let expected = Settings(api::Settings {
            polling: api::Polling { interval: Duration::minutes(1), enabled: true },
            storage: api::Storage {
                read_only: false,
                runtime_settings: "/data/updatehub/state.data".into(),
            },
            update: api::Update {
                download_dir: "/tmp/updatehub".into(),
                supported_install_modes: ["copy", "tarball"]
                    .iter()
                    .map(|i| (*i).to_string())
                    .collect(),
            },
            network: api::Network {
                server_address: "https://api.updatehub.io".to_string(),
                listen_socket: "localhost:8080".to_string(),
            },
            firmware: api::Firmware { metadata: "/usr/share/updatehub".into() },
        });
        assert_eq!(Settings::parse(sample).unwrap(), expected);
    }

    #[test]
    fn invalid_polling_interval() {
        let sample = r#"
[network]
server_address="https://api.updatehub.io"
listen_socket="localhost:8080"

[storage]
read_only = false
runtime_settings="/data/updatehub/state.data"

[polling]
enabled=true
interval="59s"

[update]
download_dir="/tmp/updatehub"
supported_install_modes=["copy", "tarball"]

[firmware]
metadata="/usr/share/updatehub"
"#;
        assert!(Settings::parse(sample).is_err());
    }

    #[test]
    fn invalid_network_server_address() {
        let sample = r#"
[network]
server_address="api.updatehub.io"
listen_socket="localhost:8080"

[storage]
read_only = false
runtime_settings="/data/updatehub/state.data"

[polling]
enabled=true
interval=60s

[update]
download_dir="/tmp/updatehub"
supported_install_modes=["copy", "tarball"]

[firmware]
metadata="/usr/share/updatehub"
"#;

        assert!(Settings::parse(sample).is_err());
    }

    #[test]
    fn default() {
        let mut settings = Settings::default();
        settings.network.server_address = "https://api.updatehub.io".to_string();

        let expected = Settings(api::Settings {
            polling: api::Polling { interval: Duration::days(1), enabled: true },
            storage: api::Storage {
                read_only: false,
                runtime_settings: "/var/lib/updatehub/runtime_settings.conf".into(),
            },
            update: api::Update {
                download_dir: "/tmp/updatehub".into(),
                supported_install_modes: [
                    "dry-run", "copy", "flash", "imxkobs", "raw", "tarball", "ubifs",
                ]
                .iter()
                .map(|i| i.to_string())
                .collect(),
            },
            network: api::Network {
                server_address: "https://api.updatehub.io".to_string(),
                listen_socket: "localhost:8080".to_string(),
            },
            firmware: api::Firmware { metadata: "/usr/share/updatehub".into() },
        });

        assert_eq!(Some(settings), Some(expected));
    }

    #[cfg(feature = "v1-parsing")]
    #[test]
    fn v1_parsing() {
        let sample = r"
[Polling]
Interval=60s
Enabled=false

[Storage]
RuntimeSettingsPath=/run/updatehub/state

[Update]
DownloadDir=/tmp/download
SupportedInstallModes=mode1,mode2

[Network]
ServerAddress=http://localhost
ListenSocket=localhost:8313
";

        let expected = Settings(api::Settings {
            polling: api::Polling { interval: Duration::minutes(1), enabled: false },
            storage: api::Storage {
                read_only: false,
                runtime_settings: "/run/updatehub/state".into(),
            },
            update: api::Update {
                download_dir: "/tmp/download".into(),
                supported_install_modes: ["mode1", "mode2"].iter().map(|i| i.to_string()).collect(),
            },
            network: api::Network {
                server_address: "http://localhost".to_string(),
                listen_socket: "localhost:8313".to_string(),
            },
            firmware: api::Firmware { metadata: "/usr/share/updatehub".into() },
        });

        assert_eq!(Settings::parse(sample).unwrap(), expected);
    }
}
<|endoftext|>


<commit_before>//! Crate to `#include` C++ headers in your Rust code, and generate
//! idiomatic bindings using `cxx`. See [include_cpp] for details.

// 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 std::{
    fs::File,
    io::Write,
    os::unix::prelude::PermissionsExt,
    path::{Path, PathBuf},
    process::Command,
};

use autocxx_engine::preprocess;
use clap::{crate_authors, crate_version, App, Arg, ArgMatches};
use indoc::indoc;
use itertools::Itertools;
use tempfile::TempDir;

static LONG_HELP: &str = indoc! {"
Command line utility to minimize autocxx bug cases.

This is a wrapper for creduce.

Example command-line:
autocxx-reduce -I my-inc-dir -h my-header -d 'generate!(\"MyClass\")' -k -- --n 64
"};

fn main() {
    let matches = App::new("autocxx-reduce")
        .version(crate_version!())
        .author(crate_authors!())
        .about("Reduce a C++ test case")
        .long_about(LONG_HELP)
        .arg(
            Arg::with_name("inc")
                .short("I")
                .long("inc")
                .multiple(true)
                .number_of_values(1)
                .value_name("INCLUDE DIRS")
                .help("include path")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("define")
                .short("D")
                .long("define")
                .multiple(true)
                .number_of_values(1)
                .value_name("DEFINE")
                .help("macro definition")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("header")
                .short("h")
                .long("header")
                .multiple(true)
                .number_of_values(1)
                .required(true)
                .value_name("HEADER")
                .help("header file name")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("directive")
                .short("d")
                .long("directive")
                .multiple(true)
                .number_of_values(1)
                .value_name("DIRECTIVE")
                .help("directives to put within include_cpp!")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("problem")
                .short("p")
                .long("problem")
                .required(true)
                .value_name("PROBLEM")
                .help("problem string we're looking for... may be in logs, or in generated C++, or generated .rs")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("creduce")
                .long("creduce")
                .required(true)
                .value_name("PATH")
                .help("creduce binary location")
                .default_value("/usr/bin/creduce")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("output")
                .short("o")
                .long("output")
                .value_name("OUTPUT")
                .help("where to write minimized output")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("gen-cmd")
                .short("g")
                .long("gen-cmd")
                .value_name("GEN-CMD")
                .help("where to find autocxx-gen")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("keep")
                .short("k")
                .long("keep-dir")
                .help("keep the temporary directory for debugging purposes"),
        )
        .arg(
            Arg::with_name("clang-args")
                .short("c")
                .long("clang-arg")
                .multiple(true)
                .value_name("CLANG_ARG")
                .help("Extra arguments to pass to Clang"),
        )
        .arg(Arg::with_name("creduce-args").last(true).multiple(true))
        .get_matches();
    run(matches).unwrap();
}

fn run(matches: ArgMatches) -> Result<(), std::io::Error> {
    let keep_tmp = matches.is_present("keep");
    let tmp_dir = TempDir::new()?;
    let r = do_run(matches, &tmp_dir);
    if keep_tmp {
        println!(
            "Keeping temp dir created at: {}",
            tmp_dir.into_path().to_str().unwrap()
        );
    }
    r
}

fn do_run(matches: ArgMatches, tmp_dir: &TempDir) -> Result<(), std::io::Error> {
    let incs: Vec<_> = matches
        .values_of("inc")
        .unwrap_or_default()
        .map(PathBuf::from)
        .collect();
    let defs: Vec<_> = matches.values_of("define").unwrap_or_default().collect();
    let headers: Vec<_> = matches.values_of("header").unwrap_or_default().collect();
    let listing_path = tmp_dir.path().join("listing.h");
    create_concatenated_header(&headers, &listing_path)?;
    let concat_path = tmp_dir.path().join("concat.h");
    announce_progress(&format!(
        "Preprocessing {:?} to {:?}",
        listing_path, concat_path
    ));
    preprocess(&listing_path, &concat_path, &incs, &defs)?;
    let rs_path = tmp_dir.path().join("input.rs");
    let directives: Vec<_> = std::iter::once("#include \"concat.h\"\n".to_string())
        .chain(
            matches
                .values_of("directive")
                .unwrap_or_default()
                .map(|s| format!("{}\n", s)),
        )
        .collect();
    create_rs_file(&rs_path, &directives)?;
    let extra_clang_args: Vec<_> = matches
        .values_of("clang-args")
        .unwrap_or_default()
        .collect();
    let default_gen_cmd = std::env::current_exe()?
        .parent()
        .unwrap()
        .join("autocxx-gen")
        .to_str()
        .unwrap()
        .to_string();
    let gen_cmd = matches.value_of("gen-cmd").unwrap_or(&default_gen_cmd);
    run_sample_gen_cmd(gen_cmd, &rs_path, &tmp_dir.path(), &extra_clang_args)?;
    let interestingness_test = tmp_dir.path().join("test.sh");
    create_interestingness_test(
        gen_cmd,
        &interestingness_test,
        matches.value_of("problem").unwrap(),
        &rs_path,
        &extra_clang_args,
    )?;
    run_interestingness_test(&interestingness_test);
    run_creduce(
        matches.value_of("creduce").unwrap(),
        &interestingness_test,
        &concat_path,
        matches.values_of("creduce-args").unwrap_or_default(),
    );
    let output_path = matches.value_of("output");
    match output_path {
        None => print_minimized_case(&concat_path)?,
        Some(output_path) => {
            std::fs::copy(&concat_path, &PathBuf::from(output_path))?;
        }
    };
    Ok(())
}

fn announce_progress(msg: &str) {
    println!("=== {} ===", msg);
}

fn print_minimized_case(concat_path: &Path) -> Result<(), std::io::Error> {
    announce_progress("Completed. Minimized test case:");
    let contents = std::fs::read_to_string(concat_path)?;
    println!("{}", contents);
    Ok(())
}

/// Arguments we always pass to creduce. This pass always seems to cause a crash
/// as far as I can tell, so always exclude it. It may be environment-dependent,
/// of course, but as I'm the primary user of this tool I am ruthlessly removing it.
const CREDUCE_STANDARD_ARGS: &[&str] = &["--remove-pass", "pass_line_markers", "*"];

fn run_creduce<'a>(
    creduce_cmd: &str,
    interestingness_test: &'a Path,
    concat_path: &'a Path,
    creduce_args: impl Iterator<Item = &'a str>,
) {
    announce_progress("creduce");
    let args = std::iter::once(interestingness_test.to_str().unwrap())
        .chain(std::iter::once(concat_path.to_str().unwrap()))
        .chain(CREDUCE_STANDARD_ARGS.iter().copied())
        .chain(creduce_args)
        .collect::<Vec<_>>();
    println!("Command: {} {}", creduce_cmd, args.join(" "));
    Command::new(creduce_cmd)
        .args(args)
        .status()
        .expect("failed to creduce");
}

fn run_sample_gen_cmd(
    gen_cmd: &str,
    rs_file: &Path,
    tmp_dir: &Path,
    extra_clang_args: &[&str],
) -> Result<(), std::io::Error> {
    let args = format_gen_cmd(rs_file, tmp_dir.to_str().unwrap(), extra_clang_args);
    let args = args.collect::<Vec<_>>();
    let args_str = args.join(" ");
    announce_progress(&format!("Running sample gen cmd: {} {}", gen_cmd, args_str));
    Command::new(gen_cmd).args(args).status()?;
    Ok(())
}

fn format_gen_cmd<'a>(
    rs_file: &Path,
    dir: &str,
    extra_clang_args: &'a [&str],
) -> impl Iterator<Item = String> + 'a {
    let args = [
        "-o".to_string(),
        dir.to_string(),
        "-I".to_string(),
        dir.to_string(),
        rs_file.to_str().unwrap().to_string(),
        "--gen-rs-complete".to_string(),
        "--gen-cpp".to_string(),
        "--".to_string(),
    ]
    .to_vec();
    args.into_iter()
        .chain(extra_clang_args.iter().map(|s| s.to_string()))
}

fn create_interestingness_test(
    gen_cmd: &str,
    test_path: &Path,
    problem: &str,
    rs_file: &Path,
    extra_clang_args: &[&str],
) -> Result<(), std::io::Error> {
    announce_progress("Creating interestingness test");
    // Ensure we refer to the input header by relative path
    // because creduce will invoke us in some other directory with
    // a copy thereof.
    let mut args = format_gen_cmd(rs_file, "$(pwd)", extra_clang_args);
    let args = args.join(" ");
    let content = format!(
        indoc! {"
        #!/bin/sh
        ({} {} 2>&1 && cat gen.complete.rs && cat autocxxgen.h) | grep \"{}\"  >/dev/null 2>&1
    "},
        gen_cmd, args, problem
    );
    println!("Interestingness test:\n{}", content);
    {
        let mut file = File::create(test_path)?;
        file.write_all(content.as_bytes())?;
    }

    let mut perms = std::fs::metadata(&test_path)?.permissions();
    perms.set_mode(0o700);
    std::fs::set_permissions(&test_path, perms)?;
    Ok(())
}

fn run_interestingness_test(test_path: &Path) {
    announce_progress("Running interestingness test");
    let status = Command::new(test_path).status().unwrap();
    announce_progress(&format!(
        "Have run interestingness test - result is {:?}",
        status.code()
    ));
}

fn create_rs_file(rs_path: &Path, directives: &[String]) -> Result<(), std::io::Error> {
    announce_progress("Creating Rust input file");
    let mut file = File::create(rs_path)?;
    file.write_all("use autocxx::include_cpp;\ninclude_cpp! (\n".as_bytes())?;
    for directive in directives {
        file.write_all(directive.as_bytes())?;
    }
    file.write_all(");\n".as_bytes())?;
    Ok(())
}

fn create_concatenated_header(headers: &[&str], listing_path: &Path) -> Result<(), std::io::Error> {
    announce_progress("Creating preprocessed header");
    let mut file = File::create(listing_path)?;
    for header in headers {
        file.write_all(format!("#include \"{}\"\n", header).as_bytes())?;
    }
    Ok(())
}
<commit_msg>Fix interestingness test for modern header name.<commit_after>//! Crate to `#include` C++ headers in your Rust code, and generate
//! idiomatic bindings using `cxx`. See [include_cpp] for details.

// 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 std::{
    fs::File,
    io::Write,
    os::unix::prelude::PermissionsExt,
    path::{Path, PathBuf},
    process::Command,
};

use autocxx_engine::preprocess;
use clap::{crate_authors, crate_version, App, Arg, ArgMatches};
use indoc::indoc;
use itertools::Itertools;
use tempfile::TempDir;

static LONG_HELP: &str = indoc! {"
Command line utility to minimize autocxx bug cases.

This is a wrapper for creduce.

Example command-line:
autocxx-reduce -I my-inc-dir -h my-header -d 'generate!(\"MyClass\")' -k -- --n 64
"};

fn main() {
    let matches = App::new("autocxx-reduce")
        .version(crate_version!())
        .author(crate_authors!())
        .about("Reduce a C++ test case")
        .long_about(LONG_HELP)
        .arg(
            Arg::with_name("inc")
                .short("I")
                .long("inc")
                .multiple(true)
                .number_of_values(1)
                .value_name("INCLUDE DIRS")
                .help("include path")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("define")
                .short("D")
                .long("define")
                .multiple(true)
                .number_of_values(1)
                .value_name("DEFINE")
                .help("macro definition")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("header")
                .short("h")
                .long("header")
                .multiple(true)
                .number_of_values(1)
                .required(true)
                .value_name("HEADER")
                .help("header file name")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("directive")
                .short("d")
                .long("directive")
                .multiple(true)
                .number_of_values(1)
                .value_name("DIRECTIVE")
                .help("directives to put within include_cpp!")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("problem")
                .short("p")
                .long("problem")
                .required(true)
                .value_name("PROBLEM")
                .help("problem string we're looking for... may be in logs, or in generated C++, or generated .rs")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("creduce")
                .long("creduce")
                .required(true)
                .value_name("PATH")
                .help("creduce binary location")
                .default_value("/usr/bin/creduce")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("output")
                .short("o")
                .long("output")
                .value_name("OUTPUT")
                .help("where to write minimized output")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("gen-cmd")
                .short("g")
                .long("gen-cmd")
                .value_name("GEN-CMD")
                .help("where to find autocxx-gen")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("keep")
                .short("k")
                .long("keep-dir")
                .help("keep the temporary directory for debugging purposes"),
        )
        .arg(
            Arg::with_name("clang-args")
                .short("c")
                .long("clang-arg")
                .multiple(true)
                .value_name("CLANG_ARG")
                .help("Extra arguments to pass to Clang"),
        )
        .arg(Arg::with_name("creduce-args").last(true).multiple(true))
        .get_matches();
    run(matches).unwrap();
}

fn run(matches: ArgMatches) -> Result<(), std::io::Error> {
    let keep_tmp = matches.is_present("keep");
    let tmp_dir = TempDir::new()?;
    let r = do_run(matches, &tmp_dir);
    if keep_tmp {
        println!(
            "Keeping temp dir created at: {}",
            tmp_dir.into_path().to_str().unwrap()
        );
    }
    r
}

fn do_run(matches: ArgMatches, tmp_dir: &TempDir) -> Result<(), std::io::Error> {
    let incs: Vec<_> = matches
        .values_of("inc")
        .unwrap_or_default()
        .map(PathBuf::from)
        .collect();
    let defs: Vec<_> = matches.values_of("define").unwrap_or_default().collect();
    let headers: Vec<_> = matches.values_of("header").unwrap_or_default().collect();
    let listing_path = tmp_dir.path().join("listing.h");
    create_concatenated_header(&headers, &listing_path)?;
    let concat_path = tmp_dir.path().join("concat.h");
    announce_progress(&format!(
        "Preprocessing {:?} to {:?}",
        listing_path, concat_path
    ));
    preprocess(&listing_path, &concat_path, &incs, &defs)?;
    let rs_path = tmp_dir.path().join("input.rs");
    let directives: Vec<_> = std::iter::once("#include \"concat.h\"\n".to_string())
        .chain(
            matches
                .values_of("directive")
                .unwrap_or_default()
                .map(|s| format!("{}\n", s)),
        )
        .collect();
    create_rs_file(&rs_path, &directives)?;
    let extra_clang_args: Vec<_> = matches
        .values_of("clang-args")
        .unwrap_or_default()
        .collect();
    let default_gen_cmd = std::env::current_exe()?
        .parent()
        .unwrap()
        .join("autocxx-gen")
        .to_str()
        .unwrap()
        .to_string();
    let gen_cmd = matches.value_of("gen-cmd").unwrap_or(&default_gen_cmd);
    run_sample_gen_cmd(gen_cmd, &rs_path, &tmp_dir.path(), &extra_clang_args)?;
    let interestingness_test = tmp_dir.path().join("test.sh");
    create_interestingness_test(
        gen_cmd,
        &interestingness_test,
        matches.value_of("problem").unwrap(),
        &rs_path,
        &extra_clang_args,
    )?;
    run_interestingness_test(&interestingness_test);
    run_creduce(
        matches.value_of("creduce").unwrap(),
        &interestingness_test,
        &concat_path,
        matches.values_of("creduce-args").unwrap_or_default(),
    );
    let output_path = matches.value_of("output");
    match output_path {
        None => print_minimized_case(&concat_path)?,
        Some(output_path) => {
            std::fs::copy(&concat_path, &PathBuf::from(output_path))?;
        }
    };
    Ok(())
}

fn announce_progress(msg: &str) {
    println!("=== {} ===", msg);
}

fn print_minimized_case(concat_path: &Path) -> Result<(), std::io::Error> {
    announce_progress("Completed. Minimized test case:");
    let contents = std::fs::read_to_string(concat_path)?;
    println!("{}", contents);
    Ok(())
}

/// Arguments we always pass to creduce. This pass always seems to cause a crash
/// as far as I can tell, so always exclude it. It may be environment-dependent,
/// of course, but as I'm the primary user of this tool I am ruthlessly removing it.
const CREDUCE_STANDARD_ARGS: &[&str] = &["--remove-pass", "pass_line_markers", "*"];

fn run_creduce<'a>(
    creduce_cmd: &str,
    interestingness_test: &'a Path,
    concat_path: &'a Path,
    creduce_args: impl Iterator<Item = &'a str>,
) {
    announce_progress("creduce");
    let args = std::iter::once(interestingness_test.to_str().unwrap())
        .chain(std::iter::once(concat_path.to_str().unwrap()))
        .chain(CREDUCE_STANDARD_ARGS.iter().copied())
        .chain(creduce_args)
        .collect::<Vec<_>>();
    println!("Command: {} {}", creduce_cmd, args.join(" "));
    Command::new(creduce_cmd)
        .args(args)
        .status()
        .expect("failed to creduce");
}

fn run_sample_gen_cmd(
    gen_cmd: &str,
    rs_file: &Path,
    tmp_dir: &Path,
    extra_clang_args: &[&str],
) -> Result<(), std::io::Error> {
    let args = format_gen_cmd(rs_file, tmp_dir.to_str().unwrap(), extra_clang_args);
    let args = args.collect::<Vec<_>>();
    let args_str = args.join(" ");
    announce_progress(&format!("Running sample gen cmd: {} {}", gen_cmd, args_str));
    Command::new(gen_cmd).args(args).status()?;
    Ok(())
}

fn format_gen_cmd<'a>(
    rs_file: &Path,
    dir: &str,
    extra_clang_args: &'a [&str],
) -> impl Iterator<Item = String> + 'a {
    let args = [
        "-o".to_string(),
        dir.to_string(),
        "-I".to_string(),
        dir.to_string(),
        rs_file.to_str().unwrap().to_string(),
        "--gen-rs-complete".to_string(),
        "--gen-cpp".to_string(),
        "--".to_string(),
    ]
    .to_vec();
    args.into_iter()
        .chain(extra_clang_args.iter().map(|s| s.to_string()))
}

fn create_interestingness_test(
    gen_cmd: &str,
    test_path: &Path,
    problem: &str,
    rs_file: &Path,
    extra_clang_args: &[&str],
) -> Result<(), std::io::Error> {
    announce_progress("Creating interestingness test");
    // Ensure we refer to the input header by relative path
    // because creduce will invoke us in some other directory with
    // a copy thereof.
    let mut args = format_gen_cmd(rs_file, "$(pwd)", extra_clang_args);
    let args = args.join(" ");
    let content = format!(
        indoc! {"
        #!/bin/sh
        ({} {} 2>&1 && cat gen.complete.rs && cat autocxxgen*.h) | grep \"{}\"  >/dev/null 2>&1
    "},
        gen_cmd, args, problem
    );
    println!("Interestingness test:\n{}", content);
    {
        let mut file = File::create(test_path)?;
        file.write_all(content.as_bytes())?;
    }

    let mut perms = std::fs::metadata(&test_path)?.permissions();
    perms.set_mode(0o700);
    std::fs::set_permissions(&test_path, perms)?;
    Ok(())
}

fn run_interestingness_test(test_path: &Path) {
    announce_progress("Running interestingness test");
    let status = Command::new(test_path).status().unwrap();
    announce_progress(&format!(
        "Have run interestingness test - result is {:?}",
        status.code()
    ));
}

fn create_rs_file(rs_path: &Path, directives: &[String]) -> Result<(), std::io::Error> {
    announce_progress("Creating Rust input file");
    let mut file = File::create(rs_path)?;
    file.write_all("use autocxx::include_cpp;\ninclude_cpp! (\n".as_bytes())?;
    for directive in directives {
        file.write_all(directive.as_bytes())?;
    }
    file.write_all(");\n".as_bytes())?;
    Ok(())
}

fn create_concatenated_header(headers: &[&str], listing_path: &Path) -> Result<(), std::io::Error> {
    announce_progress("Creating preprocessed header");
    let mut file = File::create(listing_path)?;
    for header in headers {
        file.write_all(format!("#include \"{}\"\n", header).as_bytes())?;
    }
    Ok(())
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate clap;
extern crate env_logger;
extern crate failure;
extern crate log;
extern crate pb_rs;

use clap::{App, Arg};
use pb_rs::types::FileDescriptor;
use pb_rs::ConfigBuilder;
use std::path::{Path, PathBuf};

fn run() -> Result<(), ::failure::Error> {
    let matches = App::new(crate_name!())
        .about(crate_description!())
        .author(crate_authors!("\n"))
        .version(crate_version!())
        .arg(
            Arg::with_name("OUTPUT")
                .required(false)
                .long("output")
                .short("o")
                .takes_value(true)
                .help("Generated file name, defaults to INPUT with 'rs' extension, cannot be used with --output_directory")
                .validator(|x| extension_matches(x, "rs")),
        ).arg(
            Arg::with_name("OUTPUT_DIR")
                .required(false)
                .long("output_directory")
                .short("d")
                .takes_value(true)
                .help("Output directory of generated code, cannot be used with --output"),
        ).arg(
            Arg::with_name("INCLUDE_PATH")
                .required(false)
                .long("include")
                .short("I")
                .takes_value(true)
                .help("Path to search for imported protobufs"),
        ).arg(
            Arg::with_name("SINGLE_MOD")
                .required(false)
                .long("single-mod")
                .short("s")
                .help("Omit generation of modules for each package when there is only one package"),
        ).arg(
            Arg::with_name("NO_OUTPUT")
                .required(false)
                .long("no-output")
                .short("n")
                .help(
                    "Show enums and messages in this .proto file, including those imported. \
                     No code generated",
                ),
        ).arg(
            Arg::with_name("INPUT")
                .multiple(true)
                .help("The .proto files used to generate quick-protobuf code")
                .validator(|x| extension_matches(x, "proto")),
        ).arg(
            Arg::with_name("CYCLE")
                .long("error-cycle")
                .short("e")
                .required(false)
                .help("Error out if recursive messages do not have optional fields"),
        ).arg(
            Arg::with_name("HEADERS")
                .long("no-headers")
                .short("H")
                .required(false)
                .help("Do not add module comments and module attributes in generated file"),
        ).arg(
            Arg::with_name("CUSTOM_STRUCT_DERIVE")
                .long("custom_struct_derive")
                .short("C")
                .required(false)
                .help("The comma separated values to add to #[derive(...)] for every struct"),
        ).arg(
            Arg::with_name("DONT_USE_COW")
                .required(false)
                .long("dont_use_cow")
                .short("c")
                .help("Don't use Cow for String and Byte types"),
        ).get_matches();

    let in_files = path_vec(values_t!(matches, "INPUT", String));
    let include_paths = path_vec(values_t!(matches, "INCLUDE_PATH", String));
    let out_file = matches.value_of("OUTPUT").map(|o| PathBuf::from(o));
    let out_dir = matches.value_of("OUTPUT_DIR").map(|o| PathBuf::from(o));
    let custom_struct_derive: Vec<String> = matches.value_of("CUSTOM_STRUCT_DERIVE").unwrap_or("")
        .split(",").map(|s| s.to_string()).collect();

    let compiler = ConfigBuilder::new(
        &in_files,
        out_file.as_ref(),
        out_dir.as_ref(),
        &include_paths,
    )?
    .single_module(matches.is_present("SINGLE_MOD"))
    .no_output(matches.is_present("NO_OUTPUT"))
    .error_cycle(matches.is_present("CYCLE"))
    .headers(!matches.is_present("HEADERS"))
    .dont_use_cow(matches.is_present("DONT_USE_COW"))
    .custom_struct_derive(custom_struct_derive);

    FileDescriptor::run(&compiler.build()).map_err(|e| e.into())
}

fn extension_matches<P: AsRef<Path>>(path: P, expected: &str) -> std::result::Result<(), String> {
    match path.as_ref().extension() {
        Some(x) if x == expected => Ok(()),
        Some(x) => Err(format!(
            "Expected path with extension '{}', not: '{}'",
            expected,
            x.to_string_lossy()
        )),
        None => Err(format!("Expected path with extension '{}'", expected)),
    }
}

fn path_vec(maybe_vec: std::result::Result<Vec<String>, clap::Error>) -> Vec<PathBuf> {
    maybe_vec
        .unwrap_or_else(|_| Vec::new())
        .iter()
        .map(|s| s.into())
        .collect()
}

fn main() {
    env_logger::init();
    ::std::process::exit({
        if let Err(e) = run() {
            eprintln!("pb-rs fatal error");
            for e in e.iter_chain() {
                eprintln!("  - {}", e);
            }
            1
        } else {
            0
        }
    });
}
<commit_msg>cmd line now uses -D for not using cow<commit_after>#[macro_use]
extern crate clap;
extern crate env_logger;
extern crate failure;
extern crate log;
extern crate pb_rs;

use clap::{App, Arg};
use pb_rs::types::FileDescriptor;
use pb_rs::ConfigBuilder;
use std::path::{Path, PathBuf};

fn run() -> Result<(), ::failure::Error> {
    let matches = App::new(crate_name!())
        .about(crate_description!())
        .author(crate_authors!("\n"))
        .version(crate_version!())
        .arg(
            Arg::with_name("OUTPUT")
                .required(false)
                .long("output")
                .short("o")
                .takes_value(true)
                .help("Generated file name, defaults to INPUT with 'rs' extension, cannot be used with --output_directory")
                .validator(|x| extension_matches(x, "rs")),
        ).arg(
            Arg::with_name("OUTPUT_DIR")
                .required(false)
                .long("output_directory")
                .short("d")
                .takes_value(true)
                .help("Output directory of generated code, cannot be used with --output"),
        ).arg(
            Arg::with_name("INCLUDE_PATH")
                .required(false)
                .long("include")
                .short("I")
                .takes_value(true)
                .help("Path to search for imported protobufs"),
        ).arg(
            Arg::with_name("SINGLE_MOD")
                .required(false)
                .long("single-mod")
                .short("s")
                .help("Omit generation of modules for each package when there is only one package"),
        ).arg(
            Arg::with_name("NO_OUTPUT")
                .required(false)
                .long("no-output")
                .short("n")
                .help(
                    "Show enums and messages in this .proto file, including those imported. \
                     No code generated",
                ),
        ).arg(
            Arg::with_name("INPUT")
                .multiple(true)
                .help("The .proto files used to generate quick-protobuf code")
                .validator(|x| extension_matches(x, "proto")),
        ).arg(
            Arg::with_name("CYCLE")
                .long("error-cycle")
                .short("e")
                .required(false)
                .help("Error out if recursive messages do not have optional fields"),
        ).arg(
            Arg::with_name("HEADERS")
                .long("no-headers")
                .short("H")
                .required(false)
                .help("Do not add module comments and module attributes in generated file"),
        ).arg(
            Arg::with_name("CUSTOM_STRUCT_DERIVE")
                .long("custom_struct_derive")
                .short("C")
                .required(false)
                .help("The comma separated values to add to #[derive(...)] for every struct"),
        ).arg(
            Arg::with_name("DONT_USE_COW")
                .required(false)
                .long("dont_use_cow")
                .short("D")
                .help("Don't use Cow for String and Byte types"),
        ).get_matches();

    let in_files = path_vec(values_t!(matches, "INPUT", String));
    let include_paths = path_vec(values_t!(matches, "INCLUDE_PATH", String));
    let out_file = matches.value_of("OUTPUT").map(|o| PathBuf::from(o));
    let out_dir = matches.value_of("OUTPUT_DIR").map(|o| PathBuf::from(o));
    let custom_struct_derive: Vec<String> = matches.value_of("CUSTOM_STRUCT_DERIVE").unwrap_or("")
        .split(",").map(|s| s.to_string()).collect();

    let compiler = ConfigBuilder::new(
        &in_files,
        out_file.as_ref(),
        out_dir.as_ref(),
        &include_paths,
    )?
    .single_module(matches.is_present("SINGLE_MOD"))
    .no_output(matches.is_present("NO_OUTPUT"))
    .error_cycle(matches.is_present("CYCLE"))
    .headers(!matches.is_present("HEADERS"))
    .dont_use_cow(matches.is_present("DONT_USE_COW"))
    .custom_struct_derive(custom_struct_derive);

    FileDescriptor::run(&compiler.build()).map_err(|e| e.into())
}

fn extension_matches<P: AsRef<Path>>(path: P, expected: &str) -> std::result::Result<(), String> {
    match path.as_ref().extension() {
        Some(x) if x == expected => Ok(()),
        Some(x) => Err(format!(
            "Expected path with extension '{}', not: '{}'",
            expected,
            x.to_string_lossy()
        )),
        None => Err(format!("Expected path with extension '{}'", expected)),
    }
}

fn path_vec(maybe_vec: std::result::Result<Vec<String>, clap::Error>) -> Vec<PathBuf> {
    maybe_vec
        .unwrap_or_else(|_| Vec::new())
        .iter()
        .map(|s| s.into())
        .collect()
}

fn main() {
    env_logger::init();
    ::std::process::exit({
        if let Err(e) = run() {
            eprintln!("pb-rs fatal error");
            for e in e.iter_chain() {
                eprintln!("  - {}", e);
            }
            1
        } else {
            0
        }
    });
}
<|endoftext|>


<commit_before>use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
use dom::bindings::utils::Fallible;
use dom::bindings::codegen::CanvasRenderingContext2DBinding;
use dom::window::Window;
use js::jsapi::{JSContext, JSObject};
use dom::bindings::utils::{ErrorResult};

pub struct CanvasRenderingContext2D {                           //struct containing Reflector object.
    reflector_: Reflector,
    window: @mut Window,
}

impl CanvasRenderingContext2D {                                 //CanvasRenderingContext2D interface.
    pub fn new_inherited(window: @mut Window) -> CanvasRenderingContext2D {
        CanvasRenderingContext2D {
            reflector_: Reflector::new(),
            window: window,
        }
    }

    pub fn new(window: @mut Window) -> @mut CanvasRenderingContext2D {
        reflect_dom_object(@mut CanvasRenderingContext2D::new_inherited(window), window, CanvasRenderingContext2DBinding::Wrap)
    }

 pub fn Width(&self) -> u32 {                    //Width of the canvas
        0
    }

    pub fn SetWidth(&mut self, _width: u32) -> ErrorResult {
        Ok(())
    }

    pub fn Height(&self) -> u32 {                 //Height of the canvas
        0
    }

    pub fn SetHeight(&mut self, _height: u32) -> ErrorResult {
        Ok(())
    }

}

impl CanvasRenderingContext2D {
    pub fn Constructor(window: @mut Window) -> Fallible<@mut CanvasRenderingContext2D> {
        Ok(CanvasRenderingContext2D::new(window))
    }
}

impl Reflectable for CanvasRenderingContext2D {           //implementing the Reflectable.
    fn reflector<'a>(&'a self) -> &'a Reflector {
        &self.reflector_
    }

    fn mut_reflector<'a>(&'a mut self) -> &'a mut Reflector {
        &mut self.reflector_
    }

    fn wrap_object_shared(@mut self, _cx: *JSContext, _scope: *JSObject) -> *JSObject {
        unreachable!();
    }

    fn GetParentObject(&self, _cx: *JSContext) -> Option<@mut Reflectable> {
        Some(self.window as @mut Reflectable)
    }

}
<commit_msg>Update canvasrenderingcontext2d.rs<commit_after>use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
use dom::bindings::utils::Fallible;
use dom::bindings::codegen::CanvasRenderingContext2DBinding;
use dom::window::Window;
use js::jsapi::{JSContext, JSObject};
use dom::bindings::utils::{ErrorResult};

pub struct CanvasRenderingContext2D {                           //struct containing Reflector object.
    reflector_: Reflector,
    window: @mut Window,
}

impl CanvasRenderingContext2D {                                 //CanvasRenderingContext2D interface.
    pub fn new_inherited(window: @mut Window) -> CanvasRenderingContext2D { //creating window and reflector objects.
        CanvasRenderingContext2D {
            reflector_: Reflector::new(),
            window: window,
        }
    }

    pub fn new(window: @mut Window) -> @mut CanvasRenderingContext2D {
        reflect_dom_object(@mut CanvasRenderingContext2D::new_inherited(window), window, CanvasRenderingContext2DBinding::Wrap)
    }

 pub fn Width(&self) -> u32 {                    //Width of the canvas
        0
    }

    pub fn SetWidth(&mut self, _width: u32) -> ErrorResult {
        Ok(())
    }

    pub fn Height(&self) -> u32 {                 //Height of the canvas
        0
    }

    pub fn SetHeight(&mut self, _height: u32) -> ErrorResult {
        Ok(())
    }

}

impl CanvasRenderingContext2D {
    pub fn Constructor(window: @mut Window) -> Fallible<@mut CanvasRenderingContext2D> {
        Ok(CanvasRenderingContext2D::new(window))
    }
}

impl Reflectable for CanvasRenderingContext2D {           //implementing the Reflectable.
    fn reflector<'a>(&'a self) -> &'a Reflector {
        &self.reflector_
    }

    fn mut_reflector<'a>(&'a mut self) -> &'a mut Reflector {
        &mut self.reflector_
    }

    fn wrap_object_shared(@mut self, _cx: *JSContext, _scope: *JSObject) -> *JSObject {
        unreachable!();
    }

    fn GetParentObject(&self, _cx: *JSContext) -> Option<@mut Reflectable> {
        Some(self.window as @mut Reflectable)
    }

}
<|endoftext|>


<commit_before>// Extracting metadata from crate files

import driver::session;
import session::session;
import syntax::{ast, ast_util};
import lib::llvm::{False, llvm, mk_object_file, mk_section_iter};
import syntax::attr;
import syntax::visit;
import syntax::codemap::span;
import util::{filesearch};
import io::writer_util;
import std::map::{hashmap, int_hash};
import syntax::print::pprust;
import common::*;

export read_crates;
export list_file_metadata;

// Traverses an AST, reading all the information about use'd crates and native
// libraries necessary for later resolving, typechecking, linking, etc.
fn read_crates(sess: session::session, crate: ast::crate) {
    let e = @{sess: sess,
              mut crate_cache: [],
              mut next_crate_num: 1};
    let v =
        visit::mk_simple_visitor(@{visit_view_item:
                                       bind visit_view_item(e, _),
                                   visit_item: bind visit_item(e, _)
                                      with *visit::default_simple_visitor()});
    visit::visit_crate(crate, (), v);
}

type env = @{sess: session::session,
             mut crate_cache: [(int, @[@ast::meta_item])],
             mut next_crate_num: ast::crate_num};

fn visit_view_item(e: env, i: @ast::view_item) {
    alt i.node {
      ast::view_item_use(ident, meta_items, id) {
        let cnum = resolve_crate(e, ident, meta_items, i.span);
        cstore::add_use_stmt_cnum(e.sess.cstore, id, cnum);
      }
      _ { }
    }
}

fn visit_item(e: env, i: @ast::item) {
    alt i.node {
      ast::item_native_mod(m) {
        alt attr::native_abi(i.attrs) {
          either::right(abi) {
            if abi != ast::native_abi_cdecl &&
               abi != ast::native_abi_stdcall { ret; }
          }
          either::left(msg) { e.sess.span_fatal(i.span, msg); }
        }

        let cstore = e.sess.cstore;
        let native_name =
            alt attr::get_meta_item_value_str_by_name(i.attrs, "link_name") {
              some(nn) {
                if nn == "" {
                    e.sess.span_fatal(
                        i.span,
                        "empty #[link_name] not allowed; use #[nolink].");
                }
                nn
              }
              none { i.ident }
            };
        let mut already_added = false;
        if vec::len(attr::find_attrs_by_name(i.attrs, "nolink")) == 0u {
            already_added = !cstore::add_used_library(cstore, native_name);
        }
        let link_args = attr::find_attrs_by_name(i.attrs, "link_args");
        if vec::len(link_args) > 0u && already_added {
            e.sess.span_fatal(i.span, "library '" + native_name +
                              "' already added: can't specify link_args.");
        }
        for a: ast::attribute in link_args {
            alt attr::get_meta_item_value_str(attr::attr_meta(a)) {
              some(linkarg) {
                cstore::add_used_link_args(cstore, linkarg);
              }
              none {/* fallthrough */ }
            }
        }
      }
      _ { }
    }
}

// A diagnostic function for dumping crate metadata to an output stream
fn list_file_metadata(sess: session::session, path: str, out: io::writer) {
    alt get_metadata_section(sess, path) {
      option::some(bytes) { decoder::list_crate_metadata(bytes, out); }
      option::none {
        out.write_str("could not find metadata in " + path + ".\n");
      }
    }
}

fn crate_matches(crate_data: @[u8], metas: [@ast::meta_item]) -> bool {
    let attrs = decoder::get_crate_attributes(crate_data);
    let linkage_metas = attr::find_linkage_metas(attrs);
    metadata_matches(linkage_metas, metas)
}

fn metadata_matches(extern_metas: [@ast::meta_item],
                    local_metas: [@ast::meta_item]) -> bool {

    #debug("matching %u metadata requirements against %u items",
           vec::len(local_metas), vec::len(extern_metas));

    #debug("crate metadata:");
    for have: @ast::meta_item in extern_metas {
        #debug("  %s", pprust::meta_item_to_str(*have));
    }

    for needed: @ast::meta_item in local_metas {
        #debug("looking for %s", pprust::meta_item_to_str(*needed));
        if !attr::contains(extern_metas, needed) {
            #debug("missing %s", pprust::meta_item_to_str(*needed));
            ret false;
        }
    }
    ret true;
}

fn default_native_lib_naming(sess: session::session, static: bool) ->
   {prefix: str, suffix: str} {
    if static { ret {prefix: "lib", suffix: ".rlib"}; }
    alt sess.targ_cfg.os {
      session::os_win32 { ret {prefix: "", suffix: ".dll"}; }
      session::os_macos { ret {prefix: "lib", suffix: ".dylib"}; }
      session::os_linux { ret {prefix: "lib", suffix: ".so"}; }
      session::os_freebsd { ret {prefix: "lib", suffix: ".so"}; }
    }
}

fn find_library_crate(sess: session::session, span: span,
                      metas: [@ast::meta_item])
   -> option<{ident: str, data: @[u8]}> {

    attr::require_unique_names(sess.diagnostic(), metas);
    let metas = metas;

    let crate_name =
        {
            let name_items = attr::find_meta_items_by_name(metas, "name");
            alt vec::last_opt(name_items) {
              some(i) {
                alt attr::get_meta_item_value_str(i) {
                  some(n) { n }
                  // FIXME: Probably want a warning here since the user
                  // is using the wrong type of meta item
                  _ { fail }
                }
              }
              none { fail }
            }
        };

    let nn = default_native_lib_naming(sess, sess.opts.static);
    let x =
        find_library_crate_aux(sess, span, nn, crate_name,
                               metas, sess.filesearch);
    if x != none || sess.opts.static { ret x; }
    let nn2 = default_native_lib_naming(sess, true);
    ret find_library_crate_aux(sess, span, nn2, crate_name, metas,
                               sess.filesearch);
}

fn find_library_crate_aux(sess: session::session,
                          span: span,
                          nn: {prefix: str, suffix: str},
                          crate_name: str,
                          metas: [@ast::meta_item],
                          filesearch: filesearch::filesearch) ->
   option<{ident: str, data: @[u8]}> {
    let prefix: str = nn.prefix + crate_name + "-";
    let suffix: str = nn.suffix;

    let mut matches = [];
    filesearch::search(filesearch, { |path|
        #debug("inspecting file %s", path);
        let f: str = path::basename(path);
        if !(str::starts_with(f, prefix) && str::ends_with(f, suffix)) {
            #debug("skipping %s, doesn't look like %s*%s", path, prefix,
                   suffix);
            option::none
        } else {
            #debug("%s is a candidate", path);
            alt get_metadata_section(sess, path) {
              option::some(cvec) {
                if !crate_matches(cvec, metas) {
                    #debug("skipping %s, metadata doesn't match", path);
                    option::none
                } else {
                    #debug("found %s with matching metadata", path);
                    matches += [{ident: path, data: cvec}];
                    option::none
                }
              }
              _ {
                #debug("could not load metadata for %s", path);
                option::none
              }
            }
        }
    });

    if matches.is_empty() {
        none
    } else if matches.len() == 1u {
        some(matches[0])
    } else {
        sess.span_err(
            span, #fmt("multiple matching crates for `%s`", crate_name));
        sess.note("candidates:");
        for matches.each {|match|
            sess.note(#fmt("path: %s", match.ident));
            let attrs = decoder::get_crate_attributes(match.data);
            for attr::find_linkage_attrs(attrs).each {|attr|
                sess.note(#fmt("meta: %s", pprust::attr_to_str(attr)));
            }
        }
        sess.abort_if_errors();
        none
    }
}

fn get_metadata_section(sess: session::session,
                        filename: str) -> option<@[u8]> unsafe {
    let mb = str::as_c_str(filename, {|buf|
        llvm::LLVMRustCreateMemoryBufferWithContentsOfFile(buf)
                                   });
    if mb as int == 0 { ret option::none::<@[u8]>; }
    let of = alt mk_object_file(mb) {
        option::some(of) { of }
        _ { ret option::none::<@[u8]>; }
    };
    let si = mk_section_iter(of.llof);
    while llvm::LLVMIsSectionIteratorAtEnd(of.llof, si.llsi) == False {
        let name_buf = llvm::LLVMGetSectionName(si.llsi);
        let name = unsafe { str::unsafe::from_c_str(name_buf) };
        if str::eq(name, sess.targ_cfg.target_strs.meta_sect_name) {
            let cbuf = llvm::LLVMGetSectionContents(si.llsi);
            let csz = llvm::LLVMGetSectionSize(si.llsi) as uint;
            unsafe {
                let cvbuf: *u8 = unsafe::reinterpret_cast(cbuf);
                ret some(@vec::unsafe::from_buf(cvbuf, csz));
            }
        }
        llvm::LLVMMoveToNextSection(si.llsi);
    }
    ret option::none::<@[u8]>;
}

fn load_library_crate(sess: session::session, ident: ast::ident, span: span,
                      metas: [@ast::meta_item])
   -> {ident: str, data: @[u8]} {

    alt find_library_crate(sess, span, metas) {
      some(t) { ret t; }
      none {
        sess.span_fatal(span, #fmt["can't find crate for '%s'", ident]);
      }
    }
}

fn metas_with_ident(ident: ast::ident,
                    metas: [@ast::meta_item]) -> [@ast::meta_item] {
    let name_items = attr::find_meta_items_by_name(metas, "name");
    if name_items.is_empty() {
        metas + [attr::mk_name_value_item_str("name", ident)]
    } else {
        metas
    }
}

fn existing_match(e: env, metas: [@ast::meta_item]) -> option<int> {
    let maybe_entry = e.crate_cache.find {|c|
        metadata_matches(*tuple::second(c), metas)
    };

    maybe_entry.map {|c| tuple::first(c) }
}

fn resolve_crate(e: env, ident: ast::ident, metas: [@ast::meta_item],
                 span: span) -> ast::crate_num {
    let metas = metas_with_ident(ident, metas);

    alt existing_match(e, metas) {
      none {
        let cinfo =
            load_library_crate(e.sess, ident, span, metas);

        let cfilename = cinfo.ident;
        let cdata = cinfo.data;

        let attrs = decoder::get_crate_attributes(cdata);
        let linkage_metas = attr::find_linkage_metas(attrs);

        // Claim this crate number and cache it
        let cnum = e.next_crate_num;
        e.crate_cache += [(cnum, @linkage_metas)];
        e.next_crate_num += 1;

        // Now resolve the crates referenced by this crate
        let cnum_map = resolve_crate_deps(e, cdata);

        let cmeta = @{name: ident, data: cdata,
                      cnum_map: cnum_map, cnum: cnum};

        let cstore = e.sess.cstore;
        cstore::set_crate_data(cstore, cnum, cmeta);
        cstore::add_used_crate_file(cstore, cfilename);
        ret cnum;
      }
      some(cnum) {
        ret cnum;
      }
    }
}

// Go through the crate metadata and load any crates that it references
fn resolve_crate_deps(e: env, cdata: @[u8]) -> cstore::cnum_map {
    #debug("resolving deps of external crate");
    // The map from crate numbers in the crate we're resolving to local crate
    // numbers
    let cnum_map = int_hash::<ast::crate_num>();
    for dep: decoder::crate_dep in decoder::get_crate_deps(cdata) {
        let extrn_cnum = dep.cnum;
        let cname = dep.ident;
        // FIXME: We really need to know the linkage metas of our transitive
        // dependencies in order to resolve them correctly.
        let cmetas = [];
        #debug("resolving dep %s", cname);
        alt existing_match(e, metas_with_ident(cname, cmetas)) {
          some(local_cnum) {
            #debug("already have it");
            // We've already seen this crate
            cnum_map.insert(extrn_cnum, local_cnum);
          }
          none {
            #debug("need to load it");
            // This is a new one so we've got to load it
            // FIXME: Need better error reporting than just a bogus span
            let fake_span = ast_util::dummy_sp();
            let local_cnum = resolve_crate(e, cname, cmetas, fake_span);
            cnum_map.insert(extrn_cnum, local_cnum);
          }
        }
    }
    ret cnum_map;
}

// Local Variables:
// mode: rust
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// End:
<commit_msg>rustc: Warn when linking to multiple versions of the same crate<commit_after>// Extracting metadata from crate files

import driver::session;
import session::session;
import syntax::{ast, ast_util};
import lib::llvm::{False, llvm, mk_object_file, mk_section_iter};
import syntax::attr;
import syntax::visit;
import syntax::codemap::span;
import util::{filesearch};
import io::writer_util;
import std::map::{hashmap, int_hash};
import syntax::print::pprust;
import common::*;

export read_crates;
export list_file_metadata;

// Traverses an AST, reading all the information about use'd crates and native
// libraries necessary for later resolving, typechecking, linking, etc.
fn read_crates(sess: session::session, crate: ast::crate) {
    let e = @{sess: sess,
              mut crate_cache: [],
              mut next_crate_num: 1};
    let v =
        visit::mk_simple_visitor(@{visit_view_item:
                                       bind visit_view_item(e, _),
                                   visit_item: bind visit_item(e, _)
                                      with *visit::default_simple_visitor()});
    visit::visit_crate(crate, (), v);
    warn_if_multiple_versions(sess, copy e.crate_cache);
}

type cache_entry = {
    cnum: int,
    span: span,
    metas: @[@ast::meta_item]
};

fn warn_if_multiple_versions(sess: session::session,
                             crate_cache: [cache_entry]) {
    import either::*;

    if crate_cache.is_not_empty() {
        let name = crate_name_from_metas(*crate_cache.last().metas);
        let {lefts: matches, rights: non_matches} =
            partition(crate_cache.map {|entry|
                let othername = crate_name_from_metas(*entry.metas);
                if name == othername {
                    left(entry)
                } else {
                    right(entry)
                }
            });

        assert matches.is_not_empty();

        if matches.len() != 1u {
            sess.warn(#fmt("using multiple versions of crate `%s`", name));
            for matches.each {|match|
                sess.span_note(match.span, "used here");
                let attrs = [
                    attr::mk_attr(attr::mk_list_item("link", *match.metas))
                ];
                note_linkage_attrs(sess, attrs);
            }
        }

        warn_if_multiple_versions(sess, non_matches);
    }
}

type env = @{sess: session::session,
             mut crate_cache: [cache_entry],
             mut next_crate_num: ast::crate_num};

fn visit_view_item(e: env, i: @ast::view_item) {
    alt i.node {
      ast::view_item_use(ident, meta_items, id) {
        let cnum = resolve_crate(e, ident, meta_items, i.span);
        cstore::add_use_stmt_cnum(e.sess.cstore, id, cnum);
      }
      _ { }
    }
}

fn visit_item(e: env, i: @ast::item) {
    alt i.node {
      ast::item_native_mod(m) {
        alt attr::native_abi(i.attrs) {
          either::right(abi) {
            if abi != ast::native_abi_cdecl &&
               abi != ast::native_abi_stdcall { ret; }
          }
          either::left(msg) { e.sess.span_fatal(i.span, msg); }
        }

        let cstore = e.sess.cstore;
        let native_name =
            alt attr::get_meta_item_value_str_by_name(i.attrs, "link_name") {
              some(nn) {
                if nn == "" {
                    e.sess.span_fatal(
                        i.span,
                        "empty #[link_name] not allowed; use #[nolink].");
                }
                nn
              }
              none { i.ident }
            };
        let mut already_added = false;
        if vec::len(attr::find_attrs_by_name(i.attrs, "nolink")) == 0u {
            already_added = !cstore::add_used_library(cstore, native_name);
        }
        let link_args = attr::find_attrs_by_name(i.attrs, "link_args");
        if vec::len(link_args) > 0u && already_added {
            e.sess.span_fatal(i.span, "library '" + native_name +
                              "' already added: can't specify link_args.");
        }
        for a: ast::attribute in link_args {
            alt attr::get_meta_item_value_str(attr::attr_meta(a)) {
              some(linkarg) {
                cstore::add_used_link_args(cstore, linkarg);
              }
              none {/* fallthrough */ }
            }
        }
      }
      _ { }
    }
}

// A diagnostic function for dumping crate metadata to an output stream
fn list_file_metadata(sess: session::session, path: str, out: io::writer) {
    alt get_metadata_section(sess, path) {
      option::some(bytes) { decoder::list_crate_metadata(bytes, out); }
      option::none {
        out.write_str("could not find metadata in " + path + ".\n");
      }
    }
}

fn crate_matches(crate_data: @[u8], metas: [@ast::meta_item]) -> bool {
    let attrs = decoder::get_crate_attributes(crate_data);
    let linkage_metas = attr::find_linkage_metas(attrs);
    metadata_matches(linkage_metas, metas)
}

fn metadata_matches(extern_metas: [@ast::meta_item],
                    local_metas: [@ast::meta_item]) -> bool {

    #debug("matching %u metadata requirements against %u items",
           vec::len(local_metas), vec::len(extern_metas));

    #debug("crate metadata:");
    for have: @ast::meta_item in extern_metas {
        #debug("  %s", pprust::meta_item_to_str(*have));
    }

    for needed: @ast::meta_item in local_metas {
        #debug("looking for %s", pprust::meta_item_to_str(*needed));
        if !attr::contains(extern_metas, needed) {
            #debug("missing %s", pprust::meta_item_to_str(*needed));
            ret false;
        }
    }
    ret true;
}

fn default_native_lib_naming(sess: session::session, static: bool) ->
   {prefix: str, suffix: str} {
    if static { ret {prefix: "lib", suffix: ".rlib"}; }
    alt sess.targ_cfg.os {
      session::os_win32 { ret {prefix: "", suffix: ".dll"}; }
      session::os_macos { ret {prefix: "lib", suffix: ".dylib"}; }
      session::os_linux { ret {prefix: "lib", suffix: ".so"}; }
      session::os_freebsd { ret {prefix: "lib", suffix: ".so"}; }
    }
}

fn crate_name_from_metas(metas: [@ast::meta_item]) -> str {
    let name_items = attr::find_meta_items_by_name(metas, "name");
    alt vec::last_opt(name_items) {
      some(i) {
        alt attr::get_meta_item_value_str(i) {
          some(n) { n }
          // FIXME: Probably want a warning here since the user
          // is using the wrong type of meta item
          _ { fail }
        }
      }
      none { fail "expected to find the crate name" }
    }
}

fn find_library_crate(sess: session::session, span: span,
                      metas: [@ast::meta_item])
   -> option<{ident: str, data: @[u8]}> {

    attr::require_unique_names(sess.diagnostic(), metas);
    let metas = metas;
    let crate_name = crate_name_from_metas(metas);

    let nn = default_native_lib_naming(sess, sess.opts.static);
    let x =
        find_library_crate_aux(sess, span, nn, crate_name,
                               metas, sess.filesearch);
    if x != none || sess.opts.static { ret x; }
    let nn2 = default_native_lib_naming(sess, true);
    ret find_library_crate_aux(sess, span, nn2, crate_name, metas,
                               sess.filesearch);
}

fn find_library_crate_aux(sess: session::session,
                          span: span,
                          nn: {prefix: str, suffix: str},
                          crate_name: str,
                          metas: [@ast::meta_item],
                          filesearch: filesearch::filesearch) ->
   option<{ident: str, data: @[u8]}> {
    let prefix: str = nn.prefix + crate_name + "-";
    let suffix: str = nn.suffix;

    let mut matches = [];
    filesearch::search(filesearch, { |path|
        #debug("inspecting file %s", path);
        let f: str = path::basename(path);
        if !(str::starts_with(f, prefix) && str::ends_with(f, suffix)) {
            #debug("skipping %s, doesn't look like %s*%s", path, prefix,
                   suffix);
            option::none
        } else {
            #debug("%s is a candidate", path);
            alt get_metadata_section(sess, path) {
              option::some(cvec) {
                if !crate_matches(cvec, metas) {
                    #debug("skipping %s, metadata doesn't match", path);
                    option::none
                } else {
                    #debug("found %s with matching metadata", path);
                    matches += [{ident: path, data: cvec}];
                    option::none
                }
              }
              _ {
                #debug("could not load metadata for %s", path);
                option::none
              }
            }
        }
    });

    if matches.is_empty() {
        none
    } else if matches.len() == 1u {
        some(matches[0])
    } else {
        sess.span_err(
            span, #fmt("multiple matching crates for `%s`", crate_name));
        sess.note("candidates:");
        for matches.each {|match|
            sess.note(#fmt("path: %s", match.ident));
            let attrs = decoder::get_crate_attributes(match.data);
            note_linkage_attrs(sess, attrs);
        }
        sess.abort_if_errors();
        none
    }
}

fn note_linkage_attrs(sess: session::session, attrs: [ast::attribute]) {
    for attr::find_linkage_attrs(attrs).each {|attr|
        sess.note(#fmt("meta: %s", pprust::attr_to_str(attr)));
    }
}

fn get_metadata_section(sess: session::session,
                        filename: str) -> option<@[u8]> unsafe {
    let mb = str::as_c_str(filename, {|buf|
        llvm::LLVMRustCreateMemoryBufferWithContentsOfFile(buf)
                                   });
    if mb as int == 0 { ret option::none::<@[u8]>; }
    let of = alt mk_object_file(mb) {
        option::some(of) { of }
        _ { ret option::none::<@[u8]>; }
    };
    let si = mk_section_iter(of.llof);
    while llvm::LLVMIsSectionIteratorAtEnd(of.llof, si.llsi) == False {
        let name_buf = llvm::LLVMGetSectionName(si.llsi);
        let name = unsafe { str::unsafe::from_c_str(name_buf) };
        if str::eq(name, sess.targ_cfg.target_strs.meta_sect_name) {
            let cbuf = llvm::LLVMGetSectionContents(si.llsi);
            let csz = llvm::LLVMGetSectionSize(si.llsi) as uint;
            unsafe {
                let cvbuf: *u8 = unsafe::reinterpret_cast(cbuf);
                ret some(@vec::unsafe::from_buf(cvbuf, csz));
            }
        }
        llvm::LLVMMoveToNextSection(si.llsi);
    }
    ret option::none::<@[u8]>;
}

fn load_library_crate(sess: session::session, ident: ast::ident, span: span,
                      metas: [@ast::meta_item])
   -> {ident: str, data: @[u8]} {

    alt find_library_crate(sess, span, metas) {
      some(t) { ret t; }
      none {
        sess.span_fatal(span, #fmt["can't find crate for '%s'", ident]);
      }
    }
}

fn metas_with_ident(ident: ast::ident,
                    metas: [@ast::meta_item]) -> [@ast::meta_item] {
    let name_items = attr::find_meta_items_by_name(metas, "name");
    if name_items.is_empty() {
        metas + [attr::mk_name_value_item_str("name", ident)]
    } else {
        metas
    }
}

fn existing_match(e: env, metas: [@ast::meta_item]) -> option<int> {
    let maybe_entry = e.crate_cache.find {|c|
        metadata_matches(*c.metas, metas)
    };

    maybe_entry.map {|c| c.cnum }
}

fn resolve_crate(e: env, ident: ast::ident, metas: [@ast::meta_item],
                 span: span) -> ast::crate_num {
    let metas = metas_with_ident(ident, metas);

    alt existing_match(e, metas) {
      none {
        let cinfo =
            load_library_crate(e.sess, ident, span, metas);

        let cfilename = cinfo.ident;
        let cdata = cinfo.data;

        let attrs = decoder::get_crate_attributes(cdata);
        let linkage_metas = attr::find_linkage_metas(attrs);

        // Claim this crate number and cache it
        let cnum = e.next_crate_num;
        e.crate_cache += [{cnum: cnum, span: span, metas: @linkage_metas}];
        e.next_crate_num += 1;

        // Now resolve the crates referenced by this crate
        let cnum_map = resolve_crate_deps(e, cdata);

        let cmeta = @{name: ident, data: cdata,
                      cnum_map: cnum_map, cnum: cnum};

        let cstore = e.sess.cstore;
        cstore::set_crate_data(cstore, cnum, cmeta);
        cstore::add_used_crate_file(cstore, cfilename);
        ret cnum;
      }
      some(cnum) {
        ret cnum;
      }
    }
}

// Go through the crate metadata and load any crates that it references
fn resolve_crate_deps(e: env, cdata: @[u8]) -> cstore::cnum_map {
    #debug("resolving deps of external crate");
    // The map from crate numbers in the crate we're resolving to local crate
    // numbers
    let cnum_map = int_hash::<ast::crate_num>();
    for dep: decoder::crate_dep in decoder::get_crate_deps(cdata) {
        let extrn_cnum = dep.cnum;
        let cname = dep.ident;
        // FIXME: We really need to know the linkage metas of our transitive
        // dependencies in order to resolve them correctly.
        let cmetas = [];
        #debug("resolving dep %s", cname);
        alt existing_match(e, metas_with_ident(cname, cmetas)) {
          some(local_cnum) {
            #debug("already have it");
            // We've already seen this crate
            cnum_map.insert(extrn_cnum, local_cnum);
          }
          none {
            #debug("need to load it");
            // This is a new one so we've got to load it
            // FIXME: Need better error reporting than just a bogus span
            let fake_span = ast_util::dummy_sp();
            let local_cnum = resolve_crate(e, cname, cmetas, fake_span);
            cnum_map.insert(extrn_cnum, local_cnum);
          }
        }
    }
    ret cnum_map;
}

// Local Variables:
// mode: rust
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// End:
<|endoftext|>