<commit_before>pub use font_cache::FontCache;

use au = gfx::geometry;
use au::au;
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use glyph::GlyphIndex;
use libc::{ c_int, c_double, c_ulong };
use native_font::NativeFont;
use ptr::{null, addr_of};
use text::text_run::TextRun;
use vec_to_ptr = vec::raw::to_ptr;

// Used to abstract over the shaper's choice of fixed int representation.
type FractionalPixel = float;

/**
A font handle. Layout can use this to calculate glyph metrics
and the renderer can use it to render text.
*/
struct Font {
    // A back reference to keep the library alive
    lib: @FontCache,
    fontbuf: @~[u8],
    native_font: NativeFont,
    metrics: FontMetrics,
}

struct RunMetrics {
    // may be negative due to negative width
    advance_width: au,

    ascent: au, // nonzero
    descent: au, // nonzero

    // this bounding box is relative to the left origin baseline.
    // so, bounding_box.position.y = -ascent
    bounding_box: Rect<au>
}

// Public API
pub trait FontMethods {
    fn measure_text(run: &TextRun, offset: uint, length: uint) -> RunMetrics;

    fn buf(&self) -> @~[u8];
    // these are used to get glyphs and advances in the case that the
    // shaper can't figure it out.
    fn glyph_index(char) -> Option<GlyphIndex>;
    fn glyph_h_advance(GlyphIndex) -> FractionalPixel;
}

pub impl Font : FontMethods {
    fn measure_text(run: &TextRun, offset: uint, length: uint) -> RunMetrics {
        // TODO: alter advance direction for RTL
        // TODO: using inter-char and inter-word spacing settings  when measuring text
        let mut advance = au(0);
        let mut bounds = Rect(Point2D(au(0), self.metrics.em_size.scale_by(-self.metrics.em_ascent)),
                          Size2D(au(0), self.metrics.em_size.scale_by(self.metrics.em_ascent + self.metrics.em_descent)));
        do run.glyphs.iter_glyphs_for_range(offset, length) |_i, glyph| {
            advance += glyph.advance()
        }

        // TODO: support loose and tight bounding boxes

        RunMetrics { advance_width: advance,
                     bounding_box: bounds,
                     ascent: self.metrics.em_size.scale_by(self.metrics.em_ascent),
                     descent: self.metrics.em_size.scale_by(self.metrics.em_descent),
                   }
    }

    fn buf(&self) -> @~[u8] {
        self.fontbuf
    }

    fn glyph_index(codepoint: char) -> Option<GlyphIndex> {
        self.native_font.glyph_index(codepoint)
    }

    fn glyph_h_advance(glyph: GlyphIndex) -> FractionalPixel {
        match self.native_font.glyph_h_advance(glyph) {
          Some(adv) => adv,
          None => /* FIXME: Need fallback strategy */ 10f as FractionalPixel
        }
    }
}

// TODO: font should compute its own metrics using native_font.
// TODO: who should own fontbuf?
fn Font(lib: @FontCache, fontbuf: @~[u8], native_font: NativeFont, metrics: FontMetrics) -> Font {
    Font {
        lib: lib,
        fontbuf : fontbuf,
        native_font : move native_font,
        metrics: move metrics
    }
}

// TODO: what are the units of these metrics? CTFont metrics calls are
// font units for a specific point size, and these are normalized by
// font units-per-em. Are there some cases where these metrics depend
// on font size? If so, need to be careful about using same sizes on
// font creation, metrics gathering, and drawing.
struct FontMetrics {
    underline_size:   float,
    underline_offset: float,
    leading:          float,
    x_height:         float,

    // how many appunits an em is equivalent to (based on point-to-au)
    em_size:          au,
    em_height:        float,
    em_ascent:        float,
    em_descent:       float,
    max_advance:      float
}

const TEST_FONT: [u8 * 33004] = #include_bin("JosefinSans-SemiBold.ttf");

fn test_font_bin() -> ~[u8] {
    return vec::from_fn(33004, |i| TEST_FONT[i]);
}

fn should_destruct_on_fail_without_leaking() {
    #[test];
    #[should_fail];

    let lib = FontCache();
    let _font = lib.get_test_font();
    fail;
}

fn should_get_glyph_indexes() {
    #[test];

    let lib = FontCache();
    let font = lib.get_test_font();
    let glyph_idx = font.glyph_index('w');
    assert glyph_idx == Some(40u as GlyphIndex);
}

fn should_get_glyph_advance() {
    #[test];
    #[ignore];

    let lib = FontCache();
    let font = lib.get_test_font();
    let x = font.glyph_h_advance(40u as GlyphIndex);
    assert x == 15f || x == 16f;
}

// Testing thread safety
fn should_get_glyph_advance_stress() {
    #[test];
    #[ignore];

    let mut ports = ~[];

    for iter::repeat(100) {
        let (chan, port) = pipes::stream();
        ports += [@move port];
        do task::spawn {
            let lib = FontCache();
            let font = lib.get_test_font();
            let x = font.glyph_h_advance(40u as GlyphIndex);
            assert x == 15f || x == 16f;
            chan.send(());
        }
    }

    for ports.each |port| {
        port.recv();
    }
}

fn should_be_able_to_create_instances_in_multiple_threads() {
    #[test];

    for iter::repeat(10u) {
        do task::spawn {
            let lib = FontCache();
            let _font = lib.get_test_font();
        }
    }
}

<commit_msg>measure_text(): actually extend text bounding box with each new glyph advance.<commit_after>pub use font_cache::FontCache;

use au = gfx::geometry;
use au::au;
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use glyph::GlyphIndex;
use libc::{ c_int, c_double, c_ulong };
use native_font::NativeFont;
use ptr::{null, addr_of};
use text::text_run::TextRun;
use vec_to_ptr = vec::raw::to_ptr;

// Used to abstract over the shaper's choice of fixed int representation.
type FractionalPixel = float;

/**
A font handle. Layout can use this to calculate glyph metrics
and the renderer can use it to render text.
*/
struct Font {
    // A back reference to keep the library alive
    lib: @FontCache,
    fontbuf: @~[u8],
    native_font: NativeFont,
    metrics: FontMetrics,
}

struct RunMetrics {
    // may be negative due to negative width
    advance_width: au,

    ascent: au, // nonzero
    descent: au, // nonzero

    // this bounding box is relative to the left origin baseline.
    // so, bounding_box.position.y = -ascent
    bounding_box: Rect<au>
}

// Public API
pub trait FontMethods {
    fn measure_text(run: &TextRun, offset: uint, length: uint) -> RunMetrics;

    fn buf(&self) -> @~[u8];
    // these are used to get glyphs and advances in the case that the
    // shaper can't figure it out.
    fn glyph_index(char) -> Option<GlyphIndex>;
    fn glyph_h_advance(GlyphIndex) -> FractionalPixel;
}

pub impl Font : FontMethods {
    fn measure_text(run: &TextRun, offset: uint, length: uint) -> RunMetrics {
        let em_size = self.metrics.em_size;
        let em_ascent = self.metrics.em_ascent;
        let em_descent = self.metrics.em_descent;

        // TODO: alter advance direction for RTL
        // TODO: using inter-char and inter-word spacing settings  when measuring text
        let mut advance = au(0);
        let mut bounds = Rect(Point2D(au(0), em_size.scale_by(-em_ascent)),
                          Size2D(au(0), em_size.scale_by(em_ascent + em_descent)));
        do run.glyphs.iter_glyphs_for_range(offset, length) |_i, glyph| {
            advance += glyph.advance();
            bounds = bounds.translate(&Point2D(glyph.advance(), au(0)));
        }

        // TODO: support loose and tight bounding boxes; using the
        // ascent+descent and advance is sometimes too generous and
        // looking at actual glyph extents can yield a tighter box.

        let metrics = RunMetrics { advance_width: advance,
                                  bounding_box: bounds,
                                  ascent: em_size.scale_by(em_ascent),
                                  descent: em_size.scale_by(em_descent),
                                 };
        return metrics;
    }

    fn buf(&self) -> @~[u8] {
        self.fontbuf
    }

    fn glyph_index(codepoint: char) -> Option<GlyphIndex> {
        self.native_font.glyph_index(codepoint)
    }

    fn glyph_h_advance(glyph: GlyphIndex) -> FractionalPixel {
        match self.native_font.glyph_h_advance(glyph) {
          Some(adv) => adv,
          None => /* FIXME: Need fallback strategy */ 10f as FractionalPixel
        }
    }
}

// TODO: font should compute its own metrics using native_font.
// TODO: who should own fontbuf?
fn Font(lib: @FontCache, fontbuf: @~[u8], native_font: NativeFont, metrics: FontMetrics) -> Font {
    Font {
        lib: lib,
        fontbuf : fontbuf,
        native_font : move native_font,
        metrics: move metrics
    }
}

// Most of these metrics are in terms of em. Use em_size to convert to au.
struct FontMetrics {
    underline_size:   float,
    underline_offset: float,
    leading:          float,
    x_height:         float,

    // how many appunits an em is equivalent to (based on point-to-au)
    em_size:          au,
    em_height:        float,
    em_ascent:        float,
    em_descent:       float,
    max_advance:      float
}

const TEST_FONT: [u8 * 33004] = #include_bin("JosefinSans-SemiBold.ttf");

fn test_font_bin() -> ~[u8] {
    return vec::from_fn(33004, |i| TEST_FONT[i]);
}

fn should_destruct_on_fail_without_leaking() {
    #[test];
    #[should_fail];

    let lib = FontCache();
    let _font = lib.get_test_font();
    fail;
}

fn should_get_glyph_indexes() {
    #[test];

    let lib = FontCache();
    let font = lib.get_test_font();
    let glyph_idx = font.glyph_index('w');
    assert glyph_idx == Some(40u as GlyphIndex);
}

fn should_get_glyph_advance() {
    #[test];
    #[ignore];

    let lib = FontCache();
    let font = lib.get_test_font();
    let x = font.glyph_h_advance(40u as GlyphIndex);
    assert x == 15f || x == 16f;
}

// Testing thread safety
fn should_get_glyph_advance_stress() {
    #[test];
    #[ignore];

    let mut ports = ~[];

    for iter::repeat(100) {
        let (chan, port) = pipes::stream();
        ports += [@move port];
        do task::spawn {
            let lib = FontCache();
            let font = lib.get_test_font();
            let x = font.glyph_h_advance(40u as GlyphIndex);
            assert x == 15f || x == 16f;
            chan.send(());
        }
    }

    for ports.each |port| {
        port.recv();
    }
}

