Skip to content
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

buildsys: use clap for env vars #134

Merged
merged 2 commits into from
Feb 12, 2024
Merged

buildsys: use clap for env vars #134

merged 2 commits into from
Feb 12, 2024

Conversation

webern
Copy link
Contributor

@webern webern commented Jan 17, 2024

Issue number:

Related to, refactor before #70

Description of changes:

Required input in the form of environment variables was sprinkled throughout the code. Here we aggregate the inputs into a CLI interface using Clap, while still allowing all to specified via environment variables.

Testing done:

  • Built an aws-k8s-1.27 image
  • Built it again and observed that packages were not rebuilt
  • touched a file (binutils.spec) and built it again - observed that binutils was rebuilt but other packages were not
  • Built an aws-ecs-2 image, many (but not all) packages were rebuilt due to changed variant
  • Built it again and observed that packages were not rebuilt
  • touched a file (binutils.spec) and built it again - observed that binutils was rebuilt but other packages were not

Also, during the refactoring process, I captured the output of the docker build command before and after this PR's changes. I found that I was getting the same build args and secrets args before and after (with the exception that SDK, TOOLCHAIN, TOKEN and NOCACHE have been added during build-variant, which seems fine).

Terms of contribution:

By submitting this pull request, I agree that this contribution is dual-licensed under the terms of both the Apache License, version 2.0, and the MIT license.

@webern webern force-pushed the buildsys-args branch 6 times, most recently from 5086222 to b535980 Compare January 18, 2024 01:10
@webern webern marked this pull request as ready for review January 18, 2024 19:23
Comment on lines 246 to 250
&build_variant_args.common.output_dir,
&build_variant_args.common.root_dir,
&build_variant_args.common.state_dir,
&build_variant_args.common.sdk_image,
&build_variant_args.common.toolchain,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see there's a TODO for this, but it would be nice to have a BuildContext type to hold these values so that the build function signature isn't so complicated.

Comment on lines 168 to 170
let arch = &build_variant_args.common.arch;
let goarch = serde_plain::from_str::<SupportedArch>(arch)
.context(error::UnsupportedArchSnafu { arch })?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do this conversion from &str to SupportedArch just once, perhaps when parsing arguments? Otherwise we end up doing it in a few different places - here and every time supported_arch is called.

@@ -79,11 +78,9 @@ impl LookasideCache {

/// Retrieves a file from the specified URL and write it to the given path,
/// then verifies the contents against the SHA-512 hash provided.
fn fetch_file<P: AsRef<Path>>(url: &str, path: P, hash: &str) -> Result<()> {
fn fetch_file<P: AsRef<Path>>(url: &str, path: P, hash: &str, version: &str) -> Result<()> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this function signature change - it feels like one thing to add additional context to a request header based on an environment variable, and another to take an otherwise useless parameter just to do the same.

I'd prefer to either not emit the version at all - do we really care? - or else to add it as a field to LookasideCache, pass it into LookasideCache::new, and retrieve it from self.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if we care about emitting the version, so I've kept it but with your suggestion to make it self.version.

tools/buildsys/src/args.rs Outdated Show resolved Hide resolved
Comment on lines 94 to 95
"BUILDSYS_PACKAGES_DIR",
"BUILDSYS_SOURCES_DIR",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm OK with unconditionally emitting these for variables we expect to be constant for the life of a given workspace, if we want to simplify slightly and confine the package- and variant-specific rerun_for_envs() to truly special cases.

tools/buildsys/src/args.rs Outdated Show resolved Hide resolved
tools/buildsys/src/args.rs Outdated Show resolved Hide resolved
tools/buildsys/src/args.rs Outdated Show resolved Hide resolved
tools/buildsys/src/args.rs Outdated Show resolved Hide resolved
Comment on lines +45 to +77
#[arg(long, env = "CARGO_MANIFEST_DIR")]
pub(crate) cargo_manifest_dir: PathBuf,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not as excited about converting cargo's own environment variables into arguments since the environment is the canonical way to pass this information.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My preference would be to include it here anyway for two reasons:

  1. Readability: self-documenting code
  2. If I want to write buildsys tests, I may call it directly instead of invoking it from a build.rs script and using args might be helpful for that

@webern
Copy link
Contributor Author

webern commented Jan 20, 2024

https://github.com/bottlerocket-os/twoliter/compare/b535980ed80a54ce9aaa803de228e4120a3ad096..a1eae7b2165900eca3e07e754c7c0b5a09469b89

Fixup the cargo:rerun-if-env-changed list to make it the same as it used to be.

@webern
Copy link
Contributor Author

webern commented Jan 21, 2024

@webern webern marked this pull request as draft January 23, 2024 18:27
@webern
Copy link
Contributor Author

webern commented Jan 23, 2024

I've gotten myself into a much bigger refactor of builder.rs so converted this to a draft.

@webern
Copy link
Contributor Author

webern commented Jan 25, 2024

@bcressey this is ready for you to re-review. The refactor is rather substantial, but I think it is an improvement. My goal was to separate the concerns of a) gathering data from the environment, b) manufacturing and constructing args for docker, and c) the program flow of calling docker. Previously all of those things were happening in the same code-paths. I think having them in separate code paths will make it easier to hack on this for kits.

