blob: c2a78c4de3abea761a9807e1250aa6a31a1ef9af [file] [log] [blame]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::{
io::{Seek, SeekFrom, Write},
os::unix::ffi::OsStrExt,
path::Path,
process::Command,
};
use anyhow::{ensure, Result};
use crate::processors::build_event::BuildEventProcessor;
pub fn archive_logs(
output_path: &Path,
workspace_dir: &Path,
processor: &BuildEventProcessor,
) -> Result<()> {
let files = processor.get_output_group_files("transitive_logs")?;
let mut input_file = tempfile::tempfile()?;
for relative_path in files {
// Ignore non-existent files.
if workspace_dir.join(&relative_path).try_exists()? {
input_file.write_all(relative_path.as_os_str().as_bytes())?;
input_file.write_all(&[0])?;
}
}
input_file.seek(SeekFrom::Start(0))?;
let status = Command::new("tar")
.arg("--create")
.arg("--auto-compress")
.arg("--file")
.arg(output_path)
// --directory is order-sensitive. Place it between --file and
// --files-from.
.arg("--directory")
.arg(workspace_dir)
.arg("--verbatim-files-from")
.arg("--null")
.arg("--files-from")
.arg("/dev/stdin")
.stdin(input_file)
.status()?;
ensure!(status.success(), "Failed to create tarball");
Ok(())
}
#[cfg(test)]
mod tests {
use std::process::Command;
use anyhow::ensure;
use runfiles::Runfiles;
use walkdir::WalkDir;
use crate::{
load_build_events_jsonl,
proto::build_event_stream::{
BuildEvent, BuildEventId, BuildEventPayload, File, NamedSetOfFiles, NamedSetOfFilesId,
OutputGroup, TargetComplete, TargetCompletedId,
},
};
use super::*;
#[test]
fn basic() -> Result<()> {
let runfiles = Runfiles::create()?;
let events = load_build_events_jsonl(
&runfiles.rlocation("cros/bazel/portage/tools/process_artifacts/testdata/bep.jsonl"),
)?;
let processor = BuildEventProcessor::from(&events);
// Set up a workspace directory containing fake artifacts.
let workspace_dir = tempfile::TempDir::new()?;
let workspace_dir = workspace_dir.path();
for relative_path in [
"bazel-out/k8-fastbuild/bin/bazel/portage/sdk/remote_toolchain_inputs.log",
"bazel-out/k8-fastbuild/bin/bazel/portage/sdk/remote_toolchain_inputs.profile.json",
"bazel-out/k8-fastbuild/bin/bazel/portage/sdk/sdk_from_archive.log",
"bazel-out/k8-fastbuild/bin/bazel/portage/sdk/sdk_from_archive.profile.json",
"bazel-out/k8-fastbuild/bin/bazel/portage/sdk/stage1.log",
"bazel-out/k8-fastbuild/bin/bazel/portage/sdk/stage1.profile.json",
"bazel-out/k8-fastbuild/bin/external/_main~portage~portage/internal/packages/stage1/\
target/host/chromiumos/sys-kernel/linux-headers/linux-headers-4.14-r92.log",
"bazel-out/k8-fastbuild/bin/external/_main~portage~portage/internal/packages/stage1/\
target/host/chromiumos/sys-kernel/linux-headers/linux-headers-4.14-r92.profile.json",
// We create *.tbz2, but they should not be included in the tarball.
"bazel-out/k8-fastbuild/bin/external/_main~portage~portage/internal/packages/stage1/\
target/host/chromiumos/sys-kernel/linux-headers/linux-headers-4.14-r92.tbz2",
// We omit artifacts for virtual/os-headers.
] {
let path = workspace_dir.join(relative_path);
std::fs::create_dir_all(path.parent().unwrap())?;
std::fs::write(path, relative_path)?;
}
let output_path = tempfile::Builder::new().suffix(".tar.gz").tempfile()?;
let output_path = output_path.path();
// Create an archive.
archive_logs(output_path, workspace_dir, &processor)?;
// Extract a generated archive and inspect the contents.
let extract_dir = tempfile::TempDir::new()?;
let extract_dir = extract_dir.path();
let status = Command::new("tar")
.arg("--extract")
.arg("--file")
.arg(output_path)
.arg("--directory")
.arg(extract_dir)
.arg("--verbose")
.status()?;
ensure!(status.success());
let contents: Vec<String> = WalkDir::new(extract_dir)
.sort_by_file_name()
.into_iter()
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.filter(|entry| entry.file_type().is_file())
.map(|entry| {
entry
.path()
.strip_prefix(extract_dir)
.unwrap()
.to_string_lossy()
.into_owned()
})
.collect();
assert_eq!(
contents,
[
"bazel-out/k8-fastbuild/bin/bazel/portage/sdk/remote_toolchain_inputs.log",
"bazel-out/k8-fastbuild/bin/bazel/portage/sdk/remote_toolchain_inputs.profile.json",
"bazel-out/k8-fastbuild/bin/bazel/portage/sdk/sdk_from_archive.log",
"bazel-out/k8-fastbuild/bin/bazel/portage/sdk/sdk_from_archive.profile.json",
"bazel-out/k8-fastbuild/bin/bazel/portage/sdk/stage1.log",
"bazel-out/k8-fastbuild/bin/bazel/portage/sdk/stage1.profile.json",
"bazel-out/k8-fastbuild/bin/external/_main~portage~portage/internal/packages/\
stage1/target/host/chromiumos/sys-kernel/linux-headers/linux-headers-4.14-r92.log",
"bazel-out/k8-fastbuild/bin/external/_main~portage~portage/internal/packages/\
stage1/target/host/chromiumos/sys-kernel/linux-headers/\
linux-headers-4.14-r92.profile.json",
]
);
Ok(())
}
#[test]
fn empty() -> Result<()> {
let workspace_dir = tempfile::TempDir::new()?;
let workspace_dir = workspace_dir.path();
let output_path = tempfile::Builder::new().suffix(".tar.gz").tempfile()?;
let output_path = output_path.path();
let events: Vec<BuildEvent> = vec![];
let processor = BuildEventProcessor::from(&events);
archive_logs(output_path, workspace_dir, &processor)?;
Ok(())
}
#[test]
fn output_group() -> Result<()> {
let events = vec![
BuildEvent {
id: BuildEventId::NamedSet(NamedSetOfFilesId {
id: "a".to_string(),
}),
payload: BuildEventPayload::NamedSetOfFiles(NamedSetOfFiles {
files: vec![File {
name: "a.txt".to_string(),
path_prefix: vec![],
}],
file_sets: vec![],
}),
},
BuildEvent {
id: BuildEventId::NamedSet(NamedSetOfFilesId {
id: "b".to_string(),
}),
payload: BuildEventPayload::NamedSetOfFiles(NamedSetOfFiles {
files: vec![File {
name: "b.txt".to_string(),
path_prefix: vec![],
}],
file_sets: vec![],
}),
},
BuildEvent {
id: BuildEventId::TargetCompleted(TargetCompletedId {
label: "//foo".to_string(),
aspect: None,
}),
payload: BuildEventPayload::Completed(TargetComplete {
success: true,
output_group: vec![
OutputGroup {
name: "transitive_logs".to_string(),
file_sets: vec![NamedSetOfFilesId {
id: "a".to_string(),
}],
incomplete: false,
},
OutputGroup {
name: "other".to_string(),
file_sets: vec![NamedSetOfFilesId {
id: "b".to_string(),
}],
incomplete: false,
},
],
}),
},
];
let processor = BuildEventProcessor::from(&events);
let workspace_dir = tempfile::TempDir::new()?;
let workspace_dir = workspace_dir.path();
// Create a.txt, but not b.txt, so that `archive_logs` would fail if it attempts to archive
// the other output group.
std::fs::write(workspace_dir.join("a.txt"), [])?;
let output_path = tempfile::Builder::new().suffix(".tar.gz").tempfile()?;
let output_path = output_path.path();
archive_logs(output_path, workspace_dir, &processor)?;
Ok(())
}
#[test]
fn deeply_nested_named_sets() -> Result<()> {
// Create a fibonacci-like structure of named sets.
let mut events: Vec<BuildEvent> = vec![
BuildEvent {
id: BuildEventId::NamedSet(NamedSetOfFilesId {
id: "0".to_string(),
}),
payload: BuildEventPayload::NamedSetOfFiles(NamedSetOfFiles {
files: vec![],
file_sets: vec![],
}),
},
BuildEvent {
id: BuildEventId::NamedSet(NamedSetOfFilesId {
id: "1".to_string(),
}),
payload: BuildEventPayload::NamedSetOfFiles(NamedSetOfFiles {
files: vec![],
file_sets: vec![],
}),
},
];
for i in 2..=100 {
events.push(BuildEvent {
id: BuildEventId::NamedSet(NamedSetOfFilesId { id: i.to_string() }),
payload: BuildEventPayload::NamedSetOfFiles(NamedSetOfFiles {
files: vec![],
file_sets: vec![
NamedSetOfFilesId {
id: (i - 2).to_string(),
},
NamedSetOfFilesId {
id: (i - 1).to_string(),
},
],
}),
})
}
events.push(BuildEvent {
id: BuildEventId::TargetCompleted(TargetCompletedId {
label: "//foo".to_string(),
aspect: None,
}),
payload: BuildEventPayload::Completed(TargetComplete {
output_group: vec![OutputGroup {
name: "transitive_logs".to_string(),
file_sets: vec![NamedSetOfFilesId {
id: "100".to_string(),
}],
incomplete: false,
}],
success: true,
}),
});
let processor = BuildEventProcessor::from(&events);
let workspace_dir = tempfile::TempDir::new()?;
let workspace_dir = workspace_dir.path();
let output_path = tempfile::Builder::new().suffix(".tar.gz").tempfile()?;
let output_path = output_path.path();
archive_logs(output_path, workspace_dir, &processor)?;
Ok(())
}
}