fn should_be_able_to_create_instances_in_multiple_threads() {
    #[test];

    for iter::repeat(10u) {
        do task::spawn {
            let lib = FontCache();
            let _font = lib.get_test_font();
        }
    }
}

<|endoftext|>


<commit_before>use std::{
    path::{Path, PathBuf},
    process::Command,
};

use build_helper::t;

use crate::builder::Builder;

#[derive(Copy, Clone)]
pub(crate) enum OverlayKind {
    Rust,
    LLVM,
    Cargo,
    Clippy,
    Miri,
    Rustfmt,
    RLS,
    RustAnalyzer,
}

impl OverlayKind {
    fn legal_and_readme(&self) -> &[&str] {
        match self {
            OverlayKind::Rust => &["COPYRIGHT", "LICENSE-APACHE", "LICENSE-MIT", "README.md"],
            OverlayKind::LLVM => {
                &["src/llvm-project/llvm/LICENSE.TXT", "src/llvm-project/llvm/README.txt"]
            }
            OverlayKind::Cargo => &[
                "src/tools/cargo/README.md",
                "src/tools/cargo/LICENSE-MIT",
                "src/tools/cargo/LICENSE-APACHE",
                "src/tools/cargo/LICENSE-THIRD-PARTY",
            ],
            OverlayKind::Clippy => &[
                "src/tools/clippy/README.md",
                "src/tools/clippy/LICENSE-APACHE",
                "src/tools/clippy/LICENSE-MIT",
            ],
            OverlayKind::Miri => &[
                "src/tools/miri/README.md",
                "src/tools/miri/LICENSE-APACHE",
                "src/tools/miri/LICENSE-MIT",
            ],
            OverlayKind::Rustfmt => &[
                "src/tools/rustfmt/README.md",
                "src/tools/rustfmt/LICENSE-APACHE",
                "src/tools/rustfmt/LICENSE-MIT",
            ],
            OverlayKind::RLS => &[
                "src/tools/rls/README.md",
                "src/tools/rls/LICENSE-APACHE",
                "src/tools/rls/LICENSE-MIT",
            ],
            OverlayKind::RustAnalyzer => &[
                "src/tools/rust-analyzer/README.md",
                "src/tools/rust-analyzer/LICENSE-APACHE",
                "src/tools/rust-analyzer/LICENSE-MIT",
            ],
        }
    }

    fn version(&self, builder: &Builder<'_>) -> String {
        match self {
            OverlayKind::Rust => builder.rust_version(),
            OverlayKind::LLVM => builder.rust_version(),
            OverlayKind::Cargo => {
                builder.cargo_info.version(builder, &builder.release_num("cargo"))
            }
            OverlayKind::Clippy => {
                builder.clippy_info.version(builder, &builder.release_num("clippy"))
            }
            OverlayKind::Miri => builder.miri_info.version(builder, &builder.release_num("miri")),
            OverlayKind::Rustfmt => {
                builder.rustfmt_info.version(builder, &builder.release_num("rustfmt"))
            }
            OverlayKind::RLS => builder.rls_info.version(builder, &builder.release_num("rls")),
            OverlayKind::RustAnalyzer => builder
                .rust_analyzer_info
                .version(builder, &builder.release_num("rust-analyzer/crates/rust-analyzer")),
        }
    }
}

pub(crate) struct Tarball<'a> {
    builder: &'a Builder<'a>,

    pkgname: String,
    component: String,
    target: Option<String>,
    product_name: String,
    overlay: OverlayKind,

    temp_dir: PathBuf,
    image_dir: PathBuf,
    overlay_dir: PathBuf,

    include_target_in_component_name: bool,
    is_preview: bool,
    delete_temp_dir: bool,
}

impl<'a> Tarball<'a> {
    pub(crate) fn new(builder: &'a Builder<'a>, component: &str, target: &str) -> Self {
        Self::new_inner(builder, component, Some(target.into()))
    }

    pub(crate) fn new_targetless(builder: &'a Builder<'a>, component: &str) -> Self {
        Self::new_inner(builder, component, None)
    }

    fn new_inner(builder: &'a Builder<'a>, component: &str, target: Option<String>) -> Self {
        let pkgname = crate::dist::pkgname(builder, component);

        let mut temp_dir = builder.out.join("tmp").join("tarball");
        if let Some(target) = &target {
            temp_dir = temp_dir.join(target);
        }
        let _ = std::fs::remove_dir_all(&temp_dir);

        let image_dir = temp_dir.join("image");
        let overlay_dir = temp_dir.join("overlay");

        Self {
            builder,

            pkgname,
            component: component.into(),
            target,
            product_name: "Rust".into(),
            overlay: OverlayKind::Rust,

            temp_dir,
            image_dir,
            overlay_dir,

            include_target_in_component_name: false,
            is_preview: false,
            delete_temp_dir: true,
        }
    }

    pub(crate) fn set_overlay(&mut self, overlay: OverlayKind) {
        self.overlay = overlay;
    }

    pub(crate) fn set_product_name(&mut self, name: &str) {
        self.product_name = name.into();
    }

    pub(crate) fn include_target_in_component_name(&mut self, include: bool) {
        self.include_target_in_component_name = include;
    }

    pub(crate) fn is_preview(&mut self, is: bool) {
        self.is_preview = is;
    }

    pub(crate) fn image_dir(&self) -> &Path {
        t!(std::fs::create_dir_all(&self.image_dir));
        &self.image_dir
    }

    pub(crate) fn add_file(&self, src: impl AsRef<Path>, destdir: impl AsRef<Path>, perms: u32) {
        // create_dir_all fails to create `foo/bar/.`, so when the destination is "." this simply
        // uses the base directory as the destination directory.
        let destdir = if destdir.as_ref() == Path::new(".") {
            self.image_dir.clone()
        } else {
            self.image_dir.join(destdir.as_ref())
        };

        t!(std::fs::create_dir_all(&destdir));
        self.builder.install(src.as_ref(), &destdir, perms);
    }

    pub(crate) fn add_renamed_file(
        &self,
        src: impl AsRef<Path>,
        destdir: impl AsRef<Path>,
        new_name: &str,
    ) {
        let destdir = self.image_dir.join(destdir.as_ref());
        t!(std::fs::create_dir_all(&destdir));
        self.builder.copy(src.as_ref(), &destdir.join(new_name));
    }

    pub(crate) fn add_legal_and_readme_to(&self, destdir: impl AsRef<Path>) {
        for file in self.overlay.legal_and_readme() {
            self.add_file(self.builder.src.join(file), destdir.as_ref(), 0o644);
        }
    }

    pub(crate) fn add_dir(&self, src: impl AsRef<Path>, dest: impl AsRef<Path>) {
        let dest = self.image_dir.join(dest.as_ref());

        t!(std::fs::create_dir_all(&dest));
        self.builder.cp_r(src.as_ref(), &dest);
    }

    pub(crate) fn persist_work_dir(&mut self) -> PathBuf {
        self.delete_temp_dir = false;
        self.temp_dir.clone()
    }

    pub(crate) fn generate(self) -> PathBuf {
        let mut component_name = self.component.clone();
        if self.is_preview {
            component_name.push_str("-preview");
        }
        if self.include_target_in_component_name {
            component_name.push('-');
            component_name.push_str(
                &self
                    .target
                    .as_ref()
                    .expect("include_target_in_component_name used in a targetless tarball"),
            );
        }

        self.run(|this, cmd| {
            cmd.arg("generate")
                .arg("--image-dir")
                .arg(&this.image_dir)
                .arg(format!("--component-name={}", &component_name));
            this.non_bare_args(cmd);
        })
    }

    pub(crate) fn combine(self, tarballs: &[PathBuf]) {
        let mut input_tarballs = tarballs[0].as_os_str().to_os_string();
        for tarball in &tarballs[1..] {
            input_tarballs.push(",");
            input_tarballs.push(tarball);
        }

        self.run(|this, cmd| {
            cmd.arg("combine").arg("--input-tarballs").arg(input_tarballs);
            this.non_bare_args(cmd);
        });
    }

    pub(crate) fn bare(self) -> PathBuf {
        self.run(|this, cmd| {
            cmd.arg("tarball")
                .arg("--input")
                .arg(&this.image_dir)
                .arg("--output")
                .arg(crate::dist::distdir(this.builder).join(this.package_name()));
        })
    }

    fn package_name(&self) -> String {
        if let Some(target) = &self.target {
            format!("{}-{}", self.pkgname, target)
        } else {
            self.pkgname.clone()
        }
    }

    fn non_bare_args(&self, cmd: &mut Command) {
        cmd.arg("--rel-manifest-dir=rustlib")
            .arg("--legacy-manifest-dirs=rustlib,cargo")
            .arg(format!("--product-name={}", self.product_name))
            .arg(format!("--success-message={} installed.", self.component))
            .arg(format!("--package-name={}", self.package_name()))
            .arg("--non-installed-overlay")
            .arg(&self.overlay_dir)
            .arg("--output-dir")
            .arg(crate::dist::distdir(self.builder));
    }

    fn run(self, build_cli: impl FnOnce(&Tarball<'a>, &mut Command)) -> PathBuf {
        t!(std::fs::create_dir_all(&self.overlay_dir));
        self.builder.create(&self.overlay_dir.join("version"), &self.overlay.version(self.builder));
        if let Some(sha) = self.builder.rust_sha() {
            self.builder.create(&self.overlay_dir.join("git-commit-hash"), &sha);
        }
        for file in self.overlay.legal_and_readme() {
            self.builder.install(&self.builder.src.join(file), &self.overlay_dir, 0o644);
        }

        let mut cmd = self.builder.tool_cmd(crate::tool::Tool::RustInstaller);

        let package_name = self.package_name();
        self.builder.info(&format!("Dist {}", package_name));
        let _time = crate::util::timeit(self.builder);

        build_cli(&self, &mut cmd);
        cmd.arg("--work-dir").arg(&self.temp_dir);
        self.builder.run(&mut cmd);
        if self.delete_temp_dir {
            t!(std::fs::remove_dir_all(&self.temp_dir));
        }

        crate::dist::distdir(self.builder).join(format!("{}.tar.gz", package_name))
    }
}
<commit_msg>Auto merge of #80397 - Mark-Simulacrum:fix-bare-tarball, r=pietroalbini<commit_after>use std::{
    path::{Path, PathBuf},
    process::Command,
};

use build_helper::t;

use crate::builder::Builder;