@webern webern marked this pull request as ready for review January 25, 2024 00:56
@webern webern requested a review from bcressey January 25, 2024 00:56
@webern webern changed the base branch from develop to toolsdir January 25, 2024 01:26
@webern webern force-pushed the toolsdir branch 2 times, most recently from c822ce8 to 7f95550 Compare January 29, 2024 19:57
Base automatically changed from toolsdir to develop January 29, 2024 23:50
@webern
Copy link
Contributor Author

webern commented Feb 2, 2024

Required input in the form of environment variables was sprinkled
throughout the code. Here we aggregate the inputs into a CLI interface
using Clap, while still allowing all to specified via environment
variables.
.image_features()
.map(|set| set.iter().map(|&x| x.to_owned()).collect()),
package,
publish_repo: args.publish_repo,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opened #151 to correct this since it felt out of place here.

Comment on lines 361 to 367
"build {context} \
--target {target} \
--tag {tag} \
--file {dockerfile}",
dockerfile = dockerfile.display(),
target = target,
tag = tag,
)
.split_string();

build.extend(build_args);
build.build_arg("SDK", sdk);
build.build_arg("TOOLCHAIN", toolchain);
build.build_arg("NOCACHE", nocache.to_string());
// Avoid using a cached layer from a concurrent build in another checkout.
build.build_arg("TOKEN", token);

let create = format!("create --name {} {} true", tag, tag).split_string();
let cp = format!("cp {}:/output/. {}", tag, build_dir.display()).split_string();
let rm = format!("rm --force {}", tag).split_string();
let rmi = format!("rmi --force {}", tag).split_string();

// Clean up the stopped container if it exists.
let _ = docker(&rm, Retry::No);

// Clean up the previous image if it exists.
let _ = docker(&rmi, Retry::No);

// Build the image, which builds the artifacts we want.
// Work around transient, known failure cases with Docker.
docker(
&build,
Retry::Yes {
attempts: DOCKER_BUILD_MAX_ATTEMPTS,
messages: &[
&*DOCKER_BUILD_FRONTEND_ERROR,
&*DOCKER_BUILD_DEAD_RECORD_ERROR,
&*UNEXPECTED_EOF_ERROR,
&*CREATEREPO_C_READ_HEADER_ERROR,
],
},
)?;

// Create a stopped container so we can copy artifacts out.
docker(&create, Retry::No)?;

// Copy artifacts into our output directory.
docker(&cp, Retry::No)?;

// Clean up our stopped container after copying artifacts out.
docker(&rm, Retry::No)?;

