<commit_before>use ast::Script;
use compile::ExecutableDevEnv;
use run::{ Execution, ExecutionEvent, Error as RunError, StartStopError };

use std::collections::HashMap;
use std::path::{ Path as FilePath, PathBuf as FilePathBuf };

use foxbox_taxonomy::api::ResultMap;
use foxbox_taxonomy::parse::*;
use foxbox_taxonomy::util::{ Id };

use rusqlite;
use transformable_channels::mpsc::{ channel, ExtSender, TransformableSender };

/// A ScriptManager error.
#[derive(Serialize, Debug)]
pub enum Error {
    /// The script you requested (by ID) does not exist.
    NoSuchScriptError,

    /// There was an error executing some SQL.
    SQLError(String),

    /// There was an error attempting to run a script. (See `run.rs`.)
    RunError(RunError),

    /// There was an error parsing the script's JSON.
    ParseError(String),
}

/// A type for ensuring type-safety (Id<ScriptId>).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Hash, Eq)]
pub struct ScriptId;

/// ScriptManager stores a persistent database of scripts and executes them.
/// Each script can be individually enabled or disabled.
/// When a script is enabled, it is always running (unless an error occured during launch).
/// Script sources are stored as JSON strings in a SQLite database.
pub struct ScriptManager<Env, T> where Env: ExecutableDevEnv + Clone + 'static {
    env: Env,

    /// The path to the SQLite file to store, e.g. "./database.sqlite"
    path: FilePathBuf,

    /// A map to track currently-executing scripts.
    runners: HashMap<Id<ScriptId>, Execution<Env>>,

    /// The tx end of the channel passed to ScriptManager::new()
    tx: Box<T>,
}