#[derive(Copy, Clone)]
pub(crate) enum OverlayKind {
    Rust,
    LLVM,
    Cargo,
    Clippy,
    Miri,
    Rustfmt,
    RLS,
    RustAnalyzer,
}

impl OverlayKind {
    fn legal_and_readme(&self) -> &[&str] {
        match self {
            OverlayKind::Rust => &["COPYRIGHT", "LICENSE-APACHE", "LICENSE-MIT", "README.md"],
            OverlayKind::LLVM => {
                &["src/llvm-project/llvm/LICENSE.TXT", "src/llvm-project/llvm/README.txt"]
            }
            OverlayKind::Cargo => &[
                "src/tools/cargo/README.md",
                "src/tools/cargo/LICENSE-MIT",
                "src/tools/cargo/LICENSE-APACHE",
                "src/tools/cargo/LICENSE-THIRD-PARTY",
            ],
            OverlayKind::Clippy => &[
                "src/tools/clippy/README.md",
                "src/tools/clippy/LICENSE-APACHE",
                "src/tools/clippy/LICENSE-MIT",
            ],
            OverlayKind::Miri => &[
                "src/tools/miri/README.md",
                "src/tools/miri/LICENSE-APACHE",
                "src/tools/miri/LICENSE-MIT",
            ],
            OverlayKind::Rustfmt => &[
                "src/tools/rustfmt/README.md",
                "src/tools/rustfmt/LICENSE-APACHE",
                "src/tools/rustfmt/LICENSE-MIT",
            ],
            OverlayKind::RLS => &[
                "src/tools/rls/README.md",
                "src/tools/rls/LICENSE-APACHE",
                "src/tools/rls/LICENSE-MIT",
            ],
            OverlayKind::RustAnalyzer => &[
                "src/tools/rust-analyzer/README.md",
                "src/tools/rust-analyzer/LICENSE-APACHE",
                "src/tools/rust-analyzer/LICENSE-MIT",
            ],
        }
    }

    fn version(&self, builder: &Builder<'_>) -> String {
        match self {
            OverlayKind::Rust => builder.rust_version(),
            OverlayKind::LLVM => builder.rust_version(),
            OverlayKind::Cargo => {
                builder.cargo_info.version(builder, &builder.release_num("cargo"))
            }
            OverlayKind::Clippy => {
                builder.clippy_info.version(builder, &builder.release_num("clippy"))
            }
            OverlayKind::Miri => builder.miri_info.version(builder, &builder.release_num("miri")),
            OverlayKind::Rustfmt => {
                builder.rustfmt_info.version(builder, &builder.release_num("rustfmt"))
            }
            OverlayKind::RLS => builder.rls_info.version(builder, &builder.release_num("rls")),
            OverlayKind::RustAnalyzer => builder
                .rust_analyzer_info
                .version(builder, &builder.release_num("rust-analyzer/crates/rust-analyzer")),
        }
    }
}

pub(crate) struct Tarball<'a> {
    builder: &'a Builder<'a>,

    pkgname: String,
    component: String,
    target: Option<String>,
    product_name: String,
    overlay: OverlayKind,

    temp_dir: PathBuf,
    image_dir: PathBuf,
    overlay_dir: PathBuf,

    include_target_in_component_name: bool,
    is_preview: bool,
    delete_temp_dir: bool,
}

impl<'a> Tarball<'a> {
    pub(crate) fn new(builder: &'a Builder<'a>, component: &str, target: &str) -> Self {
        Self::new_inner(builder, component, Some(target.into()))
    }

    pub(crate) fn new_targetless(builder: &'a Builder<'a>, component: &str) -> Self {
        Self::new_inner(builder, component, None)
    }

    fn new_inner(builder: &'a Builder<'a>, component: &str, target: Option<String>) -> Self {
        let pkgname = crate::dist::pkgname(builder, component);

        let mut temp_dir = builder.out.join("tmp").join("tarball");
        if let Some(target) = &target {
            temp_dir = temp_dir.join(target);
        }
        let _ = std::fs::remove_dir_all(&temp_dir);

        let image_dir = temp_dir.join("image");
        let overlay_dir = temp_dir.join("overlay");

        Self {
            builder,

            pkgname,
            component: component.into(),
            target,
            product_name: "Rust".into(),
            overlay: OverlayKind::Rust,

            temp_dir,
            image_dir,
            overlay_dir,

            include_target_in_component_name: false,
            is_preview: false,
            delete_temp_dir: true,
        }
    }

    pub(crate) fn set_overlay(&mut self, overlay: OverlayKind) {
        self.overlay = overlay;
    }

    pub(crate) fn set_product_name(&mut self, name: &str) {
        self.product_name = name.into();
    }

    pub(crate) fn include_target_in_component_name(&mut self, include: bool) {
        self.include_target_in_component_name = include;
    }

    pub(crate) fn is_preview(&mut self, is: bool) {
        self.is_preview = is;
    }

    pub(crate) fn image_dir(&self) -> &Path {
        t!(std::fs::create_dir_all(&self.image_dir));
        &self.image_dir
    }

    pub(crate) fn add_file(&self, src: impl AsRef<Path>, destdir: impl AsRef<Path>, perms: u32) {
        // create_dir_all fails to create `foo/bar/.`, so when the destination is "." this simply
        // uses the base directory as the destination directory.
        let destdir = if destdir.as_ref() == Path::new(".") {
            self.image_dir.clone()
        } else {
            self.image_dir.join(destdir.as_ref())
        };

        t!(std::fs::create_dir_all(&destdir));
        self.builder.install(src.as_ref(), &destdir, perms);
    }

    pub(crate) fn add_renamed_file(
        &self,
        src: impl AsRef<Path>,
        destdir: impl AsRef<Path>,
        new_name: &str,
    ) {
        let destdir = self.image_dir.join(destdir.as_ref());
        t!(std::fs::create_dir_all(&destdir));
        self.builder.copy(src.as_ref(), &destdir.join(new_name));
    }

    pub(crate) fn add_legal_and_readme_to(&self, destdir: impl AsRef<Path>) {
        for file in self.overlay.legal_and_readme() {
            self.add_file(self.builder.src.join(file), destdir.as_ref(), 0o644);
        }
    }

    pub(crate) fn add_dir(&self, src: impl AsRef<Path>, dest: impl AsRef<Path>) {
        let dest = self.image_dir.join(dest.as_ref());

        t!(std::fs::create_dir_all(&dest));
        self.builder.cp_r(src.as_ref(), &dest);
    }

    pub(crate) fn persist_work_dir(&mut self) -> PathBuf {
        self.delete_temp_dir = false;
        self.temp_dir.clone()
    }

    pub(crate) fn generate(self) -> PathBuf {
        let mut component_name = self.component.clone();
        if self.is_preview {
            component_name.push_str("-preview");
        }
        if self.include_target_in_component_name {
            component_name.push('-');
            component_name.push_str(
                &self
                    .target
                    .as_ref()
                    .expect("include_target_in_component_name used in a targetless tarball"),
            );
        }

        self.run(|this, cmd| {
            cmd.arg("generate")
                .arg("--image-dir")
                .arg(&this.image_dir)
                .arg(format!("--component-name={}", &component_name));
            this.non_bare_args(cmd);
        })
    }

    pub(crate) fn combine(self, tarballs: &[PathBuf]) {
        let mut input_tarballs = tarballs[0].as_os_str().to_os_string();
        for tarball in &tarballs[1..] {
            input_tarballs.push(",");
            input_tarballs.push(tarball);
        }

        self.run(|this, cmd| {
            cmd.arg("combine").arg("--input-tarballs").arg(input_tarballs);
            this.non_bare_args(cmd);
        });
    }

    pub(crate) fn bare(self) -> PathBuf {
        // Bare tarballs should have the top level directory match the package
        // name, not "image". We rename the image directory just before passing
        // into rust-installer.
        let dest = self.temp_dir.join(self.package_name());
        t!(std::fs::rename(&self.image_dir, &dest));

        self.run(|this, cmd| {
            cmd.arg("tarball")
                .arg("--input")
                .arg(&dest)
                .arg("--output")
                .arg(crate::dist::distdir(this.builder).join(this.package_name()));
        })
    }

    fn package_name(&self) -> String {
        if let Some(target) = &self.target {
            format!("{}-{}", self.pkgname, target)
        } else {
            self.pkgname.clone()
        }
    }

    fn non_bare_args(&self, cmd: &mut Command) {
        cmd.arg("--rel-manifest-dir=rustlib")
            .arg("--legacy-manifest-dirs=rustlib,cargo")
            .arg(format!("--product-name={}", self.product_name))
            .arg(format!("--success-message={} installed.", self.component))
            .arg(format!("--package-name={}", self.package_name()))
            .arg("--non-installed-overlay")
            .arg(&self.overlay_dir)
            .arg("--output-dir")
            .arg(crate::dist::distdir(self.builder));
    }

    fn run(self, build_cli: impl FnOnce(&Tarball<'a>, &mut Command)) -> PathBuf {
        t!(std::fs::create_dir_all(&self.overlay_dir));
        self.builder.create(&self.overlay_dir.join("version"), &self.overlay.version(self.builder));
        if let Some(sha) = self.builder.rust_sha() {
            self.builder.create(&self.overlay_dir.join("git-commit-hash"), &sha);
        }
        for file in self.overlay.legal_and_readme() {
            self.builder.install(&self.builder.src.join(file), &self.overlay_dir, 0o644);
        }

        let mut cmd = self.builder.tool_cmd(crate::tool::Tool::RustInstaller);

        let package_name = self.package_name();
        self.builder.info(&format!("Dist {}", package_name));
        let _time = crate::util::timeit(self.builder);

        build_cli(&self, &mut cmd);
        cmd.arg("--work-dir").arg(&self.temp_dir);
        self.builder.run(&mut cmd);
        if self.delete_temp_dir {
            t!(std::fs::remove_dir_all(&self.temp_dir));
        }

        crate::dist::distdir(self.builder).join(format!("{}.tar.gz", package_name))
    }
}
<|endoftext|>


<commit_before>use std::path::{Path, PathBuf};

use async_trait::async_trait;

use crate::bundle::KbdgenBundle;

use super::{BuildStep, BuildSteps};

use generate_klc::GenerateKlc;

#[cfg(target_os = "windows")]
mod build_klc;
mod generate_klc;
mod klc;
mod layer_set;

pub struct WindowsBuild {
    pub bundle: KbdgenBundle,
    pub output_path: PathBuf,
    pub steps: Vec<Box<dyn BuildStep>>,
}

#[async_trait(?Send)]
impl BuildSteps for WindowsBuild {
    fn new(bundle: KbdgenBundle, output_path: PathBuf) -> Self {
        let steps = vec![Box::new(GenerateKlc {}) as _];
        #[cfg(target_os = "windows")]
        steps.push(Box::new(build_klc::BuildKlc {}));
        #[cfg(not(target_os = "windows"))]
        {
            tracing::warn!("Skipping BuildKlc step");
            tracing::warn!(".klc .dlls require MSKLC to build, which is only available on Windows");
        }

        Self {
            bundle,
            output_path,
            steps,
        }
    }

    fn steps(&self) -> &[Box<dyn BuildStep>] {
        &self.steps
    }

    fn bundle(&self) -> &KbdgenBundle {
        &self.bundle
    }

    fn output_path(&self) -> &Path {
        &self.output_path
    }
}
<commit_msg>Fix windows build<commit_after>use std::path::{Path, PathBuf};

use async_trait::async_trait;

use crate::bundle::KbdgenBundle;

use super::{BuildStep, BuildSteps};

use generate_klc::GenerateKlc;

#[cfg(target_os = "windows")]
mod build_klc;
mod generate_klc;
mod klc;
mod layer_set;

pub struct WindowsBuild {
    pub bundle: KbdgenBundle,
    pub output_path: PathBuf,
    pub steps: Vec<Box<dyn BuildStep>>,
}

#[async_trait(?Send)]
impl BuildSteps for WindowsBuild {
    fn new(bundle: KbdgenBundle, output_path: PathBuf) -> Self {
        let mut steps: Vec<Box<dyn BuildStep>> = vec![];
        steps.push(Box::new(GenerateKlc {}));
        #[cfg(target_os = "windows")]
        steps.push(Box::new(build_klc::BuildKlc {}));
        #[cfg(not(target_os = "windows"))]
        {
            tracing::warn!("Skipping BuildKlc step");
            tracing::warn!(".klc .dlls require MSKLC to build, which is only available on Windows");
        }

        Self {
            bundle,
            output_path,
            steps,
        }
    }

    fn steps(&self) -> &[Box<dyn BuildStep>] {
        &self.steps
    }

    fn bundle(&self) -> &KbdgenBundle {
        &self.bundle
    }

    fn output_path(&self) -> &Path {
        &self.output_path
    }
}
<|endoftext|>


<commit_before>//! Static files support.

// //! TODO: needs to re-implement actual files handling, current impl blocks
use std::io;
use std::io::Read;
use std::fmt::Write;
use std::fs::{File, DirEntry};
use std::path::{Path, PathBuf};
use std::ops::{Deref, DerefMut};

use mime_guess::get_mime_type;

use param::FromParam;
use handler::{Handler, Responder};
use headers::ContentEncoding;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use httpcodes::{HTTPOk, HTTPFound};

/// A file with an associated name; responds with the Content-Type based on the
/// file extension.
#[derive(Debug)]
pub struct NamedFile(PathBuf, File);

impl NamedFile {
    /// Attempts to open a file in read-only mode.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use actix_web::fs::NamedFile;
    ///
    /// # #[allow(unused_variables)]
    /// let file = NamedFile::open("foo.txt");
    /// ```
    pub fn open<P: AsRef<Path>>(path: P) -> io::Result<NamedFile> {
        let file = File::open(path.as_ref())?;
        Ok(NamedFile(path.as_ref().to_path_buf(), file))
    }

    /// Returns reference to the underlying `File` object.
    #[inline]
    pub fn file(&self) -> &File {
        &self.1
    }

    /// Retrieve the path of this file.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use std::io;
    /// use actix_web::fs::NamedFile;
    ///
    /// # #[allow(dead_code)]
    /// # fn path() -> io::Result<()> {
    /// let file = NamedFile::open("test.txt")?;
    /// assert_eq!(file.path().as_os_str(), "foo.txt");
    /// # Ok(())
    /// # }
    /// ```
    #[inline]
    pub fn path(&self) -> &Path {
        self.0.as_path()
    }
}

impl Deref for NamedFile {
    type Target = File;

    fn deref(&self) -> &File {
        &self.1
    }
}

impl DerefMut for NamedFile {
    fn deref_mut(&mut self) -> &mut File {
        &mut self.1
    }
}

impl Responder for NamedFile {
    type Item = HttpResponse;
    type Error = io::Error;

    fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, io::Error> {
        let mut resp = HTTPOk.build();
        resp.content_encoding(ContentEncoding::Identity);
        if let Some(ext) = self.path().extension() {
            let mime = get_mime_type(&ext.to_string_lossy());
            resp.content_type(format!("{}", mime).as_str());
        }
        let mut data = Vec::new();
        let _ = self.1.read_to_end(&mut data);
        Ok(resp.body(data).unwrap())
    }
}

/// A directory; responds with the generated directory listing.
#[derive(Debug)]
pub struct Directory{
    base: PathBuf,
    path: PathBuf
}

impl Directory {
    pub fn new(base: PathBuf, path: PathBuf) -> Directory {
        Directory {
            base: base,
            path: path
        }
    }

    fn can_list(&self, entry: &io::Result<DirEntry>) -> bool {
        if let Ok(ref entry) = *entry {
            if let Some(name) = entry.file_name().to_str() {
                if name.starts_with('.') {
                    return false
                }
            }
            if let Ok(ref md) = entry.metadata() {
                let ft = md.file_type();
                return ft.is_dir() || ft.is_file() || ft.is_symlink()
            }
        }
        false
    }
}

impl Responder for Directory {
    type Item = HttpResponse;
    type Error = io::Error;

    fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> {
        let index_of = format!("Index of {}", req.path());
        let mut body = String::new();
        let base = Path::new(req.path());

        for entry in self.path.read_dir()? {
            if self.can_list(&entry) {
                let entry = entry.unwrap();
                let p = match entry.path().strip_prefix(&self.path) {
                    Ok(p) => base.join(p),
                    Err(_) => continue
                };
                // show file url as relative to static path
                let file_url = format!("{}", p.to_string_lossy());

                // if file is a directory, add '/' to the end of the name
                if let Ok(metadata) = entry.metadata() {
                    if metadata.is_dir() {
                        let _ = write!(body, "
{}/
",
                                       file_url, entry.file_name().to_string_lossy());
                    } else {
                        let _ = write!(body, "
{}
",
                                       file_url, entry.file_name().to_string_lossy());
                    }
                } else {
                    continue
                }
            }
        }

        let html = format!("\
                            <title>{}</title>\

{}
\

\
                            {}\

\n", index_of, index_of, body);
        Ok(HTTPOk.build()
           .content_type("text/html; charset=utf-8")
           .body(html).unwrap())
    }
}

/// This enum represents all filesystem elements.
pub enum FilesystemElement {
    File(NamedFile),
    Directory(Directory),
    Redirect(HttpResponse),
}

impl Responder for FilesystemElement {
    type Item = HttpResponse;
    type Error = io::Error;

    fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> {
        match self {
            FilesystemElement::File(file) => file.respond_to(req),
            FilesystemElement::Directory(dir) => dir.respond_to(req),
            FilesystemElement::Redirect(resp) => Ok(resp),
        }
    }
}

/// Static files handling
///
/// `StaticFile` handler must be registered with `Application::handler()` method,
/// because `StaticFile` handler requires access sub-path information.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{fs, Application};
///
/// fn main() {
///     let app = Application::new()
///         .handler("/static", fs::StaticFiles::new(".", true))
///         .finish();
/// }
/// ```
pub struct StaticFiles {
    directory: PathBuf,
    accessible: bool,
    index: Option<String>,
    show_index: bool,
    _chunk_size: usize,
    _follow_symlinks: bool,
}

impl StaticFiles {
    /// Create new `StaticFiles` instance
    ///
    /// `dir` - base directory
    ///
    /// `index` - show index for directory
    pub fn new<T: Into<PathBuf>>(dir: T, index: bool) -> StaticFiles {
        let dir = dir.into();

        let (dir, access) = match dir.canonicalize() {
            Ok(dir) => {
                if dir.is_dir() {
                    (dir, true)
                } else {
                    warn!("Is not directory `{:?}`", dir);
                    (dir, false)
                }
            },
            Err(err) => {
                warn!("Static files directory `{:?}` error: {}", dir, err);
                (dir, false)
            }
        };

        StaticFiles {
            directory: dir,
            accessible: access,
            index: None,
            show_index: index,
            _chunk_size: 0,
            _follow_symlinks: false,
        }
    }

    /// Set index file
    ///
    /// Redirects to specific index file for directory "/" instead of
    /// showing files listing.
    pub fn index_file<T: Into<String>>(mut self, index: T) -> StaticFiles {
        self.index = Some(index.into());
        self
    }
}

impl<S> Handler<S> for StaticFiles {
    type Result = Result<FilesystemElement, io::Error>;

    fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
        if !self.accessible {
            Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
        } else {
            let path = if let Some(path) = req.match_info().get("tail") {
                path
            } else {
                return Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
            };

            let relpath = PathBuf::from_param(path)
                .map_err(|_| io::Error::new(io::ErrorKind::NotFound, "not found"))?;

            // full filepath
            let path = self.directory.join(&relpath).canonicalize()?;

            if path.is_dir() {
                if let Some(ref redir_index) = self.index {
                    let mut base = Path::new(req.path()).join(relpath);
                    base.push(redir_index);
                    Ok(FilesystemElement::Redirect(
                        HTTPFound
                            .build()
                            .header("LOCATION", base.to_string_lossy().as_ref())
                            .finish().unwrap()))
                } else if self.show_index {
                    Ok(FilesystemElement::Directory(Directory::new(self.directory.clone(), path)))
                } else {
                    Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
                }
            } else {
                Ok(FilesystemElement::File(NamedFile::open(path)?))
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use http::{header, StatusCode};

    #[test]
    fn test_named_file() {
        assert!(NamedFile::open("test--").is_err());
        let mut file = NamedFile::open("Cargo.toml").unwrap();
        { file.file();
          let _f: &File = &file; }
        { let _f: &mut File = &mut file; }

        let resp = file.respond_to(HttpRequest::default()).unwrap();
        assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml")
    }

    #[test]
    fn test_static_files() {
        let mut st = StaticFiles::new(".", true);
        st.accessible = false;
        assert!(st.handle(HttpRequest::default()).is_err());

        st.accessible = true;
        st.show_index = false;
        assert!(st.handle(HttpRequest::default()).is_err());

        let mut req = HttpRequest::default();
        req.match_info_mut().add("tail", "");

        st.show_index = true;
        let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
        assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/html; charset=utf-8");
        assert!(resp.body().is_binary());
        assert!(format!("{:?}", resp.body()).contains("README.md"));
    }

    #[test]
    fn test_redirec_to_index() {
        let mut st = StaticFiles::new(".", false).index_file("index.html");
        let mut req = HttpRequest::default();
        req.match_info_mut().add("tail", "guide");

        let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
        assert_eq!(resp.status(), StatusCode::FOUND);
        assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/guide/index.html");
    }
}
<commit_msg>Avoid using `Path` to calculate URIs, because it doesn't do the right thing on Windows (#67)<commit_after>//! Static files support.

// //! TODO: needs to re-implement actual files handling, current impl blocks
use std::io;
use std::io::Read;
use std::fmt::Write;
use std::fs::{File, DirEntry};
use std::path::{Path, PathBuf};
use std::ops::{Deref, DerefMut};

use mime_guess::get_mime_type;

use param::FromParam;
use handler::{Handler, Responder};
use headers::ContentEncoding;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use httpcodes::{HTTPOk, HTTPFound};

/// A file with an associated name; responds with the Content-Type based on the
/// file extension.
#[derive(Debug)]
pub struct NamedFile(PathBuf, File);

impl NamedFile {
    /// Attempts to open a file in read-only mode.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use actix_web::fs::NamedFile;
    ///
    /// # #[allow(unused_variables)]
    /// let file = NamedFile::open("foo.txt");
    /// ```
    pub fn open<P: AsRef<Path>>(path: P) -> io::Result<NamedFile> {
        let file = File::open(path.as_ref())?;
        Ok(NamedFile(path.as_ref().to_path_buf(), file))
    }

    /// Returns reference to the underlying `File` object.
    #[inline]
    pub fn file(&self) -> &File {
        &self.1
    }

    /// Retrieve the path of this file.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use std::io;
    /// use actix_web::fs::NamedFile;
    ///
    /// # #[allow(dead_code)]
    /// # fn path() -> io::Result<()> {
    /// let file = NamedFile::open("test.txt")?;
    /// assert_eq!(file.path().as_os_str(), "foo.txt");
    /// # Ok(())
    /// # }
    /// ```
    #[inline]
    pub fn path(&self) -> &Path {
        self.0.as_path()
    }
}

impl Deref for NamedFile {
    type Target = File;

    fn deref(&self) -> &File {
        &self.1
    }
}

impl DerefMut for NamedFile {
    fn deref_mut(&mut self) -> &mut File {
        &mut self.1
    }
}

impl Responder for NamedFile {
    type Item = HttpResponse;
    type Error = io::Error;

    fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, io::Error> {
        let mut resp = HTTPOk.build();
        resp.content_encoding(ContentEncoding::Identity);
        if let Some(ext) = self.path().extension() {
            let mime = get_mime_type(&ext.to_string_lossy());
            resp.content_type(format!("{}", mime).as_str());
        }
        let mut data = Vec::new();
        let _ = self.1.read_to_end(&mut data);
        Ok(resp.body(data).unwrap())
    }
}

/// A directory; responds with the generated directory listing.
#[derive(Debug)]
pub struct Directory{
    base: PathBuf,
    path: PathBuf
}

impl Directory {
    pub fn new(base: PathBuf, path: PathBuf) -> Directory {
        Directory {
            base: base,
            path: path
        }
    }

    fn can_list(&self, entry: &io::Result<DirEntry>) -> bool {
        if let Ok(ref entry) = *entry {
            if let Some(name) = entry.file_name().to_str() {
                if name.starts_with('.') {
                    return false
                }
            }
            if let Ok(ref md) = entry.metadata() {
                let ft = md.file_type();
                return ft.is_dir() || ft.is_file() || ft.is_symlink()
            }
        }
        false
    }
}

impl Responder for Directory {
    type Item = HttpResponse;
    type Error = io::Error;

    fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> {
        let index_of = format!("Index of {}", req.path());
        let mut body = String::new();
        let base = Path::new(req.path());

        for entry in self.path.read_dir()? {
            if self.can_list(&entry) {
                let entry = entry.unwrap();
                let p = match entry.path().strip_prefix(&self.path) {
                    Ok(p) => base.join(p),
                    Err(_) => continue
                };
                // show file url as relative to static path
                let file_url = format!("{}", p.to_string_lossy());

                // if file is a directory, add '/' to the end of the name
                if let Ok(metadata) = entry.metadata() {
                    if metadata.is_dir() {
                        let _ = write!(body, "
{}/
",
                                       file_url, entry.file_name().to_string_lossy());
                    } else {
                        let _ = write!(body, "
{}
",
                                       file_url, entry.file_name().to_string_lossy());
                    }
                } else {
                    continue
                }
            }
        }

        let html = format!("\
                            <title>{}</title>\

{}
\

\
                            {}\

\n", index_of, index_of, body);
        Ok(HTTPOk.build()
           .content_type("text/html; charset=utf-8")
           .body(html).unwrap())
    }
}

/// This enum represents all filesystem elements.
pub enum FilesystemElement {
    File(NamedFile),
    Directory(Directory),
    Redirect(HttpResponse),
}

impl Responder for FilesystemElement {
    type Item = HttpResponse;
    type Error = io::Error;

    fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> {
        match self {
            FilesystemElement::File(file) => file.respond_to(req),
            FilesystemElement::Directory(dir) => dir.respond_to(req),
            FilesystemElement::Redirect(resp) => Ok(resp),
        }
    }
}

/// Static files handling
///
/// `StaticFile` handler must be registered with `Application::handler()` method,
/// because `StaticFile` handler requires access sub-path information.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{fs, Application};
///
/// fn main() {
///     let app = Application::new()
///         .handler("/static", fs::StaticFiles::new(".", true))
///         .finish();
/// }
/// ```
pub struct StaticFiles {
    directory: PathBuf,
    accessible: bool,
    index: Option<String>,
    show_index: bool,
    _chunk_size: usize,
    _follow_symlinks: bool,
}

impl StaticFiles {
    /// Create new `StaticFiles` instance
    ///
    /// `dir` - base directory
    ///
    /// `index` - show index for directory
    pub fn new<T: Into<PathBuf>>(dir: T, index: bool) -> StaticFiles {
        let dir = dir.into();

        let (dir, access) = match dir.canonicalize() {
            Ok(dir) => {
                if dir.is_dir() {
                    (dir, true)
                } else {
                    warn!("Is not directory `{:?}`", dir);
                    (dir, false)
                }
            },
            Err(err) => {
                warn!("Static files directory `{:?}` error: {}", dir, err);
                (dir, false)
            }
        };

        StaticFiles {
            directory: dir,
            accessible: access,
            index: None,
            show_index: index,
            _chunk_size: 0,
            _follow_symlinks: false,
        }
    }

    /// Set index file
    ///
    /// Redirects to specific index file for directory "/" instead of
    /// showing files listing.
    pub fn index_file<T: Into<String>>(mut self, index: T) -> StaticFiles {
        self.index = Some(index.into());
        self
    }
}

impl<S> Handler<S> for StaticFiles {
    type Result = Result<FilesystemElement, io::Error>;

    fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
        if !self.accessible {
            Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
        } else {
            let path = if let Some(path) = req.match_info().get("tail") {
                path
            } else {
                return Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
            };

            let relpath = PathBuf::from_param(path)
                .map_err(|_| io::Error::new(io::ErrorKind::NotFound, "not found"))?;

            // full filepath
            let path = self.directory.join(&relpath).canonicalize()?;

            if path.is_dir() {
                if let Some(ref redir_index) = self.index {
                    // TODO: Don't redirect, just return the index content.
                    // TODO: It'd be nice if there were a good usable URL manipulation library
                    let mut new_path: String = req.path().to_owned();
                    for el in relpath.iter() {
                        new_path.push_str(&el.to_string_lossy());
                        new_path.push('/');
                    }
                    new_path.push_str(redir_index);
                    Ok(FilesystemElement::Redirect(
                        HTTPFound
                            .build()
                            .header::<_, &str>("LOCATION", &new_path)
                            .finish().unwrap()))
                } else if self.show_index {
                    Ok(FilesystemElement::Directory(Directory::new(self.directory.clone(), path)))
                } else {
                    Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
                }
            } else {
                Ok(FilesystemElement::File(NamedFile::open(path)?))
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use http::{header, StatusCode};

    #[test]
    fn test_named_file() {
        assert!(NamedFile::open("test--").is_err());
        let mut file = NamedFile::open("Cargo.toml").unwrap();
        { file.file();
          let _f: &File = &file; }
        { let _f: &mut File = &mut file; }

        let resp = file.respond_to(HttpRequest::default()).unwrap();
        assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml")
    }

    #[test]
    fn test_static_files() {
        let mut st = StaticFiles::new(".", true);
        st.accessible = false;
        assert!(st.handle(HttpRequest::default()).is_err());

        st.accessible = true;
        st.show_index = false;
        assert!(st.handle(HttpRequest::default()).is_err());

        let mut req = HttpRequest::default();
        req.match_info_mut().add("tail", "");

        st.show_index = true;
        let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
        assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/html; charset=utf-8");
        assert!(resp.body().is_binary());
        assert!(format!("{:?}", resp.body()).contains("README.md"));
    }

    #[test]
    fn test_redirect_to_index() {
        let mut st = StaticFiles::new(".", false).index_file("index.html");
        let mut req = HttpRequest::default();
        req.match_info_mut().add("tail", "guide");

        let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
        assert_eq!(resp.status(), StatusCode::FOUND);
        assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/guide/index.html");

        let mut req = HttpRequest::default();
        req.match_info_mut().add("tail", "guide/");

        let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
        assert_eq!(resp.status(), StatusCode::FOUND);
        assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/guide/index.html");
    }

    #[test]
    fn test_redirect_to_index_nested() {
        let mut st = StaticFiles::new(".", false).index_file("Cargo.toml");
        let mut req = HttpRequest::default();
        req.match_info_mut().add("tail", "examples/basics");

        let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
        assert_eq!(resp.status(), StatusCode::FOUND);
        assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/examples/basics/Cargo.toml");
    }
}
<|endoftext|>


<commit_before>// Copyright 2018 Developers of the Rand project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use crate::{util::uninit_slice_fill_zero, Error};

extern crate std;
use std::{mem::MaybeUninit, thread_local};

use js_sys::{global, Function, Uint8Array};
use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};

// Size of our temporary Uint8Array buffer used with WebCrypto methods
// Maximum is 65536 bytes see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
const WEB_CRYPTO_BUFFER_SIZE: usize = 256;
// Node.js's crypto.randomFillSync requires the size to be less than 2**31.
const NODE_MAX_BUFFER_SIZE: usize = (1 << 31) - 1;

enum RngSource {
    Node(NodeCrypto),
    Web(WebCrypto, Uint8Array),
}

// JsValues are always per-thread, so we initialize RngSource for each thread.
//   See: https://github.com/rustwasm/wasm-bindgen/pull/955
thread_local!(
    static RNG_SOURCE: Result<RngSource, Error> = getrandom_init();
);

pub(crate) fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
    RNG_SOURCE.with(|result| {
        let source = result.as_ref().map_err(|&e| e)?;

        match source {
            RngSource::Node(n) => {
                // XXX(perf): `random_fill_sync` requires a `&mut [u8]` so we
                // have to ensure the memory in `dest` is initialized.
                let dest = uninit_slice_fill_zero(dest);

                for chunk in dest.chunks_mut(NODE_MAX_BUFFER_SIZE) {
                    if n.random_fill_sync(chunk).is_err() {
                        return Err(Error::NODE_RANDOM_FILL_SYNC);
                    }
                }
            }
            RngSource::Web(crypto, buf) => {
                // getRandomValues does not work with all types of WASM memory,
                // so we initially write to browser memory to avoid exceptions.
                for chunk in dest.chunks_mut(WEB_CRYPTO_BUFFER_SIZE) {
                    // The chunk can be smaller than buf's length, so we call to
                    // JS to create a smaller view of buf without allocation.
                    let sub_buf = buf.subarray(0, chunk.len() as u32);

                    if crypto.get_random_values(&sub_buf).is_err() {
                        return Err(Error::WEB_GET_RANDOM_VALUES);
                    }

                    // SAFETY: `sub_buf`'s length is the same length as `chunk`
                    unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr() as *mut u8) };
                }
            }
        };
        Ok(())
    })
}

fn getrandom_init() -> Result<RngSource, Error> {
    let global: Global = global().unchecked_into();

    // Get the Web Crypto interface if we are in a browser, Web Worker, Deno,
    // or another environment that supports the Web Cryptography API. This
    // also allows for user-provided polyfills in unsupported environments.
    let crypto = match global.crypto() {
        // Standard Web Crypto interface
        c if c.is_object() => c,
        // Node.js CommonJS Crypto module
        _ if is_node(&global) => {
            // If module.require isn't a valid function, we are in an ES module.
            match Module::require_fn().and_then(JsCast::dyn_into::<Function>) {
                Ok(require_fn) => match require_fn.call1(&global, &JsValue::from_str("crypto")) {
                    Ok(n) => return Ok(RngSource::Node(n.unchecked_into())),
                    Err(_) => return Err(Error::NODE_CRYPTO),
                },
                Err(_) => return Err(Error::NODE_ES_MODULE),
            }
        }
        // IE 11 Workaround
        _ => match global.ms_crypto() {
            c if c.is_object() => c,
            _ => return Err(Error::WEB_CRYPTO),
        },
    };

    let buf = Uint8Array::new_with_length(WEB_CRYPTO_BUFFER_SIZE as u32);
    Ok(RngSource::Web(crypto, buf))
}

// Taken from https://www.npmjs.com/package/browser-or-node
fn is_node(global: &Global) -> bool {
    let process = global.process();
    if process.is_object() {
        let versions = process.versions();
        if versions.is_object() {
            return versions.node().is_string();
        }
    }
    false
}

#[wasm_bindgen]
extern "C" {
    // Return type of js_sys::global()
    type Global;

    // Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/)
    type WebCrypto;
    // Getters for the WebCrypto API
    #[wasm_bindgen(method, getter)]
    fn crypto(this: &Global) -> WebCrypto;
    #[wasm_bindgen(method, getter, js_name = msCrypto)]
    fn ms_crypto(this: &Global) -> WebCrypto;
    // Crypto.getRandomValues()
    #[wasm_bindgen(method, js_name = getRandomValues, catch)]
    fn get_random_values(this: &WebCrypto, buf: &Uint8Array) -> Result<(), JsValue>;

    // Node JS crypto module (https://nodejs.org/api/crypto.html)
    type NodeCrypto;
    // crypto.randomFillSync()
    #[wasm_bindgen(method, js_name = randomFillSync, catch)]
    fn random_fill_sync(this: &NodeCrypto, buf: &mut [u8]) -> Result<(), JsValue>;

    // Ideally, we would just use `fn require(s: &str)` here. However, doing
    // this causes a Webpack warning. So we instead return the function itself
    // and manually invoke it using call1. This also lets us to check that the
    // function actually exists, allowing for better error messages. See:
    //   https://github.com/rust-random/getrandom/issues/224
    //   https://github.com/rust-random/getrandom/issues/256
    type Module;
    #[wasm_bindgen(getter, static_method_of = Module, js_class = module, js_name = require, catch)]
    fn require_fn() -> Result<JsValue, JsValue>;

    // Node JS process Object (https://nodejs.org/api/process.html)
    #[wasm_bindgen(method, getter)]
    fn process(this: &Global) -> Process;
    type Process;
    #[wasm_bindgen(method, getter)]
    fn versions(this: &Process) -> Versions;
    type Versions;
    #[wasm_bindgen(method, getter)]
    fn node(this: &Versions) -> JsValue;
}
<commit_msg>Remove buffer zeroing from Node.js implementation (#315)<commit_after>// Copyright 2018 Developers of the Rand project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use crate::Error;

extern crate std;
use std::{mem::MaybeUninit, thread_local};

use js_sys::{global, Function, Uint8Array};
use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};

// Size of our temporary Uint8Array buffer used with WebCrypto methods
// Maximum is 65536 bytes see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
const WEB_CRYPTO_BUFFER_SIZE: usize = 256;
// Node.js's crypto.randomFillSync requires the size to be less than 2**31.
const NODE_MAX_BUFFER_SIZE: usize = (1 << 31) - 1;

enum RngSource {
    Node(NodeCrypto),
    Web(WebCrypto, Uint8Array),
}

// JsValues are always per-thread, so we initialize RngSource for each thread.
//   See: https://github.com/rustwasm/wasm-bindgen/pull/955
thread_local!(
    static RNG_SOURCE: Result<RngSource, Error> = getrandom_init();
);

pub(crate) fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
    RNG_SOURCE.with(|result| {
        let source = result.as_ref().map_err(|&e| e)?;

        match source {
            RngSource::Node(n) => {
                for chunk in dest.chunks_mut(NODE_MAX_BUFFER_SIZE) {
                    // SAFETY: chunk is never used directly, the memory is only
                    // modified via the Uint8Array view, which is passed
                    // directly to JavaScript. Also, crypto.randomFillSync does
                    // not resize the buffer. We know the length is less than
                    // u32::MAX because of the chunking above.
                    // Note that this uses the fact that JavaScript doesn't
                    // have a notion of "uninitialized memory", this is purely
                    // a Rust/C/C++ concept.
                    let res = n.random_fill_sync(unsafe {
                        Uint8Array::view_mut_raw(chunk.as_mut_ptr() as *mut u8, chunk.len())
                    });
                    if res.is_err() {
                        return Err(Error::NODE_RANDOM_FILL_SYNC);
                    }
                }
            }
            RngSource::Web(crypto, buf) => {
                // getRandomValues does not work with all types of WASM memory,
                // so we initially write to browser memory to avoid exceptions.
                for chunk in dest.chunks_mut(WEB_CRYPTO_BUFFER_SIZE) {
                    // The chunk can be smaller than buf's length, so we call to
                    // JS to create a smaller view of buf without allocation.
                    let sub_buf = buf.subarray(0, chunk.len() as u32);

                    if crypto.get_random_values(&sub_buf).is_err() {
                        return Err(Error::WEB_GET_RANDOM_VALUES);
                    }

                    // SAFETY: `sub_buf`'s length is the same length as `chunk`
                    unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr() as *mut u8) };
                }
            }
        };
        Ok(())
    })
}

