Skip to content
Open
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
16 changes: 12 additions & 4 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# Shell entry points must remain LF so they can run before Rust tooling exists.
# The shared bootstrap parser still accepts well-formed CRLF in the two TOML
# files needed by an existing Windows worktree. Keep these rules coordinated
# with `tools/toolchain.sh` and `ci/check_tools.sh`.
# The typed CI tools audit the data formats below at source level. The shared
# repository-text boundary normalizes CRLF left in an existing Windows
# worktree. Shell entry points must themselves remain LF so they can run before
# Rust is built; `tools/toolchain.sh` then handles the two TOML values needed
# during bootstrap. Pure parsers and bare carriage returns remain strict. Keep
# this list coordinated with `tools/zc/src/ci.rs`, `repository_text.rs`, the
# bootstrap parser, and their tests so Git's index remains canonical and new
# checkouts receive LF.
*.sh text eol=lf
githooks/pre-push text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.toml text eol=lf
*.tsv text eol=lf
Comment thread
joshlf marked this conversation as resolved.
71 changes: 60 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

# Every job and action inherits this mapping. The exact keys and values are a
# source-level contract with `planned_adapter/planner.rs`; adding even a
# seemingly unrelated variable can change Cargo, rustup, the compiler, or a
# third-party action. Coordinate every change with that complete typed audit.
env:
CARGO_TERM_COLOR: always
# These are bounded, client-native retries for recognized transient download
Expand Down Expand Up @@ -59,8 +63,10 @@ jobs:
# jobs consume only the checked selectors from each matrix cell and pass them
# back to `cargo-zerocopy`, which reconstructs and executes the complete
# command without interpreting matrix data as shell text. Keep the output
# names coordinated with `tools/zc/src/github.rs`, the two `fromJSON`
# expressions below, and the Miri eligibility consumers below.
# names coordinated with `tools/zc/src/workflow_protocol.rs`, the two
# `fromJSON` expressions below, and the Miri eligibility consumers below.
# The producer fields are checked by the planned-job workflow audit in
# `tools/zc/src/planned_adapter/planner.rs`.
plan_ci:
name: Plan ordinary CI work
runs-on: ubuntu-latest
Expand All @@ -70,7 +76,7 @@ jobs:
build_matrix: ${{ steps.plan.outputs.build_matrix }}
miri_matrix: ${{ steps.plan.outputs.miri_matrix }}
# This gate is derived from the projected Miri matrix, not independently
# from the event name. Keep it coordinated with `tools/zc/src/github.rs`,
# from the event name. Keep it coordinated with `workflow_protocol.rs`,
# the Miri job condition, and the required-check aggregation.
miri_enabled: ${{ steps.plan.outputs.miri_enabled }}
env:
Expand All @@ -88,14 +94,16 @@ jobs:
# The absolute custom shell removes Bash startup-control variables and
# enables privileged mode so inherited startup files, shell options,
# and exported functions cannot turn this step into a successful no-op.
# The planner audit checks this exact shell template.
shell: /usr/bin/env -u BASH_ENV -u ENV -u SHELLOPTS -u BASHOPTS /bin/bash --noprofile --norc -p -euo pipefail -- {0}
working-directory: zerocopy
env:
EVENT_NAME: ${{ github.event_name }}
# The runner applies GITHUB_PATH after merging step env, so assign the
# fixed path directly to the child command. Do not rely on cargo.sh's
# /usr/bin/env bash shebang. Privileged mode also prevents an imported
# function from replacing a builtin when this child Bash starts.
# function from replacing a builtin when this child Bash starts. The
# planner audit checks the run block line-for-line.
run: |
set -euo pipefail
PATH=/home/runner/.cargo/bin:/usr/local/bin:/usr/bin:/bin \
Expand Down Expand Up @@ -142,7 +150,7 @@ jobs:
name: Build & Test (${{ matrix.crate }} / ${{ matrix.toolchain }} / ${{ matrix.feature_profile }} / ${{ matrix.target }})

steps:
- &matrix_checkout
-
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# `Prepare cargo-semver-checks` reads the pull request head commit by
Expand All @@ -156,7 +164,7 @@ jobs:
# artifact name. The ID identifies one immutable artifact from this exact
# run, so a renamed, stale, or Buildx-generated artifact cannot be selected
# accidentally. Checkout must remain first because this is a local action.
- &download_ci_image
-
name: Download prebuilt Docker image
uses: ./.github/actions/download-artifact-with-retry
with:
Expand All @@ -169,7 +177,7 @@ jobs:
# producer and every consumer intentionally use the same ubuntu-latest
# runner architecture. If CI gains another architecture, build and select a
# distinct artifact ID for it rather than silently sharing this archive.
- &load_ci_image
-
name: Load prebuilt Docker image
shell: bash
env:
Expand All @@ -187,7 +195,7 @@ jobs:
# that the archive matches this runner's architecture.
docker run --rm "$IMAGE_NAME" true

- &create_docker_shell
-
name: Create Docker Shell Wrapper
shell: bash
run: |
Expand Down Expand Up @@ -383,9 +391,50 @@ jobs:
name: Miri (${{ matrix.crate }} / ${{ matrix.feature_profile }} / ${{ matrix.miri_model }} / ${{ matrix.target }})

steps:
- *matrix_checkout
- *download_ci_image
- *load_ci_image
-
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# `Prepare cargo-semver-checks` reads the pull request head commit by
# its explicit SHA. Keep this depth large enough to include that parent
# of GitHub's synthetic pull request merge commit. A missing object
# makes `git log` fail rather than checking a different message.
fetch-depth: 2
persist-credentials: false

# The producer exposes the ID assigned by upload-artifact, rather than an
# artifact name. The ID identifies one immutable artifact from this exact
# run, so a renamed, stale, or Buildx-generated artifact cannot be selected
# accidentally. Checkout must remain first because this is a local action.
-
name: Download prebuilt Docker image
uses: ./.github/actions/download-artifact-with-retry
with:
artifact-id: ${{ needs.build_docker_env.outputs.image_artifact_id }}
path: ${{ runner.temp }}
expected-file: ${{ env.ZC_CI_IMAGE_ARCHIVE }}

# This step runs before /tmp/docker-shell.sh exists, so its explicit Bash
# shell is load-bearing. The Docker exporter is single-platform; the
# producer and every consumer intentionally use the same ubuntu-latest
# runner architecture. If CI gains another architecture, build and select a
# distinct artifact ID for it rather than silently sharing this archive.
-
name: Load prebuilt Docker image
shell: bash
env:
IMAGE_ARCHIVE: ${{ runner.temp }}/${{ env.ZC_CI_IMAGE_ARCHIVE }}
IMAGE_NAME: ${{ env.ZC_CI_IMAGE }}
run: |
set -euo pipefail
# Keep the large archive only as long as `docker load` needs it. The
# trap also reclaims it if loading or validation fails, avoiding archive
# plus expanded-image disk pressure for later diagnostic steps.
trap 'rm -f -- "$IMAGE_ARCHIVE"' EXIT
docker load --input "$IMAGE_ARCHIVE"
docker image inspect "$IMAGE_NAME" >/dev/null
# `inspect` proves the tag exists; running a trivial command also proves
# that the archive matches this runner's architecture.
docker run --rm "$IMAGE_NAME" true

# As with ordinary cells, the workflow passes only selectors. Model flags,
# feature arguments, the Cargo configuration transaction, and target
Expand Down
1 change: 1 addition & 0 deletions tools/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tools/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ license = "BSD-2-Clause OR Apache-2.0 OR MIT"
publish = false

[workspace.dependencies]
libyaml-rs = "0.3.0"
regex = "1"
serde = { version = "1", features = ["derive"] }
thiserror = "2"
Expand Down
34 changes: 17 additions & 17 deletions tools/generate-readme/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# Copyright 2024 The Fuchsia Authors
#
# Licensed under a BSD-style license <LICENSE-BSD>, 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.
[package]
edition.workspace = true
name = "generate-readme"
version.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
regex.workspace = true
# Copyright 2024 The Fuchsia Authors
#
# Licensed under a BSD-style license <LICENSE-BSD>, 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.

[package]
edition.workspace = true
name = "generate-readme"
version.workspace = true
license.workspace = true
publish.workspace = true

[dependencies]
regex.workspace = true
1 change: 1 addition & 0 deletions tools/zc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ publish.workspace = true

[dependencies]
cargo_metadata = "0.23.1"
libyaml-rs.workspace = true
same-file = "1.0.6"
serde.workspace = true
serde_json = "1"
Expand Down
49 changes: 40 additions & 9 deletions tools/zc/src/baseline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ use std::{
collections::{BTreeMap, BTreeSet},
error::Error,
fmt,
fs::{self, File},
io::{self, Read},
fs::File,
io,
path::{Component, Path, PathBuf},
str::FromStr,
};

use crate::identifier::{self, IdentifierError, MAX_ID_BYTES};
use crate::{
identifier::{self, IdentifierError, MAX_ID_BYTES},
repository_text,
};
const MANIFEST_HEADER: &str = "key\tvalue";
const BUILD_HEADER: &str = "crate\ttoolchain\tfeature_profile\ttarget";
const MIRI_HEADER: &str = "crate\ttoolchain\tfeature_profile\ttarget\tmiri_model\tmiri_model_flags";
Expand Down Expand Up @@ -1122,7 +1125,7 @@ impl Error for BaselineError {
}

fn read_source(path: &Path) -> Result<String, BaselineError> {
fs::read_to_string(path).map_err(|source| BaselineError {
repository_text::read(path).map_err(|source| BaselineError {
Comment thread
joshlf marked this conversation as resolved.
path: path.to_path_buf(),
line: None,
message: format!("failed to read file: {source}"),
Expand All @@ -1131,15 +1134,12 @@ fn read_source(path: &Path) -> Result<String, BaselineError> {
}

fn read_open_source(path: &Path, file: &File) -> Result<String, BaselineError> {
let mut source = String::new();
let mut reader = file;
reader.read_to_string(&mut source).map_err(|source| BaselineError {
repository_text::read_open(file).map_err(|source| BaselineError {
path: path.to_path_buf(),
line: None,
message: format!("failed to read file: {source}"),
source: Some(source),
})?;
Ok(source)
})
}

struct BaselineSources<'a> {
Expand Down Expand Up @@ -2419,6 +2419,37 @@ mod tests {
.contains("comments must not contain control characters"));
}

#[test]
fn retained_baseline_handles_normalize_only_well_formed_crlf() {
use std::{
fs, process,
sync::atomic::{AtomicU64, Ordering},
};

static NEXT_FILE: AtomicU64 = AtomicU64::new(0);
let unique = NEXT_FILE.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir()
.join(format!("zerocopy-open-baseline-text-test-{}-{unique}.tsv", process::id()));

// `CiInputs::load` deliberately retains already-open baseline handles
// so identity checks and parsing inspect the same file. Prove that
// this identity-preserving path has exactly the repository-text
// boundary promised for path-based reads: complete CRLF records are
// normalized, while an isolated carriage return remains invalid.
fs::write(&path, "header\r\nrow\r\n").unwrap();
let file = File::open(&path).unwrap();
assert_eq!(read_open_source(Path::new("baseline.tsv"), &file).unwrap(), "header\nrow\n");
drop(file);

fs::write(&path, "header\rrow\n").unwrap();
let file = File::open(&path).unwrap();
let error = read_open_source(Path::new("baseline.tsv"), &file).unwrap_err();
assert!(error.to_string().contains("bare carriage return"), "{error}");
drop(file);

fs::remove_file(path).unwrap();
}

#[test]
fn baseline_identifiers_enforce_the_shared_byte_bound() {
let maximum = "a".repeat(MAX_ID_BYTES);
Expand Down
Loading
Loading