impl<Env, T> ScriptManager<Env, T>
    where Env: ExecutableDevEnv + Clone + 'static,
          T: ExtSender<(Id<ScriptId>, ExecutionEvent)> + TransformableSender<(Id<ScriptId>, ExecutionEvent)> {

    /// Create a ScriptManager using a SQLite database file with the given path, i.e. filename.
    /// If the database file does not exist, it will be created.
    ///
    /// NOTE: You MUST consume the contents of `tx` to prevent memory leaks.
    ///
    /// The database stores the raw script source, but only after the source has been parsed
    /// to ensure validity.
    pub fn new(env: Env, path: &FilePath, tx: Box<T>) -> Result<Self, Error> {

        let connection = try!(rusqlite::Connection::open(&path));
        try!(connection.execute("CREATE TABLE IF NOT EXISTS scripts (
            id          TEXT NOT NULL PRIMARY KEY,
            source      TEXT NOT NULL,
            is_enabled  BOOL NOT NULL DEFAULT 1
        )", &[]));

        Ok(ScriptManager {
            path: path.to_owned(),
            env: env,
            runners: HashMap::new(),
            tx: tx
        })
    }

    /// Load and launch all existing scripts from the database.
    pub fn load(&mut self) -> Result<ResultMap<Id<ScriptId>, (), Error>, Error> {
        let connection = try!(rusqlite::Connection::open(&self.path));
        let mut result_map = HashMap::new();
        let mut stmt = try!(connection.prepare("SELECT id, source, is_enabled FROM scripts"));
        let rows = try!(stmt.query(&[]));

        for result_row in rows {
            let row = try!(result_row);
            let id_string: String = try!(row.get_checked(0));
            let id: Id<ScriptId> = Id::new(&id_string);
            let source: String = try!(row.get_checked(1));
            let is_enabled: bool = try!(row.get_checked(2));

            if is_enabled {
                result_map.insert(
                    id.clone(),
                    self.start_script(&id, &source).map(|_| ()));
            }
        }
        Ok(result_map)
    }

    /// Attempt to add a new script. The script will be executed and persisted to disk.
    /// The ID is chosen by the consumer and must be unique.
    pub fn put(&mut self, id: &Id<ScriptId>, source: &String) -> Result<(), Error> {
        try!(self.start_script(&id, &source));

        let connection = try!(rusqlite::Connection::open(&self.path));
        connection.execute("INSERT OR REPLACE INTO scripts (id, source, is_enabled)
                VALUES ($1, $2, $3)", &[&id.to_string(), source, &1])
            .map(|_| ()).map_err(From::from)
    }

    /// Enable or disable a script, starting or stopping the script if necessary.
    pub fn set_enabled(&mut self, id: &Id<ScriptId>, enabled: bool) -> Result<(), Error> {
        let source = try!(self.get_source(id));
        let is_running = self.runners.contains_key(&id);
        match (enabled, is_running) {
            (false, true) => {
                if let Some(mut runner) = self.runners.remove(&id) {
                    let (tx, rx) = channel();
                    runner.stop(move |result| {
                        let _ = tx.send(result);
                    });
                    if let Err(_) = rx.recv() {
                        return Err(Error::RunError(RunError::StartStopError(StartStopError::ThreadError)));
                    }
                }

                let connection = try!(rusqlite::Connection::open(&self.path));
                try!(connection.execute("UPDATE scripts SET is_enabled = 0 WHERE id = $1",
                                        &[&id.to_string()]));
            },
            (true, false) => {
                try!(self.start_script(id, &source));
                let connection = try!(rusqlite::Connection::open(&self.path));
                try!(connection.execute("UPDATE scripts SET is_enabled = 1 WHERE id = $1",
                                        &[&id.to_string()]));
            },
            _ => {
                // Nothing to do.
            }
        }

        Ok(())
    }

    /// Remove a script entirely, stopping it if necessary.
    /// If the script cannot be stopped (due to an error), it will not be removed.
    pub fn remove(&mut self, id: &Id<ScriptId>) -> Result<(), Error> {
        try!(self.set_enabled(id, false));
        let connection = try!(rusqlite::Connection::open(&self.path));
        connection.execute("DELETE FROM scripts WHERE id = $1", &[&id.to_string()])
            .map(|_| ())
            .map_err(From::from)
    }

    /// Remove all scripts, stopping any running scripts.
    /// (If any scripts fail to stop, we store and return those errors in a Vec<Error> so that
    /// we ensure that the database always gets wiped.)
    pub fn remove_all(&mut self) -> Result<Vec<Error>, Error> {
        // Get a copy of the keys (so that we don't borow `self` twice).
        let keys: Vec<Id<ScriptId>> = self.runners.keys().cloned().collect();
        let mut errors = Vec::new();
        // Remove any running scripts, storing errors for later return so that we
        // for sure end up nuking the database at the end.
        for id in keys {
            if let Err(err) = self.remove(&id) {
                errors.push(err);
            }
        }
        // Nuke the scripts database.
        let connection = try!(rusqlite::Connection::open(&self.path));
        try!(connection.execute("DELETE FROM scripts", &[])
                .map(|_| ()));
        Ok(errors)
    }

    /// Get the number of currently-running scripts.
    pub fn get_running_count(&self) -> usize {
        self.runners.len()
    }

    /// Get the source for a script with the given id.
    fn get_source(&self, id: &Id<ScriptId>) -> Result<String, Error> {
        let connection = try!(rusqlite::Connection::open(&self.path));
        let mut stmt = try!(connection.prepare("SELECT source FROM scripts WHERE id = $1"));
        let mut rows = try!(stmt.query(&[&id.to_string()]));
        let first_row = try!(try!(rows.nth(0).ok_or(Error::NoSuchScriptError)));
        Ok(try!(first_row.get_checked(0)))
    }

    /// Execute a script. Returns an error if the script is already running,
    /// or it won't parse, or it won't compile.
    fn start_script(&mut self, id: &Id<ScriptId>, source: &String) -> Result<(), Error> {
        if self.runners.contains_key(id) {
            return Err(Error::RunError(RunError::StartStopError(StartStopError::AlreadyRunning)));
        }
        let mut runner = Execution::<Env>::new();
        let tx_id = id.clone();
        let tx = self.tx.map(move |event| {
            (tx_id.clone(), event)
        });
        let parsed_source = try!(Script::from_str(source));
        try!(runner.start(self.env.clone(), parsed_source, tx));
        self.runners.insert(id.clone(), runner);
        Ok(())
    }
}

impl From<rusqlite::Error> for Error {
    fn from(err: rusqlite::Error) -> Error {
        Error::SQLError(format!("{:?}", err))
    }
}

impl From<RunError> for Error {
    fn from(err: RunError) -> Error {
        Error::RunError(err)
    }
}

impl From<ParseError> for Error {
    fn from(err: ParseError) -> Error {
        Error::ParseError(format!("{:?}", err))
    }
}
<commit_msg>[fxbox/thinkerbell] Merge pull request #54 from mcav/think<commit_after>use ast::Script;
use compile::ExecutableDevEnv;
use run::{ Execution, ExecutionEvent, Error as RunError, StartStopError };

use std::collections::HashMap;
use std::path::{ Path as FilePath, PathBuf as FilePathBuf };

use foxbox_taxonomy::api::ResultMap;
use foxbox_taxonomy::parse::*;
use foxbox_taxonomy::util::{ Id };

use rusqlite;
use transformable_channels::mpsc::{ channel, ExtSender, TransformableSender };

/// A ScriptManager error.
#[derive(Serialize, Debug)]
pub enum Error {
    /// The script you requested (by ID) does not exist.
    NoSuchScriptError,

    /// There was an error executing some SQL.
    SQLError(String),

    /// There was an error attempting to run a script. (See `run.rs`.)
    RunError(RunError),

    /// There was an error parsing the script's JSON.
    ParseError(String),
}

/// A type for ensuring type-safety (Id<ScriptId>).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Hash, Eq)]
pub struct ScriptId;

/// ScriptManager stores a persistent database of scripts and executes them.
/// Each script can be individually enabled or disabled.
/// When a script is enabled, it is always running (unless an error occured during launch).
/// Script sources are stored as JSON strings in a SQLite database.
pub struct ScriptManager<Env, T> where Env: ExecutableDevEnv + Clone + 'static {
    env: Env,

    /// The path to the SQLite file to store, e.g. "./database.sqlite"
    path: FilePathBuf,

    /// A map to track currently-executing scripts.
    runners: HashMap<Id<ScriptId>, Execution<Env>>,

    /// The tx end of the channel passed to ScriptManager::new()
    tx: Box<T>,
}

impl<Env, T> ScriptManager<Env, T>
    where Env: ExecutableDevEnv + Clone + 'static,
          T: ExtSender<(Id<ScriptId>, ExecutionEvent)> + TransformableSender<(Id<ScriptId>, ExecutionEvent)> {

    /// Create a ScriptManager using a SQLite database file with the given path, i.e. filename.
    /// If the database file does not exist, it will be created.
    ///
    /// NOTE: You MUST consume the contents of `tx` to prevent memory leaks.
    ///
    /// The database stores the raw script source, but only after the source has been parsed
    /// to ensure validity.
    pub fn new(env: Env, path: &FilePath, tx: Box<T>) -> Result<Self, Error> {

        let connection = try!(rusqlite::Connection::open(&path));
        try!(connection.execute("CREATE TABLE IF NOT EXISTS scripts (
            id          TEXT NOT NULL PRIMARY KEY,
            source      TEXT NOT NULL,
            is_enabled  BOOL NOT NULL DEFAULT 1
        )", &[]));

        Ok(ScriptManager {
            path: path.to_owned(),
            env: env,
            runners: HashMap::new(),
            tx: tx
        })
    }

    /// Load and launch all existing scripts from the database.
    pub fn load(&mut self) -> Result<ResultMap<Id<ScriptId>, (), Error>, Error> {
        let connection = try!(rusqlite::Connection::open(&self.path));
        let mut result_map = HashMap::new();
        let mut stmt = try!(connection.prepare("SELECT id, source, is_enabled FROM scripts"));
        let rows = try!(stmt.query(&[]));

        for result_row in rows {
            let row = try!(result_row);
            let id_string: String = try!(row.get_checked(0));
            let id: Id<ScriptId> = Id::new(&id_string);
            let source: String = try!(row.get_checked(1));
            let is_enabled: bool = try!(row.get_checked(2));

            if is_enabled {
                result_map.insert(
                    id.clone(),
                    self.start_script(&id, &source).map(|_| ()));
            }
        }
        Ok(result_map)
    }

    /// Attempt to add a new script. The script will be executed and persisted to disk.
    /// The ID is chosen by the consumer and must be unique.
    pub fn put(&mut self, id: &Id<ScriptId>, source: &String) -> Result<(), Error> {
        try!(self.start_script(&id, &source));

        let connection = try!(rusqlite::Connection::open(&self.path));
        connection.execute("INSERT OR REPLACE INTO scripts (id, source, is_enabled)
                VALUES ($1, $2, $3)", &[&id.to_string(), source, &1])
            .map(|_| ()).map_err(From::from)
    }

    /// Enable or disable a script, starting or stopping the script if necessary.
    pub fn set_enabled(&mut self, id: &Id<ScriptId>, enabled: bool) -> Result<(), Error> {
        let source = try!(self.get_source(id));
        let is_running = self.runners.contains_key(&id);
        match (enabled, is_running) {
            (false, true) => {
                if let Some(mut runner) = self.runners.remove(&id) {
                    let (tx, rx) = channel();
                    runner.stop(move |result| {
                        let _ = tx.send(result);
                    });
                    if let Err(_) = rx.recv() {
                        return Err(Error::RunError(RunError::StartStopError(StartStopError::ThreadError)));
                    }
                }

                let connection = try!(rusqlite::Connection::open(&self.path));
                try!(connection.execute("UPDATE scripts SET is_enabled = 0 WHERE id = $1",
                                        &[&id.to_string()]));
            },
            (true, false) => {
                try!(self.start_script(id, &source));
                let connection = try!(rusqlite::Connection::open(&self.path));
                try!(connection.execute("UPDATE scripts SET is_enabled = 1 WHERE id = $1",
                                        &[&id.to_string()]));
            },
            _ => {
                // Nothing to do.
            }
        }

        Ok(())
    }

    /// Remove a script entirely, stopping it if necessary.
    /// If the script cannot be stopped (due to an error), it will not be removed.
    pub fn remove(&mut self, id: &Id<ScriptId>) -> Result<(), Error> {
        try!(self.set_enabled(id, false));
        let connection = try!(rusqlite::Connection::open(&self.path));
        connection.execute("DELETE FROM scripts WHERE id = $1", &[&id.to_string()])
            .map(|_| ())
            .map_err(From::from)
    }

    /// Remove all scripts, stopping any running scripts.
    /// (If any scripts fail to stop, we store and return those errors in a Vec<Error> so that
    /// we ensure that the database always gets wiped.)
    pub fn remove_all(&mut self) -> Result<Vec<Error>, Error> {
        // Get a copy of the keys (so that we don't borow `self` twice).
        let keys: Vec<Id<ScriptId>> = self.runners.keys().cloned().collect();
        let mut errors = Vec::new();
        // Remove any running scripts, storing errors for later return so that we
        // for sure end up nuking the database at the end.
        for id in keys {
            if let Err(err) = self.remove(&id) {
                errors.push(err);
            }
        }
        // Nuke the scripts database.
        let connection = try!(rusqlite::Connection::open(&self.path));
        try!(connection.execute("DELETE FROM scripts", &[])
                .map(|_| ()));
        Ok(errors)
    }

    /// Get the number of currently-running scripts.
    pub fn get_running_count(&self) -> usize {
        self.runners.len()
    }

    /// Get the source for a script with the given id.
    fn get_source(&self, id: &Id<ScriptId>) -> Result<String, Error> {
        let connection = try!(rusqlite::Connection::open(&self.path));
        let mut stmt = try!(connection.prepare("SELECT source FROM scripts WHERE id = $1"));
        let mut rows = try!(stmt.query(&[&id.to_string()]));
        let first_row = try!(try!(rows.nth(0).ok_or(Error::NoSuchScriptError)));
        Ok(try!(first_row.get_checked(0)))
    }

    /// Return true if the script is enabled.
    pub fn is_enabled(&self, id: &Id<ScriptId>) -> bool {
        self.runners.contains_key(id)
    }

    /// Execute a script. Returns an error if the script is already running,
    /// or it won't parse, or it won't compile.
    fn start_script(&mut self, id: &Id<ScriptId>, source: &String) -> Result<(), Error> {
        if self.runners.contains_key(id) {
            return Err(Error::RunError(RunError::StartStopError(StartStopError::AlreadyRunning)));
        }
        let mut runner = Execution::<Env>::new();
        let tx_id = id.clone();
        let tx = self.tx.map(move |event| {
            (tx_id.clone(), event)
        });
        let parsed_source = try!(Script::from_str(source));
        try!(runner.start(self.env.clone(), parsed_source, tx));
        self.runners.insert(id.clone(), runner);
        Ok(())
    }
}

impl From<rusqlite::Error> for Error {
    fn from(err: rusqlite::Error) -> Error {
        Error::SQLError(format!("{:?}", err))
    }
}

impl From<RunError> for Error {
    fn from(err: RunError) -> Error {
        Error::RunError(err)
    }
}

impl From<ParseError> for Error {
    fn from(err: ParseError) -> Error {
        Error::ParseError(format!("{:?}", err))
    }
}
<|endoftext|>


<commit_before>extern crate lwnvulns;
extern crate scraper;
extern crate curl;

use lwnvulns::tokenize::{tokenize, Token, Header, Issue, SourceLink};
use lwnvulns::parse::parse;
use lwnvulns::writer::write;
use lwnvulns::transform::collapse_anemic;
use scraper::{Html, Selector};
use curl::easy::Easy;

use std::io::{Read, Write};
use std::fs::File;

struct LwnRow {
    url: String,
    id: String,
    packages: String,
    description: String,
}

fn main() {
    let mut db: Vec<String> = vec![];
    {
        let mut f = File::open("./db").unwrap();
        let mut s = String::new();
        f.read_to_string(&mut s).unwrap();
        for entry in s.split_whitespace() {
            db.push(entry.to_string());
        }
    }

    let mut tokens: Vec<Token> = tokenize(include_str!("./instructions.md").to_string());

    let mut page = 0;
    let mut pages_with_nothing = 0;

    loop {
        let mut found = 0;
        for token in tokens_from_html(fetch_page(page)) {
            if let Token::Issue(ref issue) = token {
                if db.contains(&issue.source.id) {
                    continue;
                } else {
                    found += 1;
                }
            }
            tokens.push(token);
        }

        writeln!(&mut std::io::stderr(),
                 "Page {page} yielded {found} issues, after {none} pages with nothing",
                 page = page,
                 found = found,
                 none = pages_with_nothing)
            .unwrap();

        page += 1;

        if found == 0 {
            pages_with_nothing += 1;
        } else {
            pages_with_nothing = 0;
        }

        if pages_with_nothing >= 2 {
            break;
        }

        if page > 10 {
            panic!("Not sure we should be hitting 10 pages!");
        }
    }

    print!("{}", write(&collapse_anemic(parse(tokens).unwrap())));

}

fn fetch_page(page: usize) -> scraper::Html {
    let url = format!("https://lwn.net/Vulnerabilities/?n={}&offset={}",
                      100,
                      page * 100);
    let mut data = Vec::new();

    let mut easy = Easy::new();
    easy.url(&url).unwrap();

    {
        let mut transfer = easy.transfer();
        transfer.write_function(|chunk| {
                data.extend_from_slice(chunk);
                Ok(chunk.len())
            })
            .unwrap();
        transfer.perform().unwrap();
    }

    return Html::parse_document(&String::from_utf8(data).unwrap());
}

fn tokens_from_html(html: scraper::Html) -> Vec<Token> {
    let mut tokens: Vec<Token> = vec![];

    let rows = Selector::parse("tr").unwrap();
    let links = Selector::parse("td:nth-child(1) a").unwrap();
    let packages = Selector::parse("td:nth-child(3)").unwrap();
    let descriptions = Selector::parse("td:nth-child(4)").unwrap();
    for row in html.select(&rows) {
        if let Some(link) = row.select(&links).next() {
            let package = row.select(&packages).next().unwrap().text().next().unwrap();
            let description = row.select(&descriptions).next().unwrap().text().next().unwrap();

            let lwnrow = LwnRow {
                url: link.value().attr("href").unwrap().to_string(),
                id: link.inner_html(),
                packages: package.to_string(),
                description: description.to_string(),
            };
            for token in row_to_tokens(lwnrow) {
                tokens.push(token);
            }
        }
    }

    return tokens;
}

fn row_to_tokens(row: LwnRow) -> Vec<Token> {
    let mut tokens = vec![];

    let url = format!("https://lwn.net{}", row.url);
    for pkg in split_packages(row.packages) {
        tokens.push(Token::Header(Header {
            issue_count: 0,
            notes: None,
            package: pkg.clone(),
        }));

        tokens.push(Token::Issue(Issue {
            complete: false,
            source: SourceLink {
                id: row.id.clone(),
                url: url.clone(),
            },
            content: format!(
                "([search]({search_url}), [files]({files_url}))  {description}",
                search_url = format!(
                    "http://search.nix.gsc.io/?q={package}&i=fosho&repos=nixos-nixpkgs",
                    package=pkg
                ),
                files_url = format!(
                    "{root}?utf8=%E2%9C%93&q={package}+in%3Apath&type=Code",
                    root="https://github.com/NixOS/nixpkgs/search",
                    package=pkg
                ),
                description=row.description
            ),
        }));
    }

    return tokens;
}

fn split_packages(packages: String) -> Vec<String> {
    let pkgs = packages.split_whitespace()
        .map(|s| s.replace(",", "").to_string())
        .map(|s| if s == "tiff" {
            "libtiff".to_string()
        } else if s == "libgc" {
            "boehm-gc".to_string()
        } else if s == "perl-DBD-MySQL" {
            "DBD-mysql".to_string()
        } else if s == "httpd" {
            "apache-httpd".to_string()
        } else {
            s
        })
        .collect();

    return pkgs;
}
<commit_msg>Load db in a separate function<commit_after>extern crate lwnvulns;
extern crate scraper;
extern crate curl;

use lwnvulns::tokenize::{tokenize, Token, Header, Issue, SourceLink};
use lwnvulns::parse::parse;
use lwnvulns::writer::write;
use lwnvulns::transform::collapse_anemic;
use scraper::{Html, Selector};
use curl::easy::Easy;

use std::io::{Read, Write};
use std::fs::File;

struct LwnRow {
    url: String,
    id: String,
    packages: String,
    description: String,
}

fn load_db(src: &str) -> Vec<String> {
    let mut db: Vec<String> = vec![];
    {
        let mut f = File::open(src).unwrap();
        let mut s = String::new();
        f.read_to_string(&mut s).unwrap();
        for entry in s.split_whitespace() {
            db.push(entry.to_string());
        }
    }

    return db;
}

fn main() {
    let db = load_db("./db");

    let mut tokens: Vec<Token> = tokenize(include_str!("./instructions.md").to_string());

    let mut page = 0;
    let mut pages_with_nothing = 0;

    loop {
        let mut found = 0;
        for token in tokens_from_html(fetch_page(page)) {
            if let Token::Issue(ref issue) = token {
                if db.contains(&issue.source.id) {
                    continue;
                } else {
                    found += 1;
                }
            }
            tokens.push(token);
        }

        writeln!(&mut std::io::stderr(),
                 "Page {page} yielded {found} issues, after {none} pages with nothing",
                 page = page,
                 found = found,
                 none = pages_with_nothing)
            .unwrap();

        page += 1;

        if found == 0 {
            pages_with_nothing += 1;
        } else {
            pages_with_nothing = 0;
        }

        if pages_with_nothing >= 2 {
            break;
        }

        if page > 10 {
            panic!("Not sure we should be hitting 10 pages!");
        }
    }

    print!("{}", write(&collapse_anemic(parse(tokens).unwrap())));

}

fn fetch_page(page: usize) -> scraper::Html {
    let url = format!("https://lwn.net/Vulnerabilities/?n={}&offset={}",
                      100,
                      page * 100);
    let mut data = Vec::new();

    let mut easy = Easy::new();
    easy.url(&url).unwrap();

    {
        let mut transfer = easy.transfer();
        transfer.write_function(|chunk| {
                data.extend_from_slice(chunk);
                Ok(chunk.len())
            })
            .unwrap();
        transfer.perform().unwrap();
    }

    return Html::parse_document(&String::from_utf8(data).unwrap());
}

fn tokens_from_html(html: scraper::Html) -> Vec<Token> {
    let mut tokens: Vec<Token> = vec![];

    let rows = Selector::parse("tr").unwrap();
    let links = Selector::parse("td:nth-child(1) a").unwrap();
    let packages = Selector::parse("td:nth-child(3)").unwrap();
    let descriptions = Selector::parse("td:nth-child(4)").unwrap();
    for row in html.select(&rows) {
        if let Some(link) = row.select(&links).next() {
            let package = row.select(&packages).next().unwrap().text().next().unwrap();
            let description = row.select(&descriptions).next().unwrap().text().next().unwrap();

            let lwnrow = LwnRow {
                url: link.value().attr("href").unwrap().to_string(),
                id: link.inner_html(),
                packages: package.to_string(),
                description: description.to_string(),
            };
            for token in row_to_tokens(lwnrow) {
                tokens.push(token);
            }
        }
    }

    return tokens;
}

fn row_to_tokens(row: LwnRow) -> Vec<Token> {
    let mut tokens = vec![];

    let url = format!("https://lwn.net{}", row.url);
    for pkg in split_packages(row.packages) {
        tokens.push(Token::Header(Header {
            issue_count: 0,
            notes: None,
            package: pkg.clone(),
        }));

        tokens.push(Token::Issue(Issue {
            complete: false,
            source: SourceLink {
                id: row.id.clone(),
                url: url.clone(),
            },
            content: format!(
                "([search]({search_url}), [files]({files_url}))  {description}",
                search_url = format!(
                    "http://search.nix.gsc.io/?q={package}&i=fosho&repos=nixos-nixpkgs",
                    package=pkg
                ),
                files_url = format!(
                    "{root}?utf8=%E2%9C%93&q={package}+in%3Apath&type=Code",
                    root="https://github.com/NixOS/nixpkgs/search",
                    package=pkg
                ),
                description=row.description
            ),
        }));
    }

    return tokens;
}

fn split_packages(packages: String) -> Vec<String> {
    let pkgs = packages.split_whitespace()
        .map(|s| s.replace(",", "").to_string())
        .map(|s| if s == "tiff" {
            "libtiff".to_string()
        } else if s == "libgc" {
            "boehm-gc".to_string()
        } else if s == "perl-DBD-MySQL" {
            "DBD-mysql".to_string()
        } else if s == "httpd" {
            "apache-httpd".to_string()
        } else {
            s
        })
        .collect();

    return pkgs;
}
<|endoftext|>


<commit_before>/* automatically generated by rust-bindgen */

import bindgen::{glutCreateWindow, glutDestroyWindow, glutDisplayFunc, glutInit};
import bindgen::{glutInitDisplayMode, glutPostRedisplay, glutSwapBuffers, glutTimerFunc};
import libc::*;
import dvec::{dvec, extensions};
import ptr::{addr_of, null};
import str::bytes;
import task::{local_data_get, local_data_set};
import unsafe::reinterpret_cast;
import vec::unsafe::to_ptr;

/* FIXME: global variable glutStrokeRoman */

/* FIXME: global variable glutStrokeMonoRoman */

/* FIXME: global variable glutBitmap9By15 */

/* FIXME: global variable glutBitmap8By13 */

/* FIXME: global variable glutBitmapTimesRoman10 */

/* FIXME: global variable glutBitmapTimesRoman24 */

/* FIXME: global variable glutBitmapHelvetica10 */

/* FIXME: global variable glutBitmapHelvetica12 */

/* FIXME: global variable glutBitmapHelvetica18 */

type GLenum = i32;
type GLint = i32;
type GLfloat = f32;
type GLdouble = f64;

enum Window = c_int;

fn destroy<T>(-_value: [T]/~) {
    // let it drop
}

fn init() unsafe {
    let argc = 0 as c_int;
    let command = bytes("glut");
    let argv: (*u8, *u8) = (to_ptr(command), null());
    let argv_p = reinterpret_cast(addr_of(argv));

    glutInit(addr_of(argc), argv_p);

    destroy(command);
}

fn create_window(name: str) -> Window unsafe {
    let bytes = str::bytes(name);
    ret Window(glutCreateWindow(to_ptr(bytes) as *c_char));
}

fn destroy_window(window: Window) unsafe {
    glutDestroyWindow(*window);
}

fn display_callback_tls_key(+_callback: @fn@()) {
    // Empty.
}

extern fn display_callback() unsafe {
    let callback = local_data_get(display_callback_tls_key).get();
    (*callback)();
}

fn display_func(callback: fn@()) unsafe {
    local_data_set(display_callback_tls_key, @callback);
    glutDisplayFunc(display_callback);
}

fn timer_callback_tls_key(+_callback: @dvec<fn@()>) {
    // Empty.
}

extern fn timer_callback(index: int) unsafe {
    let callbacks = local_data_get(timer_callback_tls_key).get();
    ((*callbacks)[index as uint])();
}

fn timer_func(msecs: u32, callback: fn@()) unsafe {
    let callbacks;
    alt local_data_get(timer_callback_tls_key) {
        none {
            callbacks = @dvec();
            local_data_set(timer_callback_tls_key, copy callbacks);
        }
        some(existing_callbacks) {
            callbacks = existing_callbacks;
        }
    }

    callbacks.push(callback);
    let index = (callbacks.len() - 1) as c_int;
    glutTimerFunc(msecs, timer_callback, index);
}

#[cfg(target_os="macos")]
fn check_loop() unsafe {
    ext::glutCheckLoop();
}

#[cfg(target_os="linux")]
fn check_loop() unsafe {
    ext::glutMainLoopEvent();
}

fn init_display_mode(mode: c_uint) unsafe {
    glutInitDisplayMode(mode);
}

fn swap_buffers() unsafe {
    glutSwapBuffers();
}

fn post_redisplay() unsafe {
    glutPostRedisplay();
}

#[cfg(target_os="macos")]
#[nolink]
#[link_args="-framework GLUT"]
extern mod dummy {
}

#[cfg(target_os="linux")]
#[link_name="glut"]
extern mod dummy {
}

#[cfg(target_os="macos")]
#[nolink]
extern mod ext {
    // Mac GLUT extension.
    fn glutCheckLoop();
}

#[cfg(target_os="linux")]
#[nolink]
extern mod ext {
    // freeglut extension.
    fn glutMainLoopEvent();
}

#[nolink]
extern mod bindgen {

fn glutInit(++argcp: *c_int, ++argv: **c_char);

fn glutInitDisplayMode(++mode: c_uint);

fn glutInitDisplayString(++string: *c_char);

fn glutInitWindowPosition(++x: c_int, ++y: c_int);

fn glutInitWindowSize(++width: c_int, ++height: c_int);

fn glutMainLoop();

fn glutCreateWindow(++title: *c_char) -> c_int;

fn glutCreateSubWindow(++win: c_int, ++x: c_int, ++y: c_int, ++width: c_int, ++height: c_int) -> c_int;

fn glutDestroyWindow(++win: c_int);

fn glutPostRedisplay();

fn glutPostWindowRedisplay(++win: c_int);

fn glutSwapBuffers();

fn glutGetWindow() -> c_int;

fn glutSetWindow(++win: c_int);

fn glutSetWindowTitle(++title: *c_char);

fn glutSetIconTitle(++title: *c_char);

fn glutPositionWindow(++x: c_int, ++y: c_int);

fn glutReshapeWindow(++width: c_int, ++height: c_int);

fn glutPopWindow();

fn glutPushWindow();

fn glutIconifyWindow();

fn glutShowWindow();

fn glutHideWindow();

fn glutFullScreen();

fn glutSetCursor(++cursor: c_int);

fn glutWarpPointer(++x: c_int, ++y: c_int);

fn glutEstablishOverlay();

fn glutRemoveOverlay();

fn glutUseLayer(++layer: GLenum);

fn glutPostOverlayRedisplay();

fn glutPostWindowOverlayRedisplay(++win: c_int);

fn glutShowOverlay();

fn glutHideOverlay();

fn glutCreateMenu(++arg1: *u8) -> c_int;

fn glutDestroyMenu(++menu: c_int);

fn glutGetMenu() -> c_int;

fn glutSetMenu(++menu: c_int);

fn glutAddMenuEntry(++label: *c_char, ++value: c_int);

fn glutAddSubMenu(++label: *c_char, ++submenu: c_int);

fn glutChangeToMenuEntry(++item: c_int, ++label: *c_char, ++value: c_int);

fn glutChangeToSubMenu(++item: c_int, ++label: *c_char, ++submenu: c_int);

fn glutRemoveMenuItem(++item: c_int);

fn glutAttachMenu(++button: c_int);

fn glutDetachMenu(++button: c_int);

fn glutDisplayFunc(++func: *u8);

fn glutReshapeFunc(++func: *u8);

fn glutKeyboardFunc(++func: *u8);

fn glutMouseFunc(++func: *u8);

fn glutMotionFunc(++func: *u8);

fn glutPassiveMotionFunc(++func: *u8);

fn glutEntryFunc(++func: *u8);

fn glutVisibilityFunc(++func: *u8);

fn glutIdleFunc(++func: *u8);

fn glutTimerFunc(++millis: c_uint, ++func: *u8, ++value: c_int);

fn glutMenuStateFunc(++func: *u8);

fn glutSpecialFunc(++func: *u8);

fn glutSpaceballMotionFunc(++func: *u8);

fn glutSpaceballRotateFunc(++func: *u8);

fn glutSpaceballButtonFunc(++func: *u8);

fn glutButtonBoxFunc(++func: *u8);

fn glutDialsFunc(++func: *u8);

fn glutTabletMotionFunc(++func: *u8);

fn glutTabletButtonFunc(++func: *u8);

fn glutMenuStatusFunc(++func: *u8);

fn glutOverlayDisplayFunc(++func: *u8);

fn glutWindowStatusFunc(++func: *u8);

fn glutKeyboardUpFunc(++func: *u8);

fn glutSpecialUpFunc(++func: *u8);

fn glutJoystickFunc(++func: *u8, ++pollInterval: c_int);

fn glutSetColor(++arg1: c_int, ++red: GLfloat, ++green: GLfloat, ++blue: GLfloat);

fn glutGetColor(++ndx: c_int, ++component: c_int) -> GLfloat;

fn glutCopyColormap(++win: c_int);

fn glutGet(++_type: GLenum) -> c_int;

fn glutDeviceGet(++_type: GLenum) -> c_int;

fn glutExtensionSupported(++name: *c_char) -> c_int;

fn glutGetModifiers() -> c_int;

fn glutLayerGet(++_type: GLenum) -> c_int;

fn glutGetProcAddress(++procName: *c_char) -> *c_void;

fn glutBitmapCharacter(++font: *c_void, ++character: c_int);

fn glutBitmapWidth(++font: *c_void, ++character: c_int) -> c_int;

fn glutStrokeCharacter(++font: *c_void, ++character: c_int);

fn glutStrokeWidth(++font: *c_void, ++character: c_int) -> c_int;

fn glutBitmapLength(++font: *c_void, ++string: *c_uchar) -> c_int;

fn glutStrokeLength(++font: *c_void, ++string: *c_uchar) -> c_int;

fn glutWireSphere(++radius: GLdouble, ++slices: GLint, ++stacks: GLint);

fn glutSolidSphere(++radius: GLdouble, ++slices: GLint, ++stacks: GLint);

fn glutWireCone(++base: GLdouble, ++height: GLdouble, ++slices: GLint, ++stacks: GLint);

fn glutSolidCone(++base: GLdouble, ++height: GLdouble, ++slices: GLint, ++stacks: GLint);

fn glutWireCube(++size: GLdouble);

fn glutSolidCube(++size: GLdouble);

fn glutWireTorus(++innerRadius: GLdouble, ++outerRadius: GLdouble, ++sides: GLint, ++rings: GLint);

fn glutSolidTorus(++innerRadius: GLdouble, ++outerRadius: GLdouble, ++sides: GLint, ++rings: GLint);

fn glutWireDodecahedron();

fn glutSolidDodecahedron();

fn glutWireTeapot(++size: GLdouble);

fn glutSolidTeapot(++size: GLdouble);

fn glutWireOctahedron();

fn glutSolidOctahedron();

fn glutWireTetrahedron();

fn glutSolidTetrahedron();

fn glutWireIcosahedron();

fn glutSolidIcosahedron();

fn glutVideoResizeGet(++param: GLenum) -> c_int;

fn glutSetupVideoResizing();

fn glutStopVideoResizing();

fn glutVideoResize(++x: c_int, ++y: c_int, ++width: c_int, ++height: c_int);

fn glutVideoPan(++x: c_int, ++y: c_int, ++width: c_int, ++height: c_int);

fn glutReportErrors();

fn glutIgnoreKeyRepeat(++ignore: c_int);

fn glutSetKeyRepeat(++repeatMode: c_int);

fn glutForceJoystickFunc();

fn glutGameModeString(++string: *c_char);

fn glutEnterGameMode() -> c_int;

fn glutLeaveGameMode();

fn glutGameModeGet(++mode: GLenum) -> c_int;

}
<commit_msg>Export the double buffering constant<commit_after>/* automatically generated by rust-bindgen */

import bindgen::{glutCreateWindow, glutDestroyWindow, glutDisplayFunc, glutInit};
import bindgen::{glutInitDisplayMode, glutPostRedisplay, glutSwapBuffers, glutTimerFunc};
import libc::*;
import dvec::{dvec, extensions};
import ptr::{addr_of, null};
import str::bytes;
import task::{local_data_get, local_data_set};
import unsafe::reinterpret_cast;
import vec::unsafe::to_ptr;

/* FIXME: global variable glutStrokeRoman */

/* FIXME: global variable glutStrokeMonoRoman */

/* FIXME: global variable glutBitmap9By15 */

/* FIXME: global variable glutBitmap8By13 */

/* FIXME: global variable glutBitmapTimesRoman10 */

/* FIXME: global variable glutBitmapTimesRoman24 */

/* FIXME: global variable glutBitmapHelvetica10 */

/* FIXME: global variable glutBitmapHelvetica12 */

/* FIXME: global variable glutBitmapHelvetica18 */

type GLenum = i32;
type GLint = i32;
type GLfloat = f32;
type GLdouble = f64;

enum Window = c_int;

const DOUBLE: c_uint = 2 as c_uint;

fn destroy<T>(-_value: [T]/~) {
    // let it drop
}

fn init() unsafe {
    let argc = 0 as c_int;
    let command = bytes("glut");
    let argv: (*u8, *u8) = (to_ptr(command), null());
    let argv_p = reinterpret_cast(addr_of(argv));

    glutInit(addr_of(argc), argv_p);

    destroy(command);
}

fn create_window(name: str) -> Window unsafe {
    let bytes = str::bytes(name);
    ret Window(glutCreateWindow(to_ptr(bytes) as *c_char));
}

fn destroy_window(window: Window) unsafe {
    glutDestroyWindow(*window);
}

fn display_callback_tls_key(+_callback: @fn@()) {
    // Empty.
}

extern fn display_callback() unsafe {
    let callback = local_data_get(display_callback_tls_key).get();
    (*callback)();
}

fn display_func(callback: fn@()) unsafe {
    local_data_set(display_callback_tls_key, @callback);
    glutDisplayFunc(display_callback);
}

fn timer_callback_tls_key(+_callback: @dvec<fn@()>) {
    // Empty.
}

extern fn timer_callback(index: int) unsafe {
    let callbacks = local_data_get(timer_callback_tls_key).get();
    ((*callbacks)[index as uint])();
}

fn timer_func(msecs: u32, callback: fn@()) unsafe {
    let callbacks;
    alt local_data_get(timer_callback_tls_key) {
        none {
            callbacks = @dvec();
            local_data_set(timer_callback_tls_key, copy callbacks);
        }
        some(existing_callbacks) {
            callbacks = existing_callbacks;
        }
    }

    callbacks.push(callback);
    let index = (callbacks.len() - 1) as c_int;
    glutTimerFunc(msecs, timer_callback, index);
}

#[cfg(target_os="macos")]
fn check_loop() unsafe {
    ext::glutCheckLoop();
}

#[cfg(target_os="linux")]
fn check_loop() unsafe {
    ext::glutMainLoopEvent();
}

fn init_display_mode(mode: c_uint) unsafe {
    glutInitDisplayMode(mode);
}

fn swap_buffers() unsafe {
    glutSwapBuffers();
}

fn post_redisplay() unsafe {
    glutPostRedisplay();
}

#[cfg(target_os="macos")]
#[nolink]
#[link_args="-framework GLUT"]
extern mod dummy {
}

#[cfg(target_os="linux")]
#[link_name="glut"]
extern mod dummy {
}

#[cfg(target_os="macos")]
#[nolink]
extern mod ext {
    // Mac GLUT extension.
    fn glutCheckLoop();
}

#[cfg(target_os="linux")]
#[nolink]
extern mod ext {
    // freeglut extension.
    fn glutMainLoopEvent();
}

#[nolink]
extern mod bindgen {

fn glutInit(++argcp: *c_int, ++argv: **c_char);

fn glutInitDisplayMode(++mode: c_uint);

fn glutInitDisplayString(++string: *c_char);

fn glutInitWindowPosition(++x: c_int, ++y: c_int);

fn glutInitWindowSize(++width: c_int, ++height: c_int);

fn glutMainLoop();

fn glutCreateWindow(++title: *c_char) -> c_int;

fn glutCreateSubWindow(++win: c_int, ++x: c_int, ++y: c_int, ++width: c_int, ++height: c_int) -> c_int;

fn glutDestroyWindow(++win: c_int);

fn glutPostRedisplay();

fn glutPostWindowRedisplay(++win: c_int);

fn glutSwapBuffers();

fn glutGetWindow() -> c_int;

fn glutSetWindow(++win: c_int);

fn glutSetWindowTitle(++title: *c_char);

fn glutSetIconTitle(++title: *c_char);

fn glutPositionWindow(++x: c_int, ++y: c_int);

fn glutReshapeWindow(++width: c_int, ++height: c_int);

fn glutPopWindow();

fn glutPushWindow();

fn glutIconifyWindow();

fn glutShowWindow();

fn glutHideWindow();

fn glutFullScreen();

fn glutSetCursor(++cursor: c_int);

fn glutWarpPointer(++x: c_int, ++y: c_int);

fn glutEstablishOverlay();

fn glutRemoveOverlay();

fn glutUseLayer(++layer: GLenum);

fn glutPostOverlayRedisplay();

fn glutPostWindowOverlayRedisplay(++win: c_int);

fn glutShowOverlay();

fn glutHideOverlay();

fn glutCreateMenu(++arg1: *u8) -> c_int;

fn glutDestroyMenu(++menu: c_int);

fn glutGetMenu() -> c_int;

fn glutSetMenu(++menu: c_int);

fn glutAddMenuEntry(++label: *c_char, ++value: c_int);

fn glutAddSubMenu(++label: *c_char, ++submenu: c_int);

fn glutChangeToMenuEntry(++item: c_int, ++label: *c_char, ++value: c_int);

fn glutChangeToSubMenu(++item: c_int, ++label: *c_char, ++submenu: c_int);

fn glutRemoveMenuItem(++item: c_int);

fn glutAttachMenu(++button: c_int);

fn glutDetachMenu(++button: c_int);

fn glutDisplayFunc(++func: *u8);

fn glutReshapeFunc(++func: *u8);

fn glutKeyboardFunc(++func: *u8);

fn glutMouseFunc(++func: *u8);

fn glutMotionFunc(++func: *u8);

fn glutPassiveMotionFunc(++func: *u8);

fn glutEntryFunc(++func: *u8);

fn glutVisibilityFunc(++func: *u8);

fn glutIdleFunc(++func: *u8);

fn glutTimerFunc(++millis: c_uint, ++func: *u8, ++value: c_int);

fn glutMenuStateFunc(++func: *u8);

fn glutSpecialFunc(++func: *u8);

fn glutSpaceballMotionFunc(++func: *u8);

fn glutSpaceballRotateFunc(++func: *u8);

fn glutSpaceballButtonFunc(++func: *u8);

fn glutButtonBoxFunc(++func: *u8);

fn glutDialsFunc(++func: *u8);

fn glutTabletMotionFunc(++func: *u8);

fn glutTabletButtonFunc(++func: *u8);

fn glutMenuStatusFunc(++func: *u8);

fn glutOverlayDisplayFunc(++func: *u8);

fn glutWindowStatusFunc(++func: *u8);

fn glutKeyboardUpFunc(++func: *u8);

fn glutSpecialUpFunc(++func: *u8);

fn glutJoystickFunc(++func: *u8, ++pollInterval: c_int);

fn glutSetColor(++arg1: c_int, ++red: GLfloat, ++green: GLfloat, ++blue: GLfloat);

fn glutGetColor(++ndx: c_int, ++component: c_int) -> GLfloat;

fn glutCopyColormap(++win: c_int);

fn glutGet(++_type: GLenum) -> c_int;

fn glutDeviceGet(++_type: GLenum) -> c_int;

fn glutExtensionSupported(++name: *c_char) -> c_int;

fn glutGetModifiers() -> c_int;

fn glutLayerGet(++_type: GLenum) -> c_int;

fn glutGetProcAddress(++procName: *c_char) -> *c_void;

fn glutBitmapCharacter(++font: *c_void, ++character: c_int);

fn glutBitmapWidth(++font: *c_void, ++character: c_int) -> c_int;

fn glutStrokeCharacter(++font: *c_void, ++character: c_int);

fn glutStrokeWidth(++font: *c_void, ++character: c_int) -> c_int;

fn glutBitmapLength(++font: *c_void, ++string: *c_uchar) -> c_int;

fn glutStrokeLength(++font: *c_void, ++string: *c_uchar) -> c_int;

fn glutWireSphere(++radius: GLdouble, ++slices: GLint, ++stacks: GLint);

fn glutSolidSphere(++radius: GLdouble, ++slices: GLint, ++stacks: GLint);

fn glutWireCone(++base: GLdouble, ++height: GLdouble, ++slices: GLint, ++stacks: GLint);

fn glutSolidCone(++base: GLdouble, ++height: GLdouble, ++slices: GLint, ++stacks: GLint);

fn glutWireCube(++size: GLdouble);

fn glutSolidCube(++size: GLdouble);

fn glutWireTorus(++innerRadius: GLdouble, ++outerRadius: GLdouble, ++sides: GLint, ++rings: GLint);

fn glutSolidTorus(++innerRadius: GLdouble, ++outerRadius: GLdouble, ++sides: GLint, ++rings: GLint);

fn glutWireDodecahedron();

fn glutSolidDodecahedron();

fn glutWireTeapot(++size: GLdouble);

fn glutSolidTeapot(++size: GLdouble);

fn glutWireOctahedron();

fn glutSolidOctahedron();

fn glutWireTetrahedron();

fn glutSolidTetrahedron();

fn glutWireIcosahedron();

fn glutSolidIcosahedron();

fn glutVideoResizeGet(++param: GLenum) -> c_int;

fn glutSetupVideoResizing();

fn glutStopVideoResizing();

fn glutVideoResize(++x: c_int, ++y: c_int, ++width: c_int, ++height: c_int);

fn glutVideoPan(++x: c_int, ++y: c_int, ++width: c_int, ++height: c_int);

fn glutReportErrors();

fn glutIgnoreKeyRepeat(++ignore: c_int);

fn glutSetKeyRepeat(++repeatMode: c_int);

fn glutForceJoystickFunc();

fn glutGameModeString(++string: *c_char);

fn glutEnterGameMode() -> c_int;

fn glutLeaveGameMode();

fn glutGameModeGet(++mode: GLenum) -> c_int;

}
<|endoftext|>


<commit_before>use failure::{Error, Fail};
use std::env;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use tempfile::TempDir;

const WASI_TARGET: &str = "wasm32-unknown-wasi";

#[derive(Debug, Fail)]
pub enum CompileError {
    #[fail(display = "File not found: {}", _0)]
    FileNotFound(String),
    #[fail(display = "Clang reported error: {}", _0)]
    Execution { stdout: String, stderr: String },
    #[fail(display = "Lucetc error: {}", _0)]
    Lucetc {
        #[cause]
        e: Error,
    },
    #[fail(display = "IO error: {}", _0)]
    IO {
        #[cause]
        e: std::io::Error,
    },
}

impl From<std::io::Error> for CompileError {
    fn from(e: std::io::Error) -> CompileError {
        CompileError::IO { e }
    }
}

impl CompileError {
    pub fn check(output: Output, print: bool) -> Result<(), Self> {
        if print {
            std::io::stdout().write_all(&output.stdout).unwrap();
            std::io::stderr().write_all(&output.stderr).unwrap();
        }
        if output.status.success() {
            Ok(())
        } else {
            Err(CompileError::Execution {
                stdout: String::from_utf8_lossy(&output.stdout).to_string(),
                stderr: String::from_utf8_lossy(&output.stderr).to_string(),
            })
        }
    }
}

fn wasi_sdk() -> PathBuf {
    Path::new(&env::var("WASI_SDK").unwrap_or("/opt/wasi-sdk".to_owned())).to_path_buf()
}

fn wasi_sysroot() -> PathBuf {
    match env::var("WASI_SYSROOT") {
        Ok(wasi_sysroot) => Path::new(&wasi_sysroot).to_path_buf(),
        Err(_) => {
            let mut path = wasi_sdk();
            path.push("share");
            path.push("sysroot");
            path
        }
    }
}

fn wasm_clang() -> PathBuf {
    match env::var("CLANG") {
        Ok(clang) => Path::new(&clang).to_path_buf(),
        Err(_) => {
            let mut path = wasi_sdk();
            path.push("bin");
            path.push("clang");
            path
        }
    }
}

pub struct Compile {
    input: PathBuf,
    cflags: Vec<String>,
    print_output: bool,
}

pub trait CompileOpts {
    fn cflag<S: AsRef<str>>(&mut self, cflag: S);
    fn with_cflag<S: AsRef<str>>(self, cflag: S) -> Self;

    fn include<S: AsRef<str>>(&mut self, include: S);
    fn with_include<S: AsRef<str>>(self, include: S) -> Self;
}

impl CompileOpts for Compile {
    fn cflag<S: AsRef<str>>(&mut self, cflag: S) {
        self.cflags.push(cflag.as_ref().to_owned());
    }

    fn with_cflag<S: AsRef<str>>(mut self, cflag: S) -> Self {
        self.cflag(cflag);
        self
    }

    fn include<S: AsRef<str>>(&mut self, include: S) {
        self.cflags.push(format!("-I{}", include.as_ref()));
    }

    fn with_include<S: AsRef<str>>(mut self, include: S) -> Self {
        self.include(include);
        self
    }
}

impl Compile {
    pub fn new<P: AsRef<Path>>(input: P) -> Self {
        Compile {
            input: PathBuf::from(input.as_ref()),
            cflags: Vec::new(),
            print_output: false,
        }
    }

    pub fn print_output(&mut self, print: bool) {
        self.print_output = print;
    }

    pub fn with_print_output(mut self, print: bool) -> Self {
        self.print_output(print);
        self
    }

    pub fn compile<P: AsRef<Path>>(&self, output: P) -> Result<(), CompileError> {
        let clang = wasm_clang();
        if !clang.exists() {
            Err(CompileError::FileNotFound(
                clang.to_string_lossy().into_owned(),
            ))?;
        }
        if !self.input.exists() {
            Err(CompileError::FileNotFound(
                self.input.to_string_lossy().into_owned(),
            ))?;
        }
        let mut cmd = Command::new(clang);
        cmd.arg(format!("--target={}", WASI_TARGET));
        cmd.arg(format!("--sysroot={}", wasi_sysroot().display()));
        cmd.arg("-c");
        cmd.arg(self.input.clone());
        cmd.arg("-o");
        cmd.arg(output.as_ref());
        for cflag in self.cflags.iter() {
            cmd.arg(cflag);
        }
        let run = cmd.output().expect("clang executable exists");
        CompileError::check(run, self.print_output)
    }
}

pub struct Link {
    input: Vec<PathBuf>,
    cflags: Vec<String>,
    ldflags: Vec<String>,
    print_output: bool,
}

impl Link {
    pub fn new<P: AsRef<Path>>(input: &[P]) -> Self {
        Link {
            input: input.iter().map(|p| PathBuf::from(p.as_ref())).collect(),
            cflags: vec![],
            ldflags: vec![],
            print_output: false,
        }
        .with_link_opt(LinkOpt::DefaultOpts)
    }

    pub fn print_output(&mut self, print: bool) {
        self.print_output = print;
    }

    pub fn with_print_output(mut self, print: bool) -> Self {
        self.print_output(print);
        self
    }

    pub fn link<P: AsRef<Path>>(&self, output: P) -> Result<(), CompileError> {
        let clang = wasm_clang();
        if !clang.exists() {
            Err(CompileError::FileNotFound(
                clang.to_string_lossy().into_owned(),
            ))?;
        }
        let mut cmd = Command::new(clang);
        for input in self.input.iter() {
            if !input.exists() {
                Err(CompileError::FileNotFound(
                    input.to_string_lossy().into_owned(),
                ))?;
            }
            cmd.arg(input.clone());
        }
        cmd.arg("-o");
        cmd.arg(output.as_ref());
        for cflag in self.cflags.iter() {
            cmd.arg(cflag);
        }
        for ldflag in self.ldflags.iter() {
            cmd.arg(format!("-Wl,{}", ldflag));
        }
        let run = cmd.output().expect("clang executable exists");
        CompileError::check(run, self.print_output)
    }
}

pub trait AsLink {
    fn as_link(&mut self) -> &mut Link;
}

impl AsLink for Link {
    fn as_link(&mut self) -> &mut Link {
        self
    }
}

#[derive(Clone, Copy, Debug)]
pub enum LinkOpt<'t> {
    /// Allow references to an undefined function that will be resolved later by the dynamic linker
    AllowUndefined(&'t str),

    /// Allow references to any undefined function. They will be resolved later by the dynamic linker
    AllowUndefinedAll,

    /// Default options, possibly enabling workarounds for temporary bugs
    DefaultOpts,

    /// Export a symbol
    Export(&'t str),

    /// Preserve all the symbols during LTO, even if they are not used
    ExportAll,

    /// Do not assume that the library has a predefined entry point
    NoDefaultEntryPoint,

    /// Create a shared library
    Shared,

    /// Do not put debug information (STABS or DWARF) in the output file
    StripDebug,

    /// Remove functions and data that are unreachable by the entry point or exported symbols
    StripUnused,
}

impl<'t> LinkOpt<'t> {
    #[cfg(target_os = "macos")]
    fn as_ldflags(&self) -> Vec<String> {
        match self {
            LinkOpt::AllowUndefined(_symbol) => vec![],
            LinkOpt::AllowUndefinedAll => vec!["-undefined,dynamic_lookup".to_string()],
            LinkOpt::DefaultOpts => vec![],
            LinkOpt::Export(symbol) => vec![format!("-exported_symbol,{}", symbol).to_string()],
            LinkOpt::ExportAll => vec!["-export_dynamic".to_string()],
            LinkOpt::NoDefaultEntryPoint => vec![],
            LinkOpt::Shared => vec!["-dylib".to_string()],
            LinkOpt::StripDebug => vec!["-S".to_string()],
            LinkOpt::StripUnused => vec!["-dead_strip".to_string()],
        }
    }

    #[cfg(not(target_os = "macos"))]
    fn as_ldflags(&self) -> Vec<String> {
        match self {
            LinkOpt::AllowUndefined(symbol) => vec![format!("-U,_{}", symbol).to_string()],
            LinkOpt::AllowUndefinedAll => vec!["--allow-undefined".to_string()],
            LinkOpt::DefaultOpts => vec!["--no-threads".to_string()],
            LinkOpt::Export(symbol) => vec![format!("--export={}", symbol).to_string()],
            LinkOpt::ExportAll => vec!["--export-all".to_string()],
            LinkOpt::NoDefaultEntryPoint => vec!["--no-entry".to_string()],
            LinkOpt::Shared => vec!["--shared".to_string()],
            LinkOpt::StripDebug => vec!["-S".to_string()],
            LinkOpt::StripUnused => vec!["--strip-discarded".to_string()],
        }
    }
}

pub trait LinkOpts {
    fn link_opt(&mut self, link_opt: LinkOpt);
    fn with_link_opt(self, link_opt: LinkOpt) -> Self;

    fn export<S: AsRef<str>>(&mut self, export: S);
    fn with_export<S: AsRef<str>>(self, export: S) -> Self;
}

impl<T: AsLink> LinkOpts for T {
    fn link_opt(&mut self, link_opt: LinkOpt) {
        self.as_link().ldflags.extend(link_opt.as_ldflags());
    }

    fn with_link_opt(mut self, link_opt: LinkOpt) -> Self {
        self.link_opt(link_opt);
        self
    }

    fn export<S: AsRef<str>>(&mut self, export: S) {
        self.link_opt(LinkOpt::Export(export.as_ref()));
    }

    fn with_export<S: AsRef<str>>(mut self, export: S) -> Self {
        self.export(export);
        self
    }
}

impl<T: AsLink> CompileOpts for T {
    fn cflag<S: AsRef<str>>(&mut self, cflag: S) {
        self.as_link().cflags.push(cflag.as_ref().to_owned());
    }

    fn with_cflag<S: AsRef<str>>(mut self, cflag: S) -> Self {
        self.cflag(cflag);
        self
    }

    fn include<S: AsRef<str>>(&mut self, include: S) {
        self.as_link()
            .cflags
            .push(format!("-I{}", include.as_ref()));
    }

    fn with_include<S: AsRef<str>>(mut self, include: S) -> Self {
        self.include(include);
        self
    }
}

pub struct Lucetc {
    link: Link,
    lucetc: lucetc::Lucetc,
    tmpdir: TempDir,
    wasm_file: PathBuf,
}

impl Lucetc {
    pub fn new<P: AsRef<Path>>(input: &[P]) -> Self {
        let link = Link::new(input);
        let tmpdir = TempDir::new().expect("temporary directory creation failed");
        let wasm_file = tmpdir.path().join("out.wasm");
        let lucetc = lucetc::Lucetc::new(&wasm_file);
        Lucetc {
            link,
            lucetc,
            tmpdir,
            wasm_file,
        }
    }

    pub fn print_output(mut self, print: bool) -> Self {
        self.link.print_output = print;
        self
    }

    pub fn build<P: AsRef<Path>>(self, output: P) -> Result<(), CompileError> {
        self.link.link(&self.wasm_file)?;
        self.lucetc
            .shared_object_file(output.as_ref())
            .map_err(|e| CompileError::Lucetc { e })?;
        Ok(self.tmpdir.close()?)
    }
}

impl AsLink for Lucetc {
    fn as_link(&mut self) -> &mut Link {
        &mut self.link
    }
}

impl lucetc::AsLucetc for Lucetc {
    fn as_lucetc(&mut self) -> &mut lucetc::Lucetc {
        &mut self.lucetc
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;
    #[test]
    fn wasi_sdk_installed() {
        let clang = wasm_clang();
        assert!(clang.exists(), "clang executable exists");
    }

    fn test_file(name: &str) -> PathBuf {
        let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        p.push("test");
        p.push(name);
        assert!(p.exists(), "test file does not exist");
        p
    }

    #[test]
    fn compile_a() {
        let tmp = TempDir::new().expect("create temporary directory");

        let compiler = Compile::new(test_file("a.c"));

        let objfile = tmp.path().join("a.o");

        compiler.compile(objfile.clone()).expect("compile a.c");

        assert!(objfile.exists(), "object file created");

        let mut linker = Link::new(&[objfile]);
        linker.cflag("-nostartfiles");
        linker.link_opt(LinkOpt::NoDefaultEntryPoint);

        let wasmfile = tmp.path().join("a.wasm");

        linker.link(wasmfile.clone()).expect("link a.wasm");

        assert!(wasmfile.exists(), "wasm file created");
    }

    #[test]
    fn compile_b() {
        let tmp = TempDir::new().expect("create temporary directory");

        let compiler = Compile::new(test_file("b.c"));

        let objfile = tmp.path().join("b.o");

        compiler.compile(objfile.clone()).expect("compile b.c");

        assert!(objfile.exists(), "object file created");

        let mut linker = Link::new(&[objfile]);
        linker.cflag("-nostartfiles");
        linker.link_opt(LinkOpt::NoDefaultEntryPoint);
        linker.link_opt(LinkOpt::AllowUndefinedAll);

        let wasmfile = tmp.path().join("b.wasm");

        linker.link(wasmfile.clone()).expect("link b.wasm");

        assert!(wasmfile.exists(), "wasm file created");
    }

    #[test]
    fn compile_a_and_b() {
        let tmp = TempDir::new().expect("create temporary directory");

        let mut linker = Link::new(&[test_file("a.c"), test_file("b.c")]);
        linker.cflag("-nostartfiles");
        linker.link_opt(LinkOpt::NoDefaultEntryPoint);

        let wasmfile = tmp.path().join("ab.wasm");

        linker.link(wasmfile.clone()).expect("link ab.wasm");

        assert!(wasmfile.exists(), "wasm file created");
    }

    #[test]
    fn compile_to_lucet() {
        let tmp = TempDir::new().expect("create temporary directory");

        let mut lucetc = Lucetc::new(&[test_file("a.c"), test_file("b.c")]);
        lucetc.cflag("-nostartfiles");
        lucetc.link_opt(LinkOpt::NoDefaultEntryPoint);

        let so_file = tmp.path().join("ab.so");

        lucetc.build(&so_file).expect("compile ab.so");

        assert!(so_file.exists(), "so file created");
    }
}
<commit_msg>Add sysroot to the linker flags<commit_after>use failure::{Error, Fail};
use std::env;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use tempfile::TempDir;

const WASI_TARGET: &str = "wasm32-unknown-wasi";

#[derive(Debug, Fail)]
pub enum CompileError {
    #[fail(display = "File not found: {}", _0)]
    FileNotFound(String),
    #[fail(display = "Clang reported error: {}", _0)]
    Execution { stdout: String, stderr: String },
    #[fail(display = "Lucetc error: {}", _0)]
    Lucetc {
        #[cause]
        e: Error,
    },
    #[fail(display = "IO error: {}", _0)]
    IO {
        #[cause]
        e: std::io::Error,
    },
}

impl From<std::io::Error> for CompileError {
    fn from(e: std::io::Error) -> CompileError {
        CompileError::IO { e }
    }
}

impl CompileError {
    pub fn check(output: Output, print: bool) -> Result<(), Self> {
        if print {
            std::io::stdout().write_all(&output.stdout).unwrap();
            std::io::stderr().write_all(&output.stderr).unwrap();
        }
        if output.status.success() {
            Ok(())
        } else {
            Err(CompileError::Execution {
                stdout: String::from_utf8_lossy(&output.stdout).to_string(),
                stderr: String::from_utf8_lossy(&output.stderr).to_string(),
            })
        }
    }
}

fn wasi_sdk() -> PathBuf {
    Path::new(&env::var("WASI_SDK").unwrap_or("/opt/wasi-sdk".to_owned())).to_path_buf()
}

fn wasi_sysroot() -> PathBuf {
    match env::var("WASI_SYSROOT") {
        Ok(wasi_sysroot) => Path::new(&wasi_sysroot).to_path_buf(),
        Err(_) => {
            let mut path = wasi_sdk();
            path.push("share");
            path.push("sysroot");
            path
        }
    }
}

fn wasm_clang() -> PathBuf {
    match env::var("CLANG") {
        Ok(clang) => Path::new(&clang).to_path_buf(),
        Err(_) => {
            let mut path = wasi_sdk();
            path.push("bin");
            path.push("clang");
            path
        }
    }
}

pub struct Compile {
    input: PathBuf,
    cflags: Vec<String>,
    print_output: bool,
}

pub trait CompileOpts {
    fn cflag<S: AsRef<str>>(&mut self, cflag: S);
    fn with_cflag<S: AsRef<str>>(self, cflag: S) -> Self;

    fn include<S: AsRef<str>>(&mut self, include: S);
    fn with_include<S: AsRef<str>>(self, include: S) -> Self;
}

impl CompileOpts for Compile {
    fn cflag<S: AsRef<str>>(&mut self, cflag: S) {
        self.cflags.push(cflag.as_ref().to_owned());
    }

    fn with_cflag<S: AsRef<str>>(mut self, cflag: S) -> Self {
        self.cflag(cflag);
        self
    }

    fn include<S: AsRef<str>>(&mut self, include: S) {
        self.cflags.push(format!("-I{}", include.as_ref()));
    }

    fn with_include<S: AsRef<str>>(mut self, include: S) -> Self {
        self.include(include);
        self
    }
}

impl Compile {
    pub fn new<P: AsRef<Path>>(input: P) -> Self {
        Compile {
            input: PathBuf::from(input.as_ref()),
            cflags: Vec::new(),
            print_output: false,
        }
    }

    pub fn print_output(&mut self, print: bool) {
        self.print_output = print;
    }

    pub fn with_print_output(mut self, print: bool) -> Self {
        self.print_output(print);
        self
    }

    pub fn compile<P: AsRef<Path>>(&self, output: P) -> Result<(), CompileError> {
        let clang = wasm_clang();
        if !clang.exists() {
            Err(CompileError::FileNotFound(
                clang.to_string_lossy().into_owned(),
            ))?;
        }
        if !self.input.exists() {
            Err(CompileError::FileNotFound(
                self.input.to_string_lossy().into_owned(),
            ))?;
        }
        let mut cmd = Command::new(clang);
        cmd.arg(format!("--target={}", WASI_TARGET));
        cmd.arg(format!("--sysroot={}", wasi_sysroot().display()));
        cmd.arg("-c");
        cmd.arg(self.input.clone());
        cmd.arg("-o");
        cmd.arg(output.as_ref());
        for cflag in self.cflags.iter() {
            cmd.arg(cflag);
        }
        let run = cmd.output().expect("clang executable exists");
        CompileError::check(run, self.print_output)
    }
}

pub struct Link {
    input: Vec<PathBuf>,
    cflags: Vec<String>,
    ldflags: Vec<String>,
    print_output: bool,
}

impl Link {
    pub fn new<P: AsRef<Path>>(input: &[P]) -> Self {
        Link {
            input: input.iter().map(|p| PathBuf::from(p.as_ref())).collect(),
            cflags: vec![],
            ldflags: vec![],
            print_output: false,
        }
        .with_link_opt(LinkOpt::DefaultOpts)
    }

    pub fn print_output(&mut self, print: bool) {
        self.print_output = print;
    }

    pub fn with_print_output(mut self, print: bool) -> Self {
        self.print_output(print);
        self
    }

    pub fn link<P: AsRef<Path>>(&self, output: P) -> Result<(), CompileError> {
        let clang = wasm_clang();
        if !clang.exists() {
            Err(CompileError::FileNotFound(
                clang.to_string_lossy().into_owned(),
            ))?;
        }
        let mut cmd = Command::new(clang);
        for input in self.input.iter() {
            if !input.exists() {
                Err(CompileError::FileNotFound(
                    input.to_string_lossy().into_owned(),
                ))?;
            }
            cmd.arg(input.clone());
        }
        cmd.arg(format!("--target={}", WASI_TARGET));
        cmd.arg(format!("--sysroot={}", wasi_sysroot().display()));
        cmd.arg("-o");
        cmd.arg(output.as_ref());
        for cflag in self.cflags.iter() {
            cmd.arg(cflag);
        }
        for ldflag in self.ldflags.iter() {
            cmd.arg(format!("-Wl,{}", ldflag));
        }
        let run = cmd.output().expect("clang executable exists");
        CompileError::check(run, self.print_output)
    }
}

pub trait AsLink {
    fn as_link(&mut self) -> &mut Link;
}

impl AsLink for Link {
    fn as_link(&mut self) -> &mut Link {
        self
    }
}

#[derive(Clone, Copy, Debug)]
pub enum LinkOpt<'t> {
    /// Allow references to any undefined function. They will be resolved later by the dynamic linker
    AllowUndefinedAll,

    /// Default options, possibly enabling workarounds for temporary bugs
    DefaultOpts,

    /// Export a symbol
    Export(&'t str),

    /// Preserve all the symbols during LTO, even if they are not used
    ExportAll,

    /// Do not assume that the library has a predefined entry point
    NoDefaultEntryPoint,

    /// Create a shared library
    Shared,

    /// Do not put debug information (STABS or DWARF) in the output file
    StripDebug,

    /// Remove functions and data that are unreachable by the entry point or exported symbols
    StripUnused,
}

impl<'t> LinkOpt<'t> {
    fn as_ldflags(&self) -> Vec<String> {
        match self {
            LinkOpt::AllowUndefinedAll => vec!["--allow-undefined".to_string()],
            LinkOpt::DefaultOpts => vec!["--no-threads".to_string()],
            LinkOpt::Export(symbol) => vec![format!("--export={}", symbol).to_string()],
            LinkOpt::ExportAll => vec!["--export-all".to_string()],
            LinkOpt::NoDefaultEntryPoint => vec!["--no-entry".to_string()],
            LinkOpt::Shared => vec!["--shared".to_string()],
            LinkOpt::StripDebug => vec!["--strip-debug".to_string()],
            LinkOpt::StripUnused => vec!["--strip-discarded".to_string()],
        }
    }
}

pub trait LinkOpts {
    fn link_opt(&mut self, link_opt: LinkOpt);
    fn with_link_opt(self, link_opt: LinkOpt) -> Self;

    fn export<S: AsRef<str>>(&mut self, export: S);
    fn with_export<S: AsRef<str>>(self, export: S) -> Self;
}

impl<T: AsLink> LinkOpts for T {
    fn link_opt(&mut self, link_opt: LinkOpt) {
        self.as_link().ldflags.extend(link_opt.as_ldflags());
    }

    fn with_link_opt(mut self, link_opt: LinkOpt) -> Self {
        self.link_opt(link_opt);
        self
    }

    fn export<S: AsRef<str>>(&mut self, export: S) {
        self.link_opt(LinkOpt::Export(export.as_ref()));
    }

    fn with_export<S: AsRef<str>>(mut self, export: S) -> Self {
        self.export(export);
        self
    }
}

impl<T: AsLink> CompileOpts for T {
    fn cflag<S: AsRef<str>>(&mut self, cflag: S) {
        self.as_link().cflags.push(cflag.as_ref().to_owned());
    }

    fn with_cflag<S: AsRef<str>>(mut self, cflag: S) -> Self {
        self.cflag(cflag);
        self
    }

    fn include<S: AsRef<str>>(&mut self, include: S) {
        self.as_link()
            .cflags
            .push(format!("-I{}", include.as_ref()));
    }

    fn with_include<S: AsRef<str>>(mut self, include: S) -> Self {
        self.include(include);
        self
    }
}

pub struct Lucetc {
    link: Link,
    lucetc: lucetc::Lucetc,
    tmpdir: TempDir,
    wasm_file: PathBuf,
}

impl Lucetc {
    pub fn new<P: AsRef<Path>>(input: &[P]) -> Self {
        let link = Link::new(input);
        let tmpdir = TempDir::new().expect("temporary directory creation failed");
        let wasm_file = tmpdir.path().join("out.wasm");
        let lucetc = lucetc::Lucetc::new(&wasm_file);
        Lucetc {
            link,
            lucetc,
            tmpdir,
            wasm_file,
        }
    }

    pub fn print_output(mut self, print: bool) -> Self {
        self.link.print_output = print;
        self
    }

    pub fn build<P: AsRef<Path>>(self, output: P) -> Result<(), CompileError> {
        self.link.link(&self.wasm_file)?;
        self.lucetc
            .shared_object_file(output.as_ref())
            .map_err(|e| CompileError::Lucetc { e })?;
        Ok(self.tmpdir.close()?)
    }
}

impl AsLink for Lucetc {
    fn as_link(&mut self) -> &mut Link {
        &mut self.link
    }
}

impl lucetc::AsLucetc for Lucetc {
    fn as_lucetc(&mut self) -> &mut lucetc::Lucetc {
        &mut self.lucetc
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;
    #[test]
    fn wasi_sdk_installed() {
        let clang = wasm_clang();
        assert!(clang.exists(), "clang executable exists");
    }

    fn test_file(name: &str) -> PathBuf {
        let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        p.push("test");
        p.push(name);
        assert!(p.exists(), "test file does not exist");
        p
    }

    #[test]
    fn compile_a() {
        let tmp = TempDir::new().expect("create temporary directory");

        let compiler = Compile::new(test_file("a.c"));

        let objfile = tmp.path().join("a.o");

        compiler.compile(objfile.clone()).expect("compile a.c");

        assert!(objfile.exists(), "object file created");

        let mut linker = Link::new(&[objfile]);
        linker.cflag("-nostartfiles");
        linker.link_opt(LinkOpt::NoDefaultEntryPoint);

        let wasmfile = tmp.path().join("a.wasm");

        linker.link(wasmfile.clone()).expect("link a.wasm");

        assert!(wasmfile.exists(), "wasm file created");
    }

    #[test]
    fn compile_b() {
        let tmp = TempDir::new().expect("create temporary directory");

        let compiler = Compile::new(test_file("b.c"));

        let objfile = tmp.path().join("b.o");

        compiler.compile(objfile.clone()).expect("compile b.c");

        assert!(objfile.exists(), "object file created");

        let mut linker = Link::new(&[objfile]);
        linker.cflag("-nostartfiles");
        linker.link_opt(LinkOpt::NoDefaultEntryPoint);
        linker.link_opt(LinkOpt::AllowUndefinedAll);

        let wasmfile = tmp.path().join("b.wasm");

        linker.link(wasmfile.clone()).expect("link b.wasm");

        assert!(wasmfile.exists(), "wasm file created");
    }

    #[test]
    fn compile_a_and_b() {
        let tmp = TempDir::new().expect("create temporary directory");

        let mut linker = Link::new(&[test_file("a.c"), test_file("b.c")]);
        linker.cflag("-nostartfiles");
        linker.link_opt(LinkOpt::NoDefaultEntryPoint);

        let wasmfile = tmp.path().join("ab.wasm");

        linker.link(wasmfile.clone()).expect("link ab.wasm");

        assert!(wasmfile.exists(), "wasm file created");
    }

    #[test]
    fn compile_to_lucet() {
        let tmp = TempDir::new().expect("create temporary directory");

        let mut lucetc = Lucetc::new(&[test_file("a.c"), test_file("b.c")]);
        lucetc.cflag("-nostartfiles");
        lucetc.link_opt(LinkOpt::NoDefaultEntryPoint);

        let so_file = tmp.path().join("ab.so");

        lucetc.build(&so_file).expect("compile ab.so");

        assert!(so_file.exists(), "so file created");
    }
}
<|endoftext|>


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

use dom::audionode::MAX_CHANNEL_COUNT;
use dom::bindings::cell::DomRefCell;
use dom::bindings::codegen::Bindings::AudioBufferBinding::{self, AudioBufferMethods, AudioBufferOptions};
use dom::bindings::error::{Error, Fallible};
use dom::bindings::num::Finite;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
use dom::bindings::root::DomRoot;
use dom::window::Window;
use dom_struct::dom_struct;
use js::jsapi::{Heap, JSContext, JSObject, JS_StealArrayBufferContents};
use js::rust::CustomAutoRooterGuard;
use js::typedarray::{CreateWith, Float32Array};
use servo_media::audio::buffer_source_node::AudioBuffer as ServoMediaAudioBuffer;
use std::ptr::{self, NonNull};
use std::sync::{Arc, Mutex};

type JSAudioChannel = Heap<*mut JSObject>;

#[dom_struct]
pub struct AudioBuffer {
    reflector_: Reflector,
    js_channels: DomRefCell<Vec<JSAudioChannel>>,
    #[ignore_malloc_size_of = "Arc"]
    shared_channels: Arc<Mutex<ServoMediaAudioBuffer>>,
    sample_rate: f32,
    length: u32,
    duration: f64,
    number_of_channels: u32,
}

impl AudioBuffer {
    #[allow(unrooted_must_root)]
    #[allow(unsafe_code)]
    pub fn new_inherited(cx: *mut JSContext,
                         number_of_channels: u32,
                         length: u32,
                         sample_rate: f32) -> AudioBuffer {
        let initial_data = vec![0.; length as usize];
        let mut js_channels: Vec<JSAudioChannel> = Vec::with_capacity(number_of_channels as usize);
        for _ in 0..number_of_channels {
            rooted!(in (cx) let mut array = ptr::null_mut::<JSObject>());
            let _ = unsafe {
                Float32Array::create(cx, CreateWith::Slice(initial_data.as_slice()), array.handle_mut())
            };
            let js_channel = Heap::default();
            js_channel.set(array.get());
            js_channels.push(js_channel);
        }
        AudioBuffer {
            reflector_: Reflector::new(),
            js_channels: DomRefCell::new(js_channels),
            shared_channels: Arc::new(Mutex::new(
                    ServoMediaAudioBuffer::new(number_of_channels as u8, length as usize))),
                    sample_rate: sample_rate,
                    length: length,
                    duration: length as f64 / sample_rate as f64,
                    number_of_channels: number_of_channels,
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(global: &Window,
               number_of_channels: u32,
               length: u32,
               sample_rate: f32) -> DomRoot<AudioBuffer> {
        let buffer = AudioBuffer::new_inherited(global.get_cx(), number_of_channels, length, sample_rate);
        reflect_dom_object(Box::new(buffer), global, AudioBufferBinding::Wrap)
    }

    pub fn Constructor(window: &Window,
                       options: &AudioBufferOptions) -> Fallible<DomRoot<AudioBuffer>> {
        if options.numberOfChannels > MAX_CHANNEL_COUNT {
            return Err(Error::NotSupported);
        }
        Ok(AudioBuffer::new(window, options.numberOfChannels, options.length, *options.sampleRate))
    }

    #[allow(unsafe_code)]
    fn restore_js_channel_data(&self, cx: *mut JSContext) -> bool {
        for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() {
            if !channel.get().is_null() {
                // Already have data in JS array.
                continue;
            }

            // Move the channel data from shared_channels to js_channels.
            rooted!(in (cx) let mut array = ptr::null_mut::<JSObject>());
            let shared_channel = (*self.shared_channels.lock().unwrap()).buffers.remove(i);
            if unsafe {
                Float32Array::create(cx, CreateWith::Slice(&shared_channel), array.handle_mut())
            }.is_err() {
                return false;
            }
            channel.set(array.get());
        }

        true
    }

    /// https://webaudio.github.io/web-audio-api/#acquire-the-content
    #[allow(unsafe_code)]
    pub fn acquire_contents(&self) -> Option<Arc<Mutex<ServoMediaAudioBuffer>>> {
        let cx = self.global().get_cx();
        for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() {
            // Step 1.
            if channel.get().is_null() {
                return None;
            }

            // Step 2.
            let channel_data = unsafe {
                typedarray!(in(cx) let array: Float32Array = channel.get());
                if let Ok(array) = array {
                    // XXX TypedArrays API does not expose a way to steal the buffer's
                    //     content.
                    let data = array.to_vec();
                    let _ = JS_StealArrayBufferContents(cx, channel.handle());
                    data
                } else {
                    return None;
                }
            };

            channel.set(ptr::null_mut());

            // Step 3.
            (*self.shared_channels.lock().unwrap()).buffers[i] = channel_data;

            // Step 4 will complete turning shared_channels
            // data into js_channels ArrayBuffers in restore_js_channel_data.
        }

        self.js_channels.borrow_mut().clear();

        Some(self.shared_channels.clone())
    }
}

impl AudioBufferMethods for AudioBuffer {
    /// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-samplerate
    fn SampleRate(&self) -> Finite<f32> {
        Finite::wrap(self.sample_rate)
    }

    /// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-length
    fn Length(&self) -> u32 {
        self.length
    }

    /// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-duration
    fn Duration(&self) -> Finite<f64> {
        Finite::wrap(self.duration)
    }

    /// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-numberofchannels
    fn NumberOfChannels(&self) -> u32 {
        self.number_of_channels
    }

    /// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-getchanneldata
    #[allow(unsafe_code)]
    unsafe fn GetChannelData(&self, cx: *mut JSContext, channel: u32) -> Fallible<NonNull<JSObject>> {
        if channel >= self.number_of_channels {
            return Err(Error::IndexSize);
        }

        if !self.restore_js_channel_data(cx) {
            return Err(Error::JSFailed);
        }

        Ok(NonNull::new_unchecked(self.js_channels.borrow()[channel as usize].get()))
    }

    fn CopyFromChannel(&self,
                       _destination: CustomAutoRooterGuard<Float32Array>,
                       _channel_number: u32,
                       _start_in_channel: u32) -> Fallible<()> {
        // XXX
        Ok(())
    }

    fn CopyToChannel(&self,
                     _source: CustomAutoRooterGuard<Float32Array>,
                     _channel_number: u32,
                     _start_in_channel: u32) -> Fallible<()> {
        // XXX
        Ok(())
    }
}
<commit_msg>Copy AudioBufferSourceNode buffer<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use dom::audionode::MAX_CHANNEL_COUNT;
use dom::bindings::cell::DomRefCell;
use dom::bindings::codegen::Bindings::AudioBufferBinding::{self, AudioBufferMethods, AudioBufferOptions};
use dom::bindings::error::{Error, Fallible};
use dom::bindings::num::Finite;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
use dom::bindings::root::DomRoot;
use dom::window::Window;
use dom_struct::dom_struct;
use js::jsapi::{Heap, JSContext, JSObject, JS_StealArrayBufferContents};
use js::rust::CustomAutoRooterGuard;
use js::typedarray::{CreateWith, Float32Array};
use servo_media::audio::buffer_source_node::AudioBuffer as ServoMediaAudioBuffer;
use std::ptr::{self, NonNull};

type JSAudioChannel = Heap<*mut JSObject>;

#[dom_struct]
pub struct AudioBuffer {
    reflector_: Reflector,
    js_channels: DomRefCell<Vec<JSAudioChannel>>,
    #[ignore_malloc_size_of = "servo_media"]
    shared_channels: DomRefCell<ServoMediaAudioBuffer>,
    sample_rate: f32,
    length: u32,
    duration: f64,
    number_of_channels: u32,
}

impl AudioBuffer {
    #[allow(unrooted_must_root)]
    #[allow(unsafe_code)]
    pub fn new_inherited(cx: *mut JSContext,
                         number_of_channels: u32,
                         length: u32,
                         sample_rate: f32) -> AudioBuffer {
        let initial_data = vec![0.; length as usize];
        let mut js_channels: Vec<JSAudioChannel> = Vec::with_capacity(number_of_channels as usize);
        for _ in 0..number_of_channels {
            rooted!(in (cx) let mut array = ptr::null_mut::<JSObject>());
            let _ = unsafe {
                Float32Array::create(cx, CreateWith::Slice(initial_data.as_slice()), array.handle_mut())
            };
            let js_channel = Heap::default();
            js_channel.set(array.get());
            js_channels.push(js_channel);
        }
        AudioBuffer {
            reflector_: Reflector::new(),
            js_channels: DomRefCell::new(js_channels),
            shared_channels: DomRefCell::new(ServoMediaAudioBuffer::new(number_of_channels as u8, length as usize)),
            sample_rate,
            length,
            duration: length as f64 / sample_rate as f64,
            number_of_channels,
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(global: &Window,
               number_of_channels: u32,
               length: u32,
               sample_rate: f32) -> DomRoot<AudioBuffer> {
        let buffer = AudioBuffer::new_inherited(global.get_cx(), number_of_channels, length, sample_rate);
        reflect_dom_object(Box::new(buffer), global, AudioBufferBinding::Wrap)
    }

    pub fn Constructor(window: &Window,
                       options: &AudioBufferOptions) -> Fallible<DomRoot<AudioBuffer>> {
        if options.numberOfChannels > MAX_CHANNEL_COUNT {
            return Err(Error::NotSupported);
        }
        Ok(AudioBuffer::new(window, options.numberOfChannels, options.length, *options.sampleRate))
    }

    #[allow(unsafe_code)]
    fn restore_js_channel_data(&self, cx: *mut JSContext) -> bool {
        for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() {
            if !channel.get().is_null() {
                // Already have data in JS array.
                continue;
            }

            // Move the channel data from shared_channels to js_channels.
            rooted!(in (cx) let mut array = ptr::null_mut::<JSObject>());
            let shared_channel = (*self.shared_channels.borrow_mut()).buffers.remove(i);
            if unsafe {
                Float32Array::create(cx, CreateWith::Slice(&shared_channel), array.handle_mut())
            }.is_err() {
                return false;
            }
            channel.set(array.get());
        }

        true
    }

    /// https://webaudio.github.io/web-audio-api/#acquire-the-content
    #[allow(unsafe_code)]
    pub fn acquire_contents(&self) -> Option<ServoMediaAudioBuffer> {
        let cx = self.global().get_cx();
        for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() {
            // Step 1.
            if channel.get().is_null() {
                return None;
            }

            // Step 2.
            let channel_data = unsafe {
                typedarray!(in(cx) let array: Float32Array = channel.get());
                if let Ok(array) = array {
                    // XXX TypedArrays API does not expose a way to steal the buffer's
                    //     content.
                    let data = array.to_vec();
                    let _ = JS_StealArrayBufferContents(cx, channel.handle());
                    data
                } else {
                    return None;
                }
            };

            channel.set(ptr::null_mut());

            // Step 3.
            (*self.shared_channels.borrow_mut()).buffers[i] = channel_data;

            // Step 4 will complete turning shared_channels
            // data into js_channels ArrayBuffers in restore_js_channel_data.
        }

        self.js_channels.borrow_mut().clear();

        Some((*self.shared_channels.borrow()).clone())
    }
}

impl AudioBufferMethods for AudioBuffer {
    /// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-samplerate
    fn SampleRate(&self) -> Finite<f32> {
        Finite::wrap(self.sample_rate)
    }

    /// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-length
    fn Length(&self) -> u32 {
        self.length
    }

    /// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-duration
    fn Duration(&self) -> Finite<f64> {
        Finite::wrap(self.duration)
    }

    /// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-numberofchannels
    fn NumberOfChannels(&self) -> u32 {
        self.number_of_channels
    }

    /// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-getchanneldata
    #[allow(unsafe_code)]
    unsafe fn GetChannelData(&self, cx: *mut JSContext, channel: u32) -> Fallible<NonNull<JSObject>> {
        if channel >= self.number_of_channels {
            return Err(Error::IndexSize);
        }

        if !self.restore_js_channel_data(cx) {
            return Err(Error::JSFailed);
        }

        Ok(NonNull::new_unchecked(self.js_channels.borrow()[channel as usize].get()))
    }

    fn CopyFromChannel(&self,
                       _destination: CustomAutoRooterGuard<Float32Array>,
                       _channel_number: u32,
                       _start_in_channel: u32) -> Fallible<()> {
        // XXX
        Ok(())
    }

    fn CopyToChannel(&self,
                     _source: CustomAutoRooterGuard<Float32Array>,
                     _channel_number: u32,
                     _start_in_channel: u32) -> Fallible<()> {
        // XXX
        Ok(())
    }
}
<|endoftext|>