Skip to content

Commit

Permalink
Add windows support
Browse files Browse the repository at this point in the history
Officially windows is now supported.

closes #235
  • Loading branch information
allada committed Aug 19, 2023
1 parent bff3be3 commit 2875f0b
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 53 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ on:
branches: [ main ]

jobs:
windows-tests:
# The type of runner that the job will run on.
runs-on: windows-latest
steps:
- uses: actions/checkout@v3.5.3
with:
fetch-depth: 0
- name: Compile on windows
run: cargo build --all
- name: Test on windows
run: cargo test --all

cargo-tests:
# The type of runner that the job will run on.
runs-on: ubuntu-22.04
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Turbo Cache is a project that implements the [Bazel Remote Execution protocol](h

When properly configured this project will provide extremely fast and efficient build cache for any systems that communicate using the [BRE protocol](https://github.com/bazelbuild/remote-apis/blob/main/build/bazel/remote/execution/v2/remote_execution.proto) and/or extremely fast, efficient and low foot-print remote execution capability.

Unix based operating systems and Windows are fully supported.

## TL;DR
To compile and run the server:
```sh
Expand Down
13 changes: 10 additions & 3 deletions cas/store/tests/filesystem_store_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ mod filesystem_store_tests {
file_entry
.get_file_path_locked(move |path| async move {
// Set atime to along time ago.
set_file_atime(&path, FileTime::zero())?;
set_file_atime(&path, FileTime::from_system_time(SystemTime::UNIX_EPOCH))?;

// Check to ensure it was set to zero from previous command.
assert_eq!(fs::metadata(&path).await?.accessed()?, SystemTime::UNIX_EPOCH);
Expand Down Expand Up @@ -605,7 +605,7 @@ mod filesystem_store_tests {
file_entry
.get_file_path_locked(move |path| async move {
// Set atime to along time ago.
set_file_atime(&path, FileTime::zero())?;
set_file_atime(&path, FileTime::from_system_time(SystemTime::UNIX_EPOCH))?;

// Check to ensure it was set to zero from previous command.
assert_eq!(fs::metadata(&path).await?.accessed()?, SystemTime::UNIX_EPOCH);
Expand Down Expand Up @@ -765,7 +765,6 @@ mod filesystem_store_tests {
yield_fn: F,
) -> Result<fs::DirEntry, Error> {
loop {
// Just in case there's a
yield_fn().await?;
let (_permit, dir_handle) = fs::read_dir(&temp_path).await?.into_inner();
let mut read_dir_stream = ReadDirStream::new(dir_handle);
Expand All @@ -775,6 +774,14 @@ mod filesystem_store_tests {
"There should only be one file in temp directory"
);
let dir_entry = dir_entry?;
{
// Some filesystems won't sync automatically, so force it.
let file_handle = fs::open_file(dir_entry.path().to_str().unwrap())
.await
.err_tip(|| "Failed to open temp file")?;
// We don't care if it fails, this is only best attempt.
let _ = file_handle.as_ref().sync_all().await;
}
// Ensure we have written to the file too. This ensures we have an open file handle.
// Failing to do this may result in the file existing, but the `update_fut` not actually
// sending data to it yet.
Expand Down
66 changes: 50 additions & 16 deletions cas/worker/running_actions_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
use std::collections::{vec_deque::VecDeque, HashMap};
use std::ffi::OsStr;
use std::fmt::Debug;
#[cfg(target_family = "unix")]
use std::fs::Permissions;
use std::io::Cursor;
#[cfg(target_family = "unix")]
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::Path;
use std::pin::Pin;
Expand Down Expand Up @@ -93,13 +95,14 @@ pub fn download_to_directory<'a>(
.try_into()
.err_tip(|| "In Directory::file::digest")?;
let dest = format!("{}/{}", current_directory, file.name);
let mut mtime = None;
let mut unix_mode = None;
if let Some(properties) = file.node_properties {
mtime = properties.mtime;
unix_mode = properties.unix_mode;
let (mtime, mut unix_mode) = match file.node_properties {
Some(properties) => (properties.mtime, properties.unix_mode),
None => (None, None),
};
#[cfg_attr(target_family = "windows", allow(unused_assignments))]
if file.is_executable {
unix_mode = Some(unix_mode.unwrap_or(0o444) | 0o111);
}
let is_executable = file.is_executable;
futures.push(
cas_store
.populate_fast_store(digest)
Expand All @@ -112,9 +115,7 @@ pub fn download_to_directory<'a>(
.get_file_path_locked(|src| fs::hard_link(src, &dest))
.await
.map_err(|e| make_err!(Code::Internal, "Could not make hardlink, {e:?} : {dest}"))?;
if is_executable {
unix_mode = Some(unix_mode.unwrap_or(0o444) | 0o111);
}
#[cfg(target_family = "unix")]
if let Some(unix_mode) = unix_mode {
fs::set_permissions(&dest, Permissions::from_mode(unix_mode))
.await
Expand Down Expand Up @@ -156,6 +157,7 @@ pub fn download_to_directory<'a>(
);
}

#[cfg(target_family = "unix")]
for symlink_node in directory.symlinks {
let dest = format!("{}/{}", current_directory, symlink_node.name);
futures.push(
Expand All @@ -175,6 +177,24 @@ pub fn download_to_directory<'a>(
.boxed()
}

#[cfg(target_family = "windows")]
async fn is_executable(_file_handle: &fs::FileSlot<'_>, _full_path: &impl AsRef<Path>) -> Result<bool, Error> {
static EXECUTABLE_EXTENSIONS: &[&str] = &["exe", "bat", "com"];
Ok(EXECUTABLE_EXTENSIONS
.iter()
.any(|ext| _full_path.as_ref().extension().map_or(false, |v| v == *ext)))
}

#[cfg(target_family = "unix")]
async fn is_executable(file_handle: &fs::FileSlot<'_>, full_path: &impl AsRef<Path>) -> Result<bool, Error> {
let metadata = file_handle
.as_ref()
.metadata()
.await
.err_tip(|| format!("While reading metadata for {:?}", full_path.as_ref()))?;
Ok((metadata.mode() & 0o001) != 0)
}

async fn upload_file<'a>(
file_handle: fs::FileSlot<'static>,
cas_store: Pin<&'a dyn Store>,
Expand All @@ -195,12 +215,9 @@ async fn upload_file<'a>(
.to_str()
.err_tip(|| make_err!(Code::Internal, "Could not convert {:?} to string", full_path))?
.to_string();
let metadata = file_handle
.as_ref()
.metadata()
.await
.err_tip(|| format!("While reading metadata for {full_path:?}"))?;
let is_executable = (metadata.mode() & 0o001) != 0;

let is_executable = is_executable(&file_handle, &full_path).await?;

Ok(FileInfo {
name_or_path: NameOrPath::Name(name),
digest,
Expand Down Expand Up @@ -549,7 +566,24 @@ impl RunningActionImpl {
.stderr(Stdio::piped())
.current_dir(format!("{}/{}", self.work_directory, command_proto.working_directory))
.env_clear();
for environment_variable in &command_proto.environment_variables {
#[cfg(target_family = "unix")]
let envs = &command_proto.environment_variables;
// If SystemRoot is not set on windows we set it to default. Failing to do
// this causes all commands to fail.
#[cfg(target_family = "windows")]
let envs = {
let mut envs = command_proto.environment_variables.clone();
if envs.iter().find(|v| v.name == "SystemRoot").is_none() {
envs.push(
proto::build::bazel::remote::execution::v2::command::EnvironmentVariable {
name: "SystemRoot".to_string(),
value: "C:\\Windows".to_string(),
},
);
}
envs
};
for environment_variable in envs {
command_builder.env(&environment_variable.name, &environment_variable.value);
}

Expand Down
33 changes: 24 additions & 9 deletions cas/worker/tests/local_worker_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@

use std::collections::HashMap;
use std::env;
#[cfg(target_family = "unix")]
use std::fs::Permissions;
#[cfg(target_family = "unix")]
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::sync::Arc;
Expand Down Expand Up @@ -77,7 +79,10 @@ mod local_worker_tests {
platform_properties.insert(
"baz".to_string(),
// Note: new lines will result in two entries for same key.
#[cfg(target_family = "unix")]
WrokerProperty::query_cmd("echo -e 'hello\ngoodbye'".to_string()),
#[cfg(target_family = "windows")]
WrokerProperty::query_cmd("cmd /C \"echo hello && echo goodbye\"".to_string()),
);
let mut test_context = setup_local_worker(platform_properties).await;
let streaming_response = test_context.maybe_streaming_response.take().unwrap();
Expand Down Expand Up @@ -378,12 +383,21 @@ mod local_worker_tests {
async fn precondition_script_fails() -> Result<(), Box<dyn std::error::Error>> {
let temp_path = make_temp_path("scripts");
fs::create_dir_all(temp_path.clone()).await?;
let precondition_script = format!("{}/precondition.sh", temp_path);
{
#[cfg(target_family = "unix")]
let precondition_script = {
let precondition_script = format!("{}/precondition.sh", temp_path);
let mut file = fs::create_file(precondition_script.clone()).await?;
file.write_all(b"#!/bin/sh\nexit 1\n").await?;
}
fs::set_permissions(&precondition_script, Permissions::from_mode(0o777)).await?;
fs::set_permissions(&precondition_script, Permissions::from_mode(0o777)).await?;
precondition_script
};
#[cfg(target_family = "windows")]
let precondition_script = {
let precondition_script = format!("{}/precondition.bat", temp_path);
let mut file = fs::create_file(precondition_script.clone()).await?;
file.write_all(b"@echo off\r\nexit 1").await?;
precondition_script
};
let local_worker_config = LocalWorkerConfig {
precondition_script: Some(precondition_script),
..Default::default()
Expand Down Expand Up @@ -451,6 +465,11 @@ mod local_worker_tests {
.expect_execution_response(Ok(Response::new(())))
.await;

#[cfg(target_family = "unix")]
const EXPECTED_MSG: &str = "Preconditions script returned status exit status: 1 - ";
#[cfg(target_family = "windows")]
const EXPECTED_MSG: &str = "Preconditions script returned status exit code: 1 - ";

// Now ensure the final results match our expectations.
assert_eq!(
execution_response,
Expand All @@ -460,11 +479,7 @@ mod local_worker_tests {
action_digest: Some(action_digest.into()),
salt: SALT,
result: Some(execute_result::Result::InternalError(
make_err!(
Code::ResourceExhausted,
"Preconditions script returned status exit status: 1 - "
)
.into()
make_err!(Code::ResourceExhausted, "{}", EXPECTED_MSG,).into()
)),
}
);
Expand Down
Loading

0 comments on commit 2875f0b

Please sign in to comment.