Skip to content
Merged
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
5 changes: 4 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ jobs:
- os: macos-latest
target: x86_64-apple-darwin
suffix: ""
- os: macos-14
target: aarch64-apple-darwin
suffix: ""
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
suffix: ""
Expand Down Expand Up @@ -86,7 +89,7 @@ jobs:
run: cross build --release --target ${{ matrix.target }}

- name: Codesign executable
if: ${{ matrix.target == 'x86_64-apple-darwin' }}
if: ${{ contains(matrix.target, 'apple-darwin') }}
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
Expand Down
117 changes: 116 additions & 1 deletion src/commands/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,7 @@ rpm --root="$AVOCADO_EXT_SYSROOTS/{extension_name}" --dbpath=/var/lib/extension.
})?;

// Create the image creation script
let source_date_epoch = config.source_date_epoch.unwrap_or(0);
let image_script = format!(
r#"
set -e
Expand All @@ -818,12 +819,16 @@ if [ ! -d "$AVOCADO_EXT_SYSROOTS/$EXT_NAME" ]; then
exit 1
fi

# Ensure reproducible timestamps
export SOURCE_DATE_EPOCH={source_date_epoch}

# Create squashfs image from the versioned extension sysroot
mksquashfs \
"$AVOCADO_EXT_SYSROOTS/$EXT_NAME" \
"$OUTPUT_FILE" \
-noappend \
-no-xattrs
-no-xattrs \
-reproducible

echo "Successfully created image for versioned extension '$EXT_NAME-$EXT_VERSION' at $OUTPUT_FILE"
"#
Expand Down Expand Up @@ -1791,4 +1796,114 @@ mod tests {
assert_eq!(cmd.container_args, None);
assert_eq!(cmd.dnf_args, None);
}

/// Helper that replicates the versioned extension image script template
/// from `create_versioned_extension_image` so we can unit-test the
/// SOURCE_DATE_EPOCH interpolation without needing a container.
fn build_versioned_image_script(
extension_name: &str,
ext_version: &str,
source_date_epoch: u64,
) -> String {
format!(
r#"
set -e

# Common variables
EXT_NAME="{extension_name}"
EXT_VERSION="{ext_version}"
OUTPUT_DIR="$AVOCADO_PREFIX/output/extensions"
OUTPUT_FILE="$OUTPUT_DIR/$EXT_NAME-$EXT_VERSION.raw"

# Create output directory
mkdir -p $OUTPUT_DIR

# Remove existing file if it exists (including any old versions)
rm -f "$OUTPUT_DIR/$EXT_NAME"*.raw

# Check if extension sysroot exists
if [ ! -d "$AVOCADO_EXT_SYSROOTS/$EXT_NAME" ]; then
echo "Extension sysroot does not exist: $AVOCADO_EXT_SYSROOTS/$EXT_NAME."
exit 1
fi

# Ensure reproducible timestamps
export SOURCE_DATE_EPOCH={source_date_epoch}

# Create squashfs image from the versioned extension sysroot
mksquashfs \
"$AVOCADO_EXT_SYSROOTS/$EXT_NAME" \
"$OUTPUT_FILE" \
-noappend \
-no-xattrs \
-reproducible

echo "Successfully created image for versioned extension '$EXT_NAME-$EXT_VERSION' at $OUTPUT_FILE"
"#
)
}

#[test]
fn test_versioned_image_script_source_date_epoch_default() {
let script = build_versioned_image_script("my-ext", "1.0.0", 0);

assert!(
script.contains("export SOURCE_DATE_EPOCH=0"),
"script should set SOURCE_DATE_EPOCH=0 when default is used"
);
assert!(
script.contains("-reproducible"),
"script should include -reproducible flag"
);
assert!(
script.contains("mksquashfs"),
"script should invoke mksquashfs"
);
}

#[test]
fn test_versioned_image_script_source_date_epoch_custom() {
let script = build_versioned_image_script("my-ext", "1.0.0", 1700000000);

assert!(
script.contains("export SOURCE_DATE_EPOCH=1700000000"),
"script should set SOURCE_DATE_EPOCH to the custom value"
);
assert!(
!script.contains("SOURCE_DATE_EPOCH=0"),
"script should not contain the default value when a custom one is set"
);
}

#[test]
fn test_versioned_image_script_extension_name_and_version() {
let script = build_versioned_image_script("test-extension", "2.3.4", 0);

assert!(
script.contains("EXT_NAME=\"test-extension\""),
"script should contain the extension name"
);
assert!(
script.contains("EXT_VERSION=\"2.3.4\""),
"script should contain the extension version"
);
}

#[test]
fn test_versioned_image_script_reproducible_flags() {
let script = build_versioned_image_script("my-ext", "1.0.0", 0);

assert!(
script.contains("-reproducible"),
"script should include -reproducible flag"
);
assert!(
script.contains("-noappend"),
"script should include -noappend flag"
);
assert!(
script.contains("-no-xattrs"),
"script should include -no-xattrs flag"
);
}
}
114 changes: 110 additions & 4 deletions src/commands/ext/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ impl ExtImageCommand {
OutputLevel::Normal,
);

let source_date_epoch = config.source_date_epoch.unwrap_or(0);

let result = self
.create_image(
&container_helper,
Expand All @@ -372,6 +374,7 @@ impl ExtImageCommand {
repo_url.as_ref(),
repo_release.as_ref(),
&merged_container_args,
source_date_epoch,
)
.await?;

Expand Down Expand Up @@ -441,9 +444,10 @@ impl ExtImageCommand {
repo_url: Option<&String>,
repo_release: Option<&String>,
merged_container_args: &Option<Vec<String>>,
source_date_epoch: u64,
) -> Result<bool> {
// Create the build script
let build_script = self.create_build_script(ext_version, extension_type);
let build_script = self.create_build_script(ext_version, extension_type, source_date_epoch);

// Execute the build script in the SDK container
if self.verbose {
Expand All @@ -470,7 +474,12 @@ impl ExtImageCommand {
Ok(result)
}

fn create_build_script(&self, ext_version: &str, _extension_type: &str) -> String {
fn create_build_script(
&self,
ext_version: &str,
_extension_type: &str,
source_date_epoch: u64,
) -> String {
format!(
r#"
set -e
Expand All @@ -493,16 +502,22 @@ if [ ! -d "$AVOCADO_EXT_SYSROOTS/$EXT_NAME" ]; then
exit 1
fi

# Ensure reproducible timestamps
export SOURCE_DATE_EPOCH={source_date_epoch}

# Create squashfs image
mksquashfs \
"$AVOCADO_EXT_SYSROOTS/$EXT_NAME" \
"$OUTPUT_FILE" \
-noappend \
-no-xattrs
-no-xattrs \
-reproducible

echo "Created extension image: $OUTPUT_FILE"
"#,
self.extension, ext_version
self.extension,
ext_version,
source_date_epoch = source_date_epoch
)
}

Expand Down Expand Up @@ -537,3 +552,94 @@ echo "Created extension image: $OUTPUT_FILE"
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

fn make_cmd(extension: &str) -> ExtImageCommand {
ExtImageCommand::new(
extension.to_string(),
"avocado.yaml".to_string(),
false,
None,
None,
None,
)
}

#[test]
fn test_create_build_script_contains_reproducible_flags() {
let cmd = make_cmd("my-ext");
let script = cmd.create_build_script("1.0.0", "sysext", 0);

assert!(
script.contains("-reproducible"),
"script should include -reproducible flag"
);
assert!(
script.contains("-noappend"),
"script should include -noappend flag"
);
assert!(
script.contains("-no-xattrs"),
"script should include -no-xattrs flag"
);
assert!(
script.contains("mksquashfs"),
"script should invoke mksquashfs"
);
}

#[test]
fn test_create_build_script_source_date_epoch_default() {
let cmd = make_cmd("my-ext");
let script = cmd.create_build_script("1.0.0", "sysext", 0);

assert!(
script.contains("export SOURCE_DATE_EPOCH=0"),
"script should set SOURCE_DATE_EPOCH=0 when default is used"
);
}

#[test]
fn test_create_build_script_source_date_epoch_custom() {
let cmd = make_cmd("my-ext");
let script = cmd.create_build_script("1.0.0", "sysext", 1700000000);

assert!(
script.contains("export SOURCE_DATE_EPOCH=1700000000"),
"script should set SOURCE_DATE_EPOCH to the custom value"
);
assert!(
!script.contains("SOURCE_DATE_EPOCH=0"),
"script should not contain the default value when a custom one is set"
);
}

#[test]
fn test_create_build_script_extension_name_and_version() {
let cmd = make_cmd("test-extension");
let script = cmd.create_build_script("2.3.4", "sysext", 0);

assert!(
script.contains("EXT_NAME=\"test-extension\""),
"script should contain the extension name"
);
assert!(
script.contains("EXT_VERSION=\"2.3.4\""),
"script should contain the extension version"
);
}

#[test]
fn test_create_build_script_output_path() {
let cmd = make_cmd("my-ext");
let script = cmd.create_build_script("1.0.0", "sysext", 0);

assert!(
script.contains("OUTPUT_FILE=\"$OUTPUT_DIR/$EXT_NAME-$EXT_VERSION.raw\""),
"script should set the output file with .raw extension"
);
}
}
2 changes: 1 addition & 1 deletion src/commands/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ impl InitCommand {
let prefix = if path.is_empty() {
String::new()
} else {
format!("{}/", path)
format!("{path}/")
};

for entry in tree.tree {
Expand Down
Loading
Loading