fn getrandom_init() -> Result<RngSource, Error> {
    let global: Global = global().unchecked_into();

    // Get the Web Crypto interface if we are in a browser, Web Worker, Deno,
    // or another environment that supports the Web Cryptography API. This
    // also allows for user-provided polyfills in unsupported environments.
    let crypto = match global.crypto() {
        // Standard Web Crypto interface
        c if c.is_object() => c,
        // Node.js CommonJS Crypto module
        _ if is_node(&global) => {
            // If module.require isn't a valid function, we are in an ES module.
            match Module::require_fn().and_then(JsCast::dyn_into::<Function>) {
                Ok(require_fn) => match require_fn.call1(&global, &JsValue::from_str("crypto")) {
                    Ok(n) => return Ok(RngSource::Node(n.unchecked_into())),
                    Err(_) => return Err(Error::NODE_CRYPTO),
                },
                Err(_) => return Err(Error::NODE_ES_MODULE),
            }
        }
        // IE 11 Workaround
        _ => match global.ms_crypto() {
            c if c.is_object() => c,
            _ => return Err(Error::WEB_CRYPTO),
        },
    };

    let buf = Uint8Array::new_with_length(WEB_CRYPTO_BUFFER_SIZE as u32);
    Ok(RngSource::Web(crypto, buf))
}

// Taken from https://www.npmjs.com/package/browser-or-node
fn is_node(global: &Global) -> bool {
    let process = global.process();
    if process.is_object() {
        let versions = process.versions();
        if versions.is_object() {
            return versions.node().is_string();
        }
    }
    false
}

#[wasm_bindgen]
extern "C" {
    // Return type of js_sys::global()
    type Global;

    // Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/)
    type WebCrypto;
    // Getters for the WebCrypto API
    #[wasm_bindgen(method, getter)]
    fn crypto(this: &Global) -> WebCrypto;
    #[wasm_bindgen(method, getter, js_name = msCrypto)]
    fn ms_crypto(this: &Global) -> WebCrypto;
    // Crypto.getRandomValues()
    #[wasm_bindgen(method, js_name = getRandomValues, catch)]
    fn get_random_values(this: &WebCrypto, buf: &Uint8Array) -> Result<(), JsValue>;

    // Node JS crypto module (https://nodejs.org/api/crypto.html)
    type NodeCrypto;
    // crypto.randomFillSync()
    #[wasm_bindgen(method, js_name = randomFillSync, catch)]
    fn random_fill_sync(this: &NodeCrypto, buf: Uint8Array) -> Result<(), JsValue>;

    // Ideally, we would just use `fn require(s: &str)` here. However, doing
    // this causes a Webpack warning. So we instead return the function itself
    // and manually invoke it using call1. This also lets us to check that the
    // function actually exists, allowing for better error messages. See:
    //   https://github.com/rust-random/getrandom/issues/224
    //   https://github.com/rust-random/getrandom/issues/256
    type Module;
    #[wasm_bindgen(getter, static_method_of = Module, js_class = module, js_name = require, catch)]
    fn require_fn() -> Result<JsValue, JsValue>;

    // Node JS process Object (https://nodejs.org/api/process.html)
    #[wasm_bindgen(method, getter)]
    fn process(this: &Global) -> Process;
    type Process;
    #[wasm_bindgen(method, getter)]
    fn versions(this: &Process) -> Versions;
    type Versions;
    #[wasm_bindgen(method, getter)]
    fn node(this: &Versions) -> JsValue;
}
<|endoftext|>


