Skip to content

Switch to workspace dir as the include path #253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions gen/build/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
use std::error::Error as StdError;
use std::fmt::{self, Display};
use std::io;
use std::path::PathBuf;

pub(super) type Result<T, E = Error> = std::result::Result<T, E>;

#[derive(Debug)]
pub(super) enum Error {
MissingOutDir,
TargetDir,
MissingManifestDir,
CargoDirNotParent {
manifest_dir: PathBuf,
child: PathBuf,
},
Io(io::Error),
}

impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::MissingOutDir => write!(f, "missing OUT_DIR environment variable"),
Error::TargetDir => write!(f, "failed to locate target dir"),
Error::MissingManifestDir => {
write!(f, "missing CARGO_MANIFEST_DIR environment variable")
}
Error::CargoDirNotParent {
manifest_dir,
child,
} => write!(
f,
"{} is not child of {}",
child.display(),
manifest_dir.display()
),
Error::Io(err) => err.fmt(f),
}
}
Expand Down
41 changes: 31 additions & 10 deletions gen/build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,13 @@ pub fn bridge(rust_source_file: impl AsRef<Path>) -> cc::Build {
/// .flag_if_supported("-std=c++11")
/// .compile("cxxbridge-demo");
/// ```
#[must_use]
pub fn bridges(rust_source_files: impl IntoIterator<Item = impl AsRef<Path>>) -> cc::Build {
// try to clean up everything so that we don't end up with dangling files if things get moved around
if let Ok(out_dir) = paths::out_dir() {
std::fs::remove_dir_all(&out_dir).ok();
std::fs::create_dir(out_dir).ok();
}
let mut build = paths::cc_build();
build.cpp(true);
build.cpp_link_stdlib(None); // linked via link-cplusplus crate
Expand All @@ -97,26 +103,41 @@ pub fn bridges(rust_source_files: impl IntoIterator<Item = impl AsRef<Path>>) ->
process::exit(1);
}
}

if let Err(err) = write_header_file() {
writeln!(io::stderr(), "\n\ncxxbridge error: {:?}\n\n", anyhow!(err)).ok();
process::exit(1);
}
build
}

fn try_generate_bridge(build: &mut cc::Build, rust_source_file: &Path) -> Result<()> {
let header = gen::do_generate_header(rust_source_file, Opt::default());
let header_path = paths::out_with_extension(rust_source_file, ".h")?;
fs::create_dir_all(header_path.parent().unwrap())?;
fs::write(&header_path, header)?;
paths::symlink_header(&header_path, rust_source_file);
let manifest_header_path =
paths::out_with_extension(rust_source_file, ".h", paths::RelativeToDir::Manifest)?;

fs::create_dir_all(manifest_header_path.parent().unwrap())?;
fs::write(&manifest_header_path, &header)?;

let bridge = gen::do_generate_bridge(rust_source_file, Opt::default());
let bridge_path = paths::out_with_extension(rust_source_file, ".cc")?;
let bridge_path =
paths::out_with_extension(rust_source_file, ".cc", paths::RelativeToDir::Manifest)?;
fs::write(&bridge_path, bridge)?;
build.file(&bridge_path);

let ref cxx_h = paths::include_dir()?.join("rust").join("cxx.h");
let _ = fs::create_dir_all(cxx_h.parent().unwrap());
let _ = fs::remove_file(cxx_h);
let _ = fs::write(cxx_h, gen::include::HEADER);
// Write out headers relative to the workspace path as well so that includes relative to there work.
let workspace_header_path =
paths::out_with_extension(rust_source_file, ".h", paths::RelativeToDir::Workspace)?;

fs::create_dir_all(workspace_header_path.parent().unwrap())?;
fs::write(&workspace_header_path, &header)?;

Ok(())
}

fn write_header_file() -> Result<()> {
let ref cxx_h = paths::include_dir()?.join("rust").join("cxx.h");
fs::create_dir_all(cxx_h.parent().unwrap()).ok();
fs::remove_file(cxx_h).ok();
fs::write(cxx_h, gen::include::HEADER)?;
Ok(())
}
136 changes: 66 additions & 70 deletions gen/build/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,84 +3,98 @@ use std::env;
use std::fs;
use std::path::{Path, PathBuf};

fn out_dir() -> Result<PathBuf> {
pub(crate) enum RelativeToDir {
Workspace,
Manifest,
}

impl RelativeToDir {
fn dir(self) -> Result<PathBuf> {
match self {
Self::Workspace => workspace_dir(),
Self::Manifest => manifest_dir(),
}
}
}

pub(crate) fn out_dir() -> Result<PathBuf> {
env::var_os("OUT_DIR")
.map(PathBuf::from)
.ok_or(Error::MissingOutDir)
}

fn manifest_dir() -> Result<PathBuf> {
std::env::var_os("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.ok_or(Error::MissingManifestDir)
}

fn workspace_dir() -> Result<PathBuf> {
let manifest_dir = manifest_dir()?;
for workspace_dir in manifest_dir.ancestors() {
let cargo = workspace_dir.join("Cargo.toml");
if cargo.exists() {
if let Ok(workspace) = fs::read_to_string(cargo) {
let workspace = workspace.to_lowercase();
let path_to_manifest = manifest_dir
.strip_prefix(workspace_dir)
.unwrap()
.to_string_lossy();
// Leaving this so that it is clear that this needs to be handled differently on windows.
let path_to_manifest = if cfg!(windows) {
path_to_manifest.replace("\\", "/")
} else {
path_to_manifest.into_owned()
};
if workspace.contains("[workspace]")
&& workspace.contains(&format!(r#""{}""#, path_to_manifest))
{
return Ok(workspace_dir.to_path_buf());
}
}
}
}
return Ok(manifest_dir);
}

pub(crate) fn cc_build() -> cc::Build {
try_cc_build().unwrap_or_default()
}

fn try_cc_build() -> Result<cc::Build> {
let mut build = cc::Build::new();
build.include(include_dir()?);
build.include(target_dir()?.parent().unwrap());
build.include(out_dir()?);
build.include(manifest_dir()?);
build.include(workspace_dir()?);
Ok(build)
}

// Symlink the header file into a predictable place. The header generated from
// path/to/mod.rs gets linked to targets/cxxbridge/path/to/mod.rs.h.
pub(crate) fn symlink_header(path: &Path, original: &Path) {
let _ = try_symlink_header(path, original);
}

fn try_symlink_header(path: &Path, original: &Path) -> Result<()> {
let suffix = relative_to_parent_of_target_dir(original)?;
let ref dst = include_dir()?.join(suffix);

fs::create_dir_all(dst.parent().unwrap())?;
let _ = fs::remove_file(dst);
symlink_or_copy(path, dst)?;

let mut file_name = dst.file_name().unwrap().to_os_string();
file_name.push(".h");
let ref dst2 = dst.with_file_name(file_name);
symlink_or_copy(path, dst2)?;

Ok(())
}

fn relative_to_parent_of_target_dir(original: &Path) -> Result<PathBuf> {
let target_dir = target_dir()?;
let mut outer = target_dir.parent().unwrap();
fn relative_to_dir(original: &Path, relative_dir: RelativeToDir) -> Result<PathBuf> {
let relative_dir = relative_dir.dir()?;
let dir = canonicalize(relative_dir)?;
let original = canonicalize(original)?;
loop {
if let Ok(suffix) = original.strip_prefix(outer) {
return Ok(suffix.to_owned());
}
match outer.parent() {
Some(parent) => outer = parent,
None => return Ok(original.components().skip(1).collect()),
}
}

original
.strip_prefix(&dir)
.map(|p| p.to_path_buf())
.map_err(|_| Error::CargoDirNotParent {
manifest_dir: dir,
child: original,
})
}

pub(crate) fn out_with_extension(path: &Path, ext: &str) -> Result<PathBuf> {
pub(crate) fn out_with_extension(path: &Path, ext: &str, dir: RelativeToDir) -> Result<PathBuf> {
let mut file_name = path.file_name().unwrap().to_owned();
file_name.push(ext);

let out_dir = out_dir()?;
let rel = relative_to_parent_of_target_dir(path)?;
let rel = relative_to_dir(path, dir)?;
Ok(out_dir.join(rel).with_file_name(file_name))
}

pub(crate) fn include_dir() -> Result<PathBuf> {
let target_dir = target_dir()?;
Ok(target_dir.join("cxxbridge"))
}

fn target_dir() -> Result<PathBuf> {
let mut dir = out_dir().and_then(canonicalize)?;
loop {
if dir.ends_with("target") {
return Ok(dir);
}
if !dir.pop() {
return Err(Error::TargetDir);
}
}
out_dir().map(|p| p.join("cxxbridge"))
}

#[cfg(not(windows))]
Expand All @@ -96,21 +110,3 @@ fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf> {
// https://github.com/alexcrichton/cc-rs/issues/169
Ok(env::current_dir()?.join(path))
}

#[cfg(unix)]
use std::os::unix::fs::symlink as symlink_or_copy;

#[cfg(windows)]
fn symlink_or_copy(src: &Path, dst: &Path) -> Result<()> {
use std::os::windows::fs::symlink_file;

// Pre-Windows 10, symlinks require admin privileges. Since Windows 10, they
// require Developer Mode. If it fails, fall back to copying the file.
if symlink_file(src, dst).is_err() {
fs::copy(src, dst)?;
}
Ok(())
}

#[cfg(not(any(unix, windows)))]
use std::fs::copy as symlink_or_copy;
1 change: 1 addition & 0 deletions tests/ffi/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub mod ffi {
}

extern "C" {
// tests includes relative to workspace
include!("tests/ffi/tests.h");

type C;
Expand Down