1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

pub use autocxx_engine::Error as EngineError;
use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use syn::Item;
use tempfile::{tempdir, TempDir};

/// Errors returned during creation of a cc::Build from an include_cxx
/// macro.
#[derive(Debug)]
pub enum Error {
    /// The .rs file didn't exist or couldn't be read.
    FileReadError(std::io::Error),
    /// The .rs file couldn't be parsed.
    Syntax(syn::Error),
    /// The cxx module couldn't parse the code generated by autocxx.
    /// This could well be a bug in autocxx.
    InvalidCxx(EngineError),
    /// We couldn't create a temporary directory to store the c++ code.
    TempDirCreationFailed(std::io::Error),
    /// We couldn't write the c++ code to disk.
    FileWriteFail(std::io::Error),
    /// No `include_cxx` macro was found anywhere.
    NoIncludeCxxMacrosFound,
    /// Unable to parse the include_cxx macro.
    MacroParseFail(EngineError),
    /// Problem converting the `AUTOCXX_INC` environment variable
    /// to a set of canonical paths.
    IncludeDirProblem(EngineError),
}

/// Structure for use in a build.rs file to aid with conversion
/// of a `include_cxx!` macro into a `cc::Build`.
/// This structure owns a temporary directory containing
/// the generated C++ code, as well as owning the cc::Build
/// which knows how to build it.
/// Typically you'd use this from a build.rs file by
/// using `new` and then using `builder` to fetch the `cc::Build`
/// object and asking the resultant `cc::Build` to compile the code.
/// You'll also need to set the `AUTOCXX_INC` environment variable
/// to specify the path for finding header files.
pub struct Builder {
    build: cc::Build,
    _tdir: TempDir,
}

impl Builder {
    /// Construct a Builder.
    pub fn new<P1: AsRef<Path>>(rs_file: P1, autocxx_inc: &str) -> Result<Self, Error> {
        // TODO - we have taken a different approach here from cxx.
        // cxx jumps through many (probably very justifiable) hoops
        // to generate .h and .cxx files in the Cargo out directory
        // (I think). We cheat and just make a temp dir. We shouldn't.
        let tdir = tempdir().map_err(Error::TempDirCreationFailed)?;
        let mut builder = cc::Build::new();
        builder.cpp(true);
        let source = fs::read_to_string(rs_file).map_err(Error::FileReadError)?;
        // TODO - put this macro-finding code into the 'engine'
        // directory such that it can be shared with gen/cmd.
        // However, the use of cc::Build is unique to gen/build.
        let source = syn::parse_file(&source).map_err(Error::Syntax)?;
        let mut counter = 0;
        for item in source.items {
            if let Item::Macro(mac) = item {
                if mac.mac.path.is_ident("include_cxx") {
                    let mut include_cpp = autocxx_engine::IncludeCpp::new_from_syn(mac.mac)
                        .map_err(Error::MacroParseFail)?;
                    include_cpp.set_include_dirs(autocxx_inc);
                    for inc_dir in include_cpp
                        .include_dirs()
                        .map_err(Error::IncludeDirProblem)?
                    {
                        builder.include(inc_dir);
                    }
                    let generated_code = include_cpp
                        .generate_h_and_cxx()
                        .map_err(Error::InvalidCxx)?;
                    let fname = format!("gen{}.cxx", counter);
                    counter += 1;
                    let gen_cxx_path =
                        Self::write_to_file(&tdir, &fname, &generated_code.implementation)
                            .map_err(Error::FileWriteFail)?;
                    builder.file(gen_cxx_path);
                }
            }
        }
        if counter == 0 {
            Err(Error::NoIncludeCxxMacrosFound)
        } else {
            Ok(Builder {
                build: builder,
                _tdir: tdir,
            })
        }
    }

    /// Fetch the cc::Build from this.
    pub fn builder(&mut self) -> &mut cc::Build {
        &mut self.build
    }

    fn write_to_file(tdir: &TempDir, filename: &str, content: &[u8]) -> std::io::Result<PathBuf> {
        let path = tdir.path().join(filename);
        let mut f = File::create(&path)?;
        f.write_all(content)?;
        Ok(path)
    }
}