<commit_before>// -*- rust -*-

import front.creader;
import front.parser;
import front.token;
import front.eval;
import front.ast;
import middle.trans;
import middle.resolve;
import middle.capture;
import middle.ty;
import middle.typeck;
import middle.typestate_check;
import back.Link;
import lib.llvm;
import util.common;

import std.fs;
import std.map.mk_hashmap;
import std.option;
import std.option.some;
import std.option.none;
import std._str;
import std._vec;
import std.io;
import std.Time;

import std.GetOpts;
import std.GetOpts.optopt;
import std.GetOpts.optmulti;
import std.GetOpts.optflag;
import std.GetOpts.opt_present;

import back.Link.output_type;

fn default_environment(session.session sess,
                       str argv0,
                       str input) -> eval.env {

    auto libc = "libc.so";
    alt (sess.get_targ_cfg().os) {
        case (session.os_win32) { libc = "msvcrt.dll"; }
        case (session.os_macos) { libc = "libc.dylib"; }
        case (session.os_linux) { libc = "libc.so.6"; }
    }

    ret
        vec(
            // Target bindings.
            tup("target_os", eval.val_str(std.os.target_os())),
            tup("target_arch", eval.val_str("x86")),
            tup("target_libc", eval.val_str(libc)),

            // Build bindings.
            tup("build_compiler", eval.val_str(argv0)),
            tup("build_input", eval.val_str(input))
            );
}

fn parse_input(session.session sess,
                      parser.parser p,
                      str input) -> @ast.crate {
    if (_str.ends_with(input, ".rc")) {
        ret parser.parse_crate_from_crate_file(p);
    } else if (_str.ends_with(input, ".rs")) {
        ret parser.parse_crate_from_source_file(p);
    }
    sess.err("unknown input file type: " + input);
    fail;
}

fn time[T](bool do_it, str what, fn()->T thunk) -> T {
    if (!do_it) { ret thunk(); }

    auto start = Time.get_time();
    auto rv = thunk();
    auto end = Time.get_time();

    // FIXME: Actually do timeval math.
    log_err #fmt("time: %s took %u s", what, (end.sec - start.sec) as uint);
    ret rv;
}

fn compile_input(session.session sess,
                 eval.env env,
                 str input, str output) {
    auto time_passes = sess.get_opts().time_passes;
    auto def = tup(0, 0);
    auto p = parser.new_parser(sess, env, def, input, 0u);
    auto crate = time[@ast.crate](time_passes, "parsing",
                                  bind parse_input(sess, p, input));
    if (sess.get_opts().output_type == Link.output_type_none) {ret;}

    crate = time[@ast.crate](time_passes, "external crate reading",
                             bind creader.read_crates(sess, crate));
    crate = time[@ast.crate](time_passes, "resolution",
                             bind resolve.resolve_crate(sess, crate));
    time[()](time_passes, "capture checking",
             bind capture.check_for_captures(sess, crate));

    auto ty_cx = ty.mk_ctxt(sess);
    auto typeck_result =
        time[typeck.typecheck_result](time_passes, "typechecking",
                                      bind typeck.check_crate(ty_cx, crate));
    crate = typeck_result._0;
    auto type_cache = typeck_result._1;

    if (sess.get_opts().run_typestate) {
        crate = time[@ast.crate](time_passes, "typestate checking",
            bind typestate_check.check_crate(crate));
    }

    auto llmod = time[llvm.ModuleRef](time_passes, "translation",
        bind trans.trans_crate(sess, crate, ty_cx, type_cache, output));

    time[()](time_passes, "LLVM passes",
             bind Link.Write.run_passes(sess, llmod, output));
}

fn pretty_print_input(session.session sess,
                             eval.env env,
                             str input) {
    auto def = tup(0, 0);
    auto p = front.parser.new_parser(sess, env, def, input, 0u);
    auto crate = front.parser.parse_crate_from_source_file(p);
    pretty.pprust.print_file(crate.node.module, input, std.io.stdout());
}

fn usage(str argv0) {
    io.stdout().write_str(#fmt("usage: %s [options] <input>\n", argv0) + "
options:

    -o <filename>      write output to <filename>
    --glue             generate glue.bc file
    --shared           compile a shared-library crate
    --pretty           pretty-print the input instead of compiling
    --ls               list the symbols defined by a crate file
    -L <path>          add a directory to the library search path
    --noverify         suppress LLVM verification step (slight speedup)
    --depend           print dependencies, in makefile-rule form
    --parse-only       parse only; do not compile, assemble, or link
    -g                 produce debug info
    -O                 optimize
    -S                 compile only; do not assemble or link
    -c                 compile and assemble, but do not link
    --save-temps       write intermediate files in addition to normal output
    --time-passes      time the individual phases of the compiler
    --sysroot <path>   override the system root (default: rustc's directory)
    --no-typestate     don't run the typestate pass (unsafe!)
    -h                 display this message\n\n");
}

fn get_os() -> session.os {
    auto s = std.os.target_os();
    if (_str.eq(s, "win32")) { ret session.os_win32; }
    if (_str.eq(s, "macos")) { ret session.os_macos; }
    if (_str.eq(s, "linux")) { ret session.os_linux; }
}

fn get_default_sysroot(str binary) -> str {
    auto dirname = fs.dirname(binary);
    if (_str.eq(dirname, binary)) { ret "."; }
    ret dirname;
}

fn main(vec[str] args) {

    // FIXME: don't hard-wire this.
    let @session.config target_cfg =
        @rec(os = get_os(),
             arch = session.arch_x86,
             int_type = common.ty_i32,
             uint_type = common.ty_u32,
             float_type = common.ty_f64);

    auto opts = vec(optflag("h"), optflag("glue"),
                    optflag("pretty"), optflag("ls"), optflag("parse-only"),
                    optflag("O"), optflag("shared"), optmulti("L"),
                    optflag("S"), optflag("c"), optopt("o"), optopt("g"),
                    optflag("save-temps"), optopt("sysroot"),
                    optflag("time-passes"), optflag("no-typestate"),
                    optflag("noverify"));
    auto binary = _vec.shift[str](args);
    auto match;
    alt (GetOpts.getopts(args, opts)) {
        case (GetOpts.failure(?f)) {
            log_err #fmt("error: %s", GetOpts.fail_str(f));
            fail;
        }
        case (GetOpts.success(?m)) { match = m; }
    }
    if (opt_present(match, "h")) {
        usage(binary);
        ret;
    }

    auto pretty = opt_present(match, "pretty");
    auto ls = opt_present(match, "ls");
    auto glue = opt_present(match, "glue");
    auto shared = opt_present(match, "shared");
    auto output_file = GetOpts.opt_maybe_str(match, "o");
    auto library_search_paths = GetOpts.opt_strs(match, "L");

    auto output_type = Link.output_type_bitcode;
    if (opt_present(match, "parse-only")) {
        output_type = Link.output_type_none;
    } else if (opt_present(match, "S")) {
        output_type = Link.output_type_assembly;
    } else if (opt_present(match, "c")) {
        output_type = Link.output_type_object;
    }

    auto verify = !opt_present(match, "noverify");
    auto save_temps = opt_present(match, "save-temps");
    // FIXME: Maybe we should support -O0, -O1, -Os, etc
    auto optimize = opt_present(match, "O");
    auto debuginfo = opt_present(match, "g");
    auto time_passes = opt_present(match, "time-passes");
    auto run_typestate = !opt_present(match, "no-typestate");
    auto sysroot_opt = GetOpts.opt_maybe_str(match, "sysroot");

    auto sysroot;
    alt (sysroot_opt) {
        case (none[str]) { sysroot = get_default_sysroot(binary); }
        case (some[str](?s)) { sysroot = s; }
    }

    let @session.options sopts =
        @rec(shared = shared,
             optimize = optimize,
             debuginfo = debuginfo,
             verify = verify,
             run_typestate = run_typestate,
             save_temps = save_temps,
             time_passes = time_passes,
             output_type = output_type,
             library_search_paths = library_search_paths,
             sysroot = sysroot);

    auto crate_cache = common.new_int_hash[session.crate_metadata]();
    auto target_crate_num = 0;
    let vec[@ast.meta_item] md = vec();
    auto sess =
        session.session(target_crate_num, target_cfg, sopts,
                        crate_cache, md, front.codemap.new_codemap());

    auto n_inputs = _vec.len[str](match.free);

    if (glue) {
        if (n_inputs > 0u) {
            sess.err("No input files allowed with --glue.");
        }
        auto out = option.from_maybe[str]("glue.bc", output_file);
        middle.trans.make_common_glue(sess, out);
        ret;
    }

    if (n_inputs == 0u) {
        sess.err("No input filename given.");
    } else if (n_inputs > 1u) {
        sess.err("Multiple input filenames provided.");
    }

    auto ifile = match.free.(0);
    auto env = default_environment(sess, args.(0), ifile);
    if (pretty) {
        pretty_print_input(sess, env, ifile);
    } else if (ls) {
        front.creader.list_file_metadata(ifile, std.io.stdout());
    } else {
        alt (output_file) {
            case (none[str]) {
                let vec[str] parts = _str.split(ifile, '.' as u8);
                _vec.pop[str](parts);
                alt (output_type) {
                    case (Link.output_type_none) { parts += vec("pp"); }
                    case (Link.output_type_bitcode) { parts += vec("bc"); }
                    case (Link.output_type_assembly) { parts += vec("s"); }
                    case (Link.output_type_object) { parts += vec("o"); }
                }
                auto ofile = _str.connect(parts, ".");
                compile_input(sess, env, ifile, ofile);
            }
            case (some[str](?ofile)) {
                compile_input(sess, env, ifile, ofile);
            }
        }
    }
}

// Local Variables:
// mode: rust
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
// End:
<commit_msg>Add --help, --version and -v flags (not yet supporting GIT_REV env var, waiting on snapshot).<commit_after>// -*- rust -*-

import front.creader;
import front.parser;
import front.token;
import front.eval;
import front.ast;
import middle.trans;
import middle.resolve;
import middle.capture;
import middle.ty;
import middle.typeck;
import middle.typestate_check;
import back.Link;
import lib.llvm;
import util.common;

import std.fs;
import std.map.mk_hashmap;
import std.option;
import std.option.some;
import std.option.none;
import std._str;
import std._vec;
import std.io;
import std.Time;

import std.GetOpts;
import std.GetOpts.optopt;
import std.GetOpts.optmulti;
import std.GetOpts.optflag;
import std.GetOpts.opt_present;

import back.Link.output_type;

fn default_environment(session.session sess,
                       str argv0,
                       str input) -> eval.env {

    auto libc = "libc.so";
    alt (sess.get_targ_cfg().os) {
        case (session.os_win32) { libc = "msvcrt.dll"; }
        case (session.os_macos) { libc = "libc.dylib"; }
        case (session.os_linux) { libc = "libc.so.6"; }
    }

    ret
        vec(
            // Target bindings.
            tup("target_os", eval.val_str(std.os.target_os())),
            tup("target_arch", eval.val_str("x86")),
            tup("target_libc", eval.val_str(libc)),

            // Build bindings.
            tup("build_compiler", eval.val_str(argv0)),
            tup("build_input", eval.val_str(input))
            );
}

fn parse_input(session.session sess,
                      parser.parser p,
                      str input) -> @ast.crate {
    if (_str.ends_with(input, ".rc")) {
        ret parser.parse_crate_from_crate_file(p);
    } else if (_str.ends_with(input, ".rs")) {
        ret parser.parse_crate_from_source_file(p);
    }
    sess.err("unknown input file type: " + input);
    fail;
}

fn time[T](bool do_it, str what, fn()->T thunk) -> T {
    if (!do_it) { ret thunk(); }

    auto start = Time.get_time();
    auto rv = thunk();
    auto end = Time.get_time();

    // FIXME: Actually do timeval math.
    log_err #fmt("time: %s took %u s", what, (end.sec - start.sec) as uint);
    ret rv;
}

fn compile_input(session.session sess,
                 eval.env env,
                 str input, str output) {
    auto time_passes = sess.get_opts().time_passes;
    auto def = tup(0, 0);
    auto p = parser.new_parser(sess, env, def, input, 0u);
    auto crate = time[@ast.crate](time_passes, "parsing",
                                  bind parse_input(sess, p, input));
    if (sess.get_opts().output_type == Link.output_type_none) {ret;}

    crate = time[@ast.crate](time_passes, "external crate reading",
                             bind creader.read_crates(sess, crate));
    crate = time[@ast.crate](time_passes, "resolution",
                             bind resolve.resolve_crate(sess, crate));
    time[()](time_passes, "capture checking",
             bind capture.check_for_captures(sess, crate));

    auto ty_cx = ty.mk_ctxt(sess);
    auto typeck_result =
        time[typeck.typecheck_result](time_passes, "typechecking",
                                      bind typeck.check_crate(ty_cx, crate));
    crate = typeck_result._0;
    auto type_cache = typeck_result._1;

    if (sess.get_opts().run_typestate) {
        crate = time[@ast.crate](time_passes, "typestate checking",
            bind typestate_check.check_crate(crate));
    }

    auto llmod = time[llvm.ModuleRef](time_passes, "translation",
        bind trans.trans_crate(sess, crate, ty_cx, type_cache, output));

    time[()](time_passes, "LLVM passes",
             bind Link.Write.run_passes(sess, llmod, output));
}

fn pretty_print_input(session.session sess,
                             eval.env env,
                             str input) {
    auto def = tup(0, 0);
    auto p = front.parser.new_parser(sess, env, def, input, 0u);
    auto crate = front.parser.parse_crate_from_source_file(p);
    pretty.pprust.print_file(crate.node.module, input, std.io.stdout());
}

fn version(str argv0) {
    auto git_rev = ""; // when snapshotted to extenv: #env("GIT_REV");
    if (_str.byte_len(git_rev) != 0u) {
        git_rev = #fmt(" (git: %s)", git_rev);
    }
    io.stdout().write_str(#fmt("%s prerelease%s\n", argv0, git_rev));
}

fn usage(str argv0) {
    io.stdout().write_str(#fmt("usage: %s [options] <input>\n", argv0) + "
options:

    -h --help          display this message
    -v --version       print version info and exit

    -o <filename>      write output to <filename>
    --glue             generate glue.bc file
    --shared           compile a shared-library crate
    --pretty           pretty-print the input instead of compiling
    --ls               list the symbols defined by a crate file
    -L <path>          add a directory to the library search path
    --noverify         suppress LLVM verification step (slight speedup)
    --depend           print dependencies, in makefile-rule form
    --parse-only       parse only; do not compile, assemble, or link
    -g                 produce debug info
    -O                 optimize
    -S                 compile only; do not assemble or link
    -c                 compile and assemble, but do not link
    --save-temps       write intermediate files in addition to normal output
    --time-passes      time the individual phases of the compiler
    --sysroot <path>   override the system root (default: rustc's directory)
    --no-typestate     don't run the typestate pass (unsafe!)\n\n");
}

fn get_os() -> session.os {
    auto s = std.os.target_os();
    if (_str.eq(s, "win32")) { ret session.os_win32; }
    if (_str.eq(s, "macos")) { ret session.os_macos; }
    if (_str.eq(s, "linux")) { ret session.os_linux; }
}

fn get_default_sysroot(str binary) -> str {
    auto dirname = fs.dirname(binary);
    if (_str.eq(dirname, binary)) { ret "."; }
    ret dirname;
}

fn main(vec[str] args) {

    // FIXME: don't hard-wire this.
    let @session.config target_cfg =
        @rec(os = get_os(),
             arch = session.arch_x86,
             int_type = common.ty_i32,
             uint_type = common.ty_u32,
             float_type = common.ty_f64);

    auto opts = vec(optflag("h"), optflag("help"),
                    optflag("v"), optflag("version"),
                    optflag("glue"),
                    optflag("pretty"), optflag("ls"), optflag("parse-only"),
                    optflag("O"), optflag("shared"), optmulti("L"),
                    optflag("S"), optflag("c"), optopt("o"), optopt("g"),
                    optflag("save-temps"), optopt("sysroot"),
                    optflag("time-passes"), optflag("no-typestate"),
                    optflag("noverify"));
    auto binary = _vec.shift[str](args);
    auto match;
    alt (GetOpts.getopts(args, opts)) {
        case (GetOpts.failure(?f)) {
            log_err #fmt("error: %s", GetOpts.fail_str(f));
            fail;
        }
        case (GetOpts.success(?m)) { match = m; }
    }
    if (opt_present(match, "h") ||
        opt_present(match, "help")) {
        usage(binary);
        ret;
    }

    if (opt_present(match, "v") ||
        opt_present(match, "version")) {
        version(binary);
        ret;
    }

    auto pretty = opt_present(match, "pretty");
    auto ls = opt_present(match, "ls");
    auto glue = opt_present(match, "glue");
    auto shared = opt_present(match, "shared");
    auto output_file = GetOpts.opt_maybe_str(match, "o");
    auto library_search_paths = GetOpts.opt_strs(match, "L");

    auto output_type = Link.output_type_bitcode;
    if (opt_present(match, "parse-only")) {
        output_type = Link.output_type_none;
    } else if (opt_present(match, "S")) {
        output_type = Link.output_type_assembly;
    } else if (opt_present(match, "c")) {
        output_type = Link.output_type_object;
    }

    auto verify = !opt_present(match, "noverify");
    auto save_temps = opt_present(match, "save-temps");
    // FIXME: Maybe we should support -O0, -O1, -Os, etc
    auto optimize = opt_present(match, "O");
    auto debuginfo = opt_present(match, "g");
    auto time_passes = opt_present(match, "time-passes");
    auto run_typestate = !opt_present(match, "no-typestate");
    auto sysroot_opt = GetOpts.opt_maybe_str(match, "sysroot");

    auto sysroot;
    alt (sysroot_opt) {
        case (none[str]) { sysroot = get_default_sysroot(binary); }
        case (some[str](?s)) { sysroot = s; }
    }

    let @session.options sopts =
        @rec(shared = shared,
             optimize = optimize,
             debuginfo = debuginfo,
             verify = verify,
             run_typestate = run_typestate,
             save_temps = save_temps,
             time_passes = time_passes,
             output_type = output_type,
             library_search_paths = library_search_paths,
             sysroot = sysroot);

    auto crate_cache = common.new_int_hash[session.crate_metadata]();
    auto target_crate_num = 0;
    let vec[@ast.meta_item] md = vec();
    auto sess =
        session.session(target_crate_num, target_cfg, sopts,
                        crate_cache, md, front.codemap.new_codemap());

    auto n_inputs = _vec.len[str](match.free);

    if (glue) {
        if (n_inputs > 0u) {
            sess.err("No input files allowed with --glue.");
        }
        auto out = option.from_maybe[str]("glue.bc", output_file);
        middle.trans.make_common_glue(sess, out);
        ret;
    }

    if (n_inputs == 0u) {
        sess.err("No input filename given.");
    } else if (n_inputs > 1u) {
        sess.err("Multiple input filenames provided.");
    }

    auto ifile = match.free.(0);
    auto env = default_environment(sess, args.(0), ifile);
    if (pretty) {
        pretty_print_input(sess, env, ifile);
    } else if (ls) {
        front.creader.list_file_metadata(ifile, std.io.stdout());
    } else {
        alt (output_file) {
            case (none[str]) {
                let vec[str] parts = _str.split(ifile, '.' as u8);
                _vec.pop[str](parts);
                alt (output_type) {
                    case (Link.output_type_none) { parts += vec("pp"); }
                    case (Link.output_type_bitcode) { parts += vec("bc"); }
                    case (Link.output_type_assembly) { parts += vec("s"); }
                    case (Link.output_type_object) { parts += vec("o"); }
                }
                auto ofile = _str.connect(parts, ".");
                compile_input(sess, env, ifile, ofile);
            }
            case (some[str](?ofile)) {
                compile_input(sess, env, ifile, ofile);
            }
        }
    }
}

// Local Variables:
// mode: rust
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
// End:
<|endoftext|>