// Clean up our image now that we're done.
docker(&rmi, Retry::No)?;

// Copy artifacts to the expected directory and write markers to track them.
copy_build_files(&build_dir, output_dir)?;
context = self.context.display(),
dockerfile = self.dockerfile.display(),
target = self.target,
tag = self.tag,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the spacing on these lines seems off?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the spacing on these lines seems off?

Yeah fmt is no help here. I've now aligned the multiline string line starts with the variable list below it.

Comment on lines +13 to +18
/// A list of environment variables and the type of build that should be rerun if that environment
/// variable changes. The build type is represented with bit flags so that we can easily list
/// multiple build types for a single variable. See `[BuildType]` and `[rerun_for_envs]` below to
/// see how this list is used.
const REBUILD_VARS: [(&str, u8); 13] = [
("BUILDSYS_ARCH", PACKAGE | VARIANT),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

This is a nice way to organize something that was very ad-hoc and organic before.

Comment on lines +20 to +21
("BUILDSYS_OUTPUT_DIR", VARIANT),
("BUILDSYS_PACKAGES_DIR", PACKAGE),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it feels like a (pre-existing) mistake to not track BUILDSYS_SOURCES_DIR if we are tracking BUILDSYS_PACKAGES_DIR - if we override the build to point to a completely different sources tree then that should invalidate any builds that used those sources.

Copy link
Contributor Author

@webern webern Feb 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it feels like a (pre-existing) mistake to not track BUILDSYS_SOURCES_DIR if we are tracking BUILDSYS_PACKAGES_DIR - if we override the build to point to a completely different sources tree then that should invalidate any builds that used those sources.

I'm going to defer this because I'm concerned that the symlink change when changing variant could trigger something. I'll track this as a GitHub issue and play around with adding it. #152

Edit: no that's dumb. I can safely add this.

Edit, edit: Actually I'm still squeamish... deferring.

Comment on lines +95 to +96
#[arg(long, env = "BUILDSYS_VARIANT")]
pub(crate) variant: String,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To check my understanding - we don't want this in Common because kits won't need this variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To check my understanding - we don't want this in Common because kits won't need this variable?

That's the idea, yeah. And I think our goal should be that package builds also don't need to know the variant name.

Copy link
Contributor

@ecpullen ecpullen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

#[arg(long, env = "BUILDSYS_PACKAGES_DIR")]
pub(crate) packages_dir: PathBuf,

#[arg(long, env = "BUILDSYS_VARIANT")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This goes away once we no longer have conditional compilation right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This goes away once we no longer have conditional compilation right?

That should be the goal for packages, yes.

let mut build = format!(
"build . \
let mut build = format!(
"build {context} \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this intending looks strange to me?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this intending looks strange to me?

Yeah fmt is no help here. I've now aligned the multiline string line starts with the variable list below it.

@@ -358,6 +443,8 @@ fn docker(args: &[String], retry: Retry) -> Result<Output> {
retry_messages = messages;
}

println!("Running command:\ndocker {}", args.join(" /\n"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only one place out of two we println!() in this file. Is that intentional? I think so but wanted to ensure this isn't a hold over from debugging and this output is doing something useful for logs/output for the user?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only one place out of two we println!() in this file. Is that intentional? I think so but wanted to ensure this isn't a hold over from debugging and this output is doing something useful for logs/output for the user?

Good catch. Fixed.

target: String,
tag: String,
root_dir: PathBuf,
// build_dir: PathBuf,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed this.

Suggested change
// build_dir: PathBuf,

@@ -358,6 +443,8 @@ fn docker(args: &[String], retry: Retry) -> Result<Output> {
retry_messages = messages;
}

println!("Running command:\ndocker {}", args.join(" /\n"));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only one place out of two we println!() in this file. Is that intentional? I think so but wanted to ensure this isn't a hold over from debugging and this output is doing something useful for logs/output for the user?

Good catch. Fixed.

Comment on lines 361 to 367
"build {context} \
--target {target} \
--tag {tag} \
--file {dockerfile}",
dockerfile = dockerfile.display(),
target = target,
tag = tag,
)
.split_string();

build.extend(build_args);
build.build_arg("SDK", sdk);
build.build_arg("TOOLCHAIN", toolchain);
build.build_arg("NOCACHE", nocache.to_string());
// Avoid using a cached layer from a concurrent build in another checkout.
build.build_arg("TOKEN", token);

let create = format!("create --name {} {} true", tag, tag).split_string();
let cp = format!("cp {}:/output/. {}", tag, build_dir.display()).split_string();
let rm = format!("rm --force {}", tag).split_string();
let rmi = format!("rmi --force {}", tag).split_string();

// Clean up the stopped container if it exists.
let _ = docker(&rm, Retry::No);

// Clean up the previous image if it exists.
let _ = docker(&rmi, Retry::No);

// Build the image, which builds the artifacts we want.
// Work around transient, known failure cases with Docker.
docker(
&build,
Retry::Yes {
attempts: DOCKER_BUILD_MAX_ATTEMPTS,
messages: &[
&*DOCKER_BUILD_FRONTEND_ERROR,
&*DOCKER_BUILD_DEAD_RECORD_ERROR,
&*UNEXPECTED_EOF_ERROR,
&*CREATEREPO_C_READ_HEADER_ERROR,
],
},
)?;

// Create a stopped container so we can copy artifacts out.
docker(&create, Retry::No)?;

// Copy artifacts into our output directory.
docker(&cp, Retry::No)?;

// Clean up our stopped container after copying artifacts out.
docker(&rm, Retry::No)?;

// Clean up our image now that we're done.
docker(&rmi, Retry::No)?;

// Copy artifacts to the expected directory and write markers to track them.
copy_build_files(&build_dir, output_dir)?;
context = self.context.display(),
dockerfile = self.dockerfile.display(),
target = self.target,
tag = self.tag,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the spacing on these lines seems off?

Yeah fmt is no help here. I've now aligned the multiline string line starts with the variable list below it.

let mut build = format!(
"build . \
let mut build = format!(
"build {context} \
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this intending looks strange to me?

Yeah fmt is no help here. I've now aligned the multiline string line starts with the variable list below it.

#[arg(long, env = "BUILDSYS_PACKAGES_DIR")]
pub(crate) packages_dir: PathBuf,

#[arg(long, env = "BUILDSYS_VARIANT")]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This goes away once we no longer have conditional compilation right?

That should be the goal for packages, yes.

Comment on lines +95 to +96
#[arg(long, env = "BUILDSYS_VARIANT")]
pub(crate) variant: String,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To check my understanding - we don't want this in Common because kits won't need this variable?

That's the idea, yeah. And I think our goal should be that package builds also don't need to know the variant name.

Comment on lines +20 to +21
("BUILDSYS_OUTPUT_DIR", VARIANT),
("BUILDSYS_PACKAGES_DIR", PACKAGE),
Copy link
Contributor Author

@webern webern Feb 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it feels like a (pre-existing) mistake to not track BUILDSYS_SOURCES_DIR if we are tracking BUILDSYS_PACKAGES_DIR - if we override the build to point to a completely different sources tree then that should invalidate any builds that used those sources.

I'm going to defer this because I'm concerned that the symlink change when changing variant could trigger something. I'll track this as a GitHub issue and play around with adding it. #152

Edit: no that's dumb. I can safely add this.

Edit, edit: Actually I'm still squeamish... deferring.

Refactor the code, primarily in builder.rs. The goal of the refactor is
to segregate data collection and docker argument creation from program
flow logic.
@webern webern merged commit 6fec72b into develop Feb 12, 2024
1 check passed
@webern webern deleted the buildsys-args branch February 12, 2024 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants