13 Commits

Author SHA1 Message Date
cuddle-please
a94fd3e68e chore(release): 0.2.0
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/tag Build is failing
continuous-integration/drone/push Build is passing
2023-08-12 19:14:42 +00:00
e2f1d79031 fix(ci): only set local url instead of insteadOf
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2023-08-12 21:12:45 +02:00
07c593bb08 fix(ci): trim remote_url newlines
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2023-08-12 21:00:58 +02:00
61c34b9fb8 fix(ci): trim remote_url newlines
Some checks failed
continuous-integration/drone/push Build is failing
Signed-off-by: kjuulh <contact@kjuulh.io>
2023-08-12 20:57:21 +02:00
03784be431 fix(ci): repo should be ssh
Some checks failed
continuous-integration/drone/push Build is failing
Signed-off-by: kjuulh <contact@kjuulh.io>
2023-08-12 18:54:06 +00:00
1cf349c3c6 fix(ci): make sure to run ssh as user git
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone Build is passing
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2023-08-12 20:26:13 +02:00
7277e06c0b chore: set fixed versions
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2023-08-12 20:14:27 +02:00
776db7274a fix: ci
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2023-08-12 18:08:04 +00:00
e1428a8fbb feat: with rust build and test
Signed-off-by: kjuulh <contact@kjuulh.io>
2023-08-12 18:08:04 +00:00
a17e527b91 chore(deps): update rust crate tokio to 1.31.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-08-12 16:53:55 +00:00
5c53589c27 refactor(ci): move cuddle please image to cuddle
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2023-08-12 12:26:12 +02:00
8c3b5e660f feat(ci): with internal please action
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2023-08-11 18:23:05 +00:00
37054fa012 Add renovate.json
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-11 18:22:57 +00:00
32 changed files with 960 additions and 296 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/target
.env
.cuddle/
target/

View File

@@ -6,6 +6,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.2.0] - 2023-08-12
### Added
- with rust build and test
- *(ci)* with internal please action
### Fixed
- *(ci)* only set local url instead of insteadOf
- *(ci)* trim remote_url newlines
- *(ci)* trim remote_url newlines
- *(ci)* repo should be ssh
- *(ci)* make sure to run ssh as user git
- ci
### Other
- set fixed versions
- *(deps)* update rust crate tokio to 1.31.0
- *(ci)* move cuddle please image to cuddle
- Add renovate.json
## [0.1.0] - 2023-08-11
### Added

54
Cargo.lock generated
View File

@@ -86,9 +86,9 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.72"
version = "0.1.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09"
checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [
"proc-macro2",
"quote",
@@ -183,6 +183,8 @@ dependencies = [
"async-scoped",
"clap",
"color-eyre",
"dagger-cuddle-please",
"dagger-rust",
"dagger-sdk",
"dotenv",
"eyre",
@@ -348,6 +350,16 @@ dependencies = [
"eyre",
]
[[package]]
name = "dagger-rust"
version = "0.1.0"
dependencies = [
"async-trait",
"dagger-sdk",
"eyre",
"tokio",
]
[[package]]
name = "dagger-sdk"
version = "0.2.22"
@@ -966,9 +978,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.19"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
@@ -1223,6 +1235,36 @@ dependencies = [
"winapi",
]
[[package]]
name = "rust-build"
version = "0.1.0"
dependencies = [
"dagger-rust",
"dagger-sdk",
"eyre",
"tokio",
]
[[package]]
name = "rust-src"
version = "0.1.0"
dependencies = [
"dagger-rust",
"dagger-sdk",
"eyre",
"tokio",
]
[[package]]
name = "rust-test"
version = "0.1.0"
dependencies = [
"dagger-rust",
"dagger-sdk",
"eyre",
"tokio",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
@@ -1507,9 +1549,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.30.0"
version = "1.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d3ce25f50619af8b0aec2eb23deebe84249e19e2ddd393a6e16e3300a6dadfd"
checksum = "40de3a2ba249dcb097e01be5e67a5ff53cf250397715a071a81543e8a832a920"
dependencies = [
"backtrace",
"bytes",

View File

@@ -1,18 +1,16 @@
[workspace]
members = [
"crates/*",
"examples/*",
"ci"
]
members = ["crates/*", "examples/*", "ci"]
resolver = "2"
[workspace.dependencies]
cuddle-components = {path = "crates/cuddle-components"}
dagger-components = {path = "crates/dagger-components"}
dagger-cuddle-please = {path = "crates/dagger-cuddle-please"}
ci = {path = "ci"}
cuddle-components = { path = "crates/cuddle-components" }
dagger-components = { path = "crates/dagger-components" }
dagger-cuddle-please = { path = "crates/dagger-cuddle-please" }
dagger-rust = { path = "crates/dagger-rust" }
ci = { path = "ci" }
dagger-sdk = "0.2.22"
eyre = "0.6.8"
tokio = "1.30.0"
dotenv = "*"
tokio = "1.31.0"
dotenv = "0.15.0"
async-trait = "0.1.73"

View File

@@ -6,6 +6,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dagger-cuddle-please.workspace = true
dagger-rust.workspace = true
dagger-sdk = "*"
eyre = "*"
color-eyre = "*"

View File

@@ -1,4 +1,3 @@
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
@@ -51,6 +50,9 @@ pub struct GlobalArgs {
#[arg(long, global = true, help_heading = "Global")]
rust_builder_image: Option<String>,
#[arg(long, global = true, help_heading = "Global")]
cuddle_please_image: Option<String>,
#[arg(long, global = true, help_heading = "Global")]
source: Option<PathBuf>,
}
@@ -67,9 +69,7 @@ async fn main() -> eyre::Result<()> {
match &cli.commands {
Commands::Local { command } => match command {
LocalCommands::Test => {
let base_image =
base_rust_image(client.clone(), &cli.global, &None, &"debug".into()).await?;
test::execute(client, &cli.global, base_image).await?;
test::execute(client, &cli.global).await?;
}
LocalCommands::PleaseRelease => todo!(),
},
@@ -77,12 +77,7 @@ async fn main() -> eyre::Result<()> {
async fn test(client: Arc<dagger_sdk::Query>, cli: &Command) {
let args = &cli.global;
let base_image = base_rust_image(client.clone(), args, &None, &"debug".into())
.await
.unwrap();
test::execute(client.clone(), args, base_image)
.await
.unwrap();
test::execute(client.clone(), args).await.unwrap();
}
tokio::join!(test(client.clone(), &cli),);
@@ -91,12 +86,7 @@ async fn main() -> eyre::Result<()> {
async fn test(client: Arc<dagger_sdk::Query>, cli: &Command) {
let args = &cli.global;
let base_image = base_rust_image(client.clone(), args, &None, &"debug".into())
.await
.unwrap();
test::execute(client.clone(), args, base_image)
.await
.unwrap();
test::execute(client.clone(), args).await.unwrap();
}
async fn cuddle_please(client: Arc<dagger_sdk::Query>, cli: &Command) {
@@ -119,262 +109,49 @@ async fn main() -> eyre::Result<()> {
mod please_release {
use std::sync::Arc;
use dagger_cuddle_please::{models::CuddlePleaseSrcArgs, DaggerCuddlePleaseAction};
use crate::GlobalArgs;
pub async fn run_release_please(
client: Arc<dagger_sdk::Query>,
args: &GlobalArgs,
) -> eyre::Result<()> {
let build_image = client
.container()
.from("kasperhermansen/cuddle-please:main-1691504183");
let src = client
.git_opts(
"https://git.front.kjuulh.io/kjuulh/dagger-components",
dagger_sdk::QueryGitOpts {
experimental_service_host: None,
keep_git_dir: Some(true),
DaggerCuddlePleaseAction::dagger(client)
.execute_src(&CuddlePleaseSrcArgs {
cuddle_image: args
.cuddle_please_image
.clone()
.unwrap_or("kasperhermansen/cuddle-please:latest".into()),
server: dagger_cuddle_please::models::SrcServer::Gitea {
token: std::env::var("CUDDLE_PLEASE_TOKEN")
.expect("CUDDLE_PLEASE_TOKEN to be present"),
},
)
.branch("main")
.tree();
let res = build_image
.with_secret_variable(
"CUDDLE_PLEASE_TOKEN",
client
.set_secret("CUDDLE_PLEASE_TOKEN", std::env::var("CUDDLE_PLEASE_TOKEN")?)
.id()
.await?,
)
.with_workdir("/mnt/app")
.with_directory(".", src.id().await?)
.with_exec(vec![
"git",
"remote",
"set-url",
"origin",
&format!(
"https://git:{}@git.front.kjuulh.io/kjuulh/dagger-components.git",
std::env::var("CUDDLE_PLEASE_TOKEN")?
),
])
.with_exec(vec![
"cuddle-please",
"release",
"--engine=gitea",
"--owner=kjuulh",
"--repo=dagger-components",
"--branch=main",
"--api-url=https://git.front.kjuulh.io",
"--log-level=debug",
]);
let exit_code = res.exit_code().await?;
if exit_code != 0 {
eyre::bail!("failed to run cuddle-please");
}
let please_out = res.stdout().await?;
println!("{please_out}");
let please_out = res.stderr().await?;
println!("{please_out}");
log_level: Some(dagger_cuddle_please::models::LogLevel::Debug),
})
.await?;
Ok(())
}
}
mod test {
use std::sync::Arc;
use std::{path::PathBuf, sync::Arc};
use dagger_rust::build::RustVersion;
use crate::GlobalArgs;
pub async fn execute(
_client: Arc<dagger_sdk::Query>,
_args: &GlobalArgs,
container: dagger_sdk::Container,
) -> eyre::Result<()> {
let test_image = container
.pipeline("rust:test")
.with_exec(vec!["apt", "update"])
.with_exec(vec!["apt", "install", "-y", "git"])
.with_exec(vec!["cargo", "test"]);
let please_out = test_image.stdout().await?;
println!("{please_out}");
let please_out = test_image.stderr().await?;
println!("{please_out}");
test_image.exit_code().await?;
pub async fn execute(client: Arc<dagger_sdk::Query>, _args: &GlobalArgs) -> eyre::Result<()> {
dagger_rust::test::RustTest::new(client)
.test(
None::<PathBuf>,
RustVersion::Nightly,
&["crates/*", "examples/*", "ci"],
&[],
)
.await?;
Ok(())
}
}
pub fn get_src(
client: Arc<dagger_sdk::Query>,
args: &GlobalArgs,
) -> eyre::Result<dagger_sdk::Directory> {
let directory = client.host().directory_opts(
args.source
.clone()
.unwrap_or(PathBuf::from("."))
.display()
.to_string(),
dagger_sdk::HostDirectoryOptsBuilder::default()
.exclude(vec!["node_modules/", ".git/", "target/"])
.build()?,
);
Ok(directory)
}
pub async fn get_rust_dep_src(
client: Arc<dagger_sdk::Query>,
args: &GlobalArgs,
) -> eyre::Result<dagger_sdk::Directory> {
let directory = client.host().directory_opts(
args.source
.clone()
.unwrap_or(PathBuf::from("."))
.display()
.to_string(),
dagger_sdk::HostDirectoryOptsBuilder::default()
.include(vec!["**/Cargo.toml", "**/Cargo.lock"])
.build()?,
);
Ok(directory)
}
pub async fn get_rust_skeleton_files(
client: Arc<dagger_sdk::Query>,
_args: &GlobalArgs,
) -> eyre::Result<(dagger_sdk::Directory, Vec<String>)> {
let mut rust_crates = vec![PathBuf::from("ci")];
let mut dirs = tokio::fs::read_dir("crates").await?;
while let Some(entry) = dirs.next_entry().await? {
if entry.metadata().await?.is_dir() {
rust_crates.push(entry.path())
}
}
let mut dirs = tokio::fs::read_dir("examples").await?;
while let Some(entry) = dirs.next_entry().await? {
if entry.metadata().await?.is_dir() {
rust_crates.push(entry.path())
}
}
fn create_skeleton_files(
directory: dagger_sdk::Directory,
path: &Path,
) -> eyre::Result<dagger_sdk::Directory> {
println!("found crates: {}", path.display());
let main_content = r#"
#[allow(dead_code)]
fn main() { panic!("should never be executed"); }"#;
let lib_content = r#"
#[allow(dead_code)]
fn some() { panic!("should never be executed"); }"#;
let directory = directory.with_new_file(
path.join("src").join("main.rs").display().to_string(),
main_content,
);
let directory = directory.with_new_file(
path.join("src").join("lib.rs").display().to_string(),
lib_content,
);
Ok(directory)
}
let mut directory = client.directory();
let mut crate_names = Vec::new();
for rust_crate in rust_crates.iter() {
if let Some(file_name) = rust_crate.file_name() {
crate_names.push(file_name.to_str().unwrap().to_string());
}
directory = create_skeleton_files(directory, rust_crate)?;
}
Ok((directory, crate_names))
}
pub async fn base_rust_image(
client: Arc<dagger_sdk::Query>,
args: &GlobalArgs,
platform: &Option<String>,
profile: &String,
) -> eyre::Result<dagger_sdk::Container> {
let dep_src = get_rust_dep_src(client.clone(), args).await?;
let (skeleton_files, crates) = get_rust_skeleton_files(client.clone(), args).await?;
let src = get_src(client.clone(), args)?;
let client = client.pipeline("rust_base_image");
let rust_target = match platform
.clone()
.unwrap_or("linux/amd64".to_string())
.as_str()
{
"linux/amd64" => "x86_64-unknown-linux-gnu",
"linux/arm64" => "aarch64-unknown-linux-gnu",
_ => eyre::bail!("architecture not supported"),
};
let rust_build_image = client
.container()
.from(
args.rust_builder_image
.as_ref()
.unwrap_or(&"rustlang/rust:nightly".into()),
)
.with_exec(vec!["rustup", "target", "add", rust_target])
.with_exec(vec!["apt", "update"])
.with_exec(vec!["apt", "install", "-y", "jq"]);
let target_cache = client.cache_volume(format!("rust_target_{}", profile));
let mut build_options = vec!["cargo", "build", "--target", rust_target, "--workspace"];
if profile == "release" {
build_options.push("--release");
}
let rust_prebuild = rust_build_image
.with_workdir("/mnt/src")
.with_directory("/mnt/src", dep_src.id().await?)
.with_directory("/mnt/src/", skeleton_files.id().await?)
.with_exec(build_options)
.with_mounted_cache("/mnt/src/target/", target_cache.id().await?);
let exclude = crates
.iter()
.filter(|c| **c != "ci")
.map(|c| format!("**/*{}*", c.replace('-', "_")))
.collect::<Vec<_>>();
let exclude = exclude.iter().map(|c| c.as_str()).collect();
let incremental_dir = client.directory().with_directory_opts(
".",
rust_prebuild.directory("target").id().await?,
dagger_sdk::DirectoryWithDirectoryOpts {
exclude: Some(exclude),
include: None,
},
);
let rust_with_src = rust_build_image
.with_workdir("/mnt/src")
.with_directory(
"/usr/local/cargo",
rust_prebuild.directory("/usr/local/cargo").id().await?,
)
.with_directory("target", incremental_dir.id().await?)
.with_directory("/mnt/src/", src.id().await?);
Ok(rust_with_src)
}

View File

@@ -8,4 +8,4 @@ edition = "2021"
[dependencies]
dagger-sdk.workspace = true
eyre.workspace = true
async-trait = "*"
async-trait.workspace = true

View File

@@ -275,7 +275,6 @@ impl DaggerCuddlePlease {
args: &CuddlePleaseSrcArgs,
) -> eyre::Result<()> {
let build_image = client.container().from(&args.cuddle_image);
let res = build_image
.with_secret_variable(
"CUDDLE_PLEASE_TOKEN",
@@ -309,6 +308,7 @@ impl DaggerCuddlePlease {
contents: Some(
"
Host *
User git
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
",
@@ -316,26 +316,46 @@ Host *
owner: Some("root"),
permissions: Some(700),
},
)
.with_exec(vec![
"cuddle-please",
"release",
&format!(
"--engine={}",
match &args.server {
SrcServer::Gitea { .. } => "gitea",
SrcServer::GitHub { .. } => "github",
}
),
"--log-level",
match args.log_level.as_ref().unwrap_or(&LogLevel::Info) {
LogLevel::Trace => "trace",
LogLevel::Debug => "debug",
LogLevel::Info => "info",
LogLevel::Warn => "warn",
LogLevel::Error => "error",
},
]);
);
let remote_url = res
.with_exec(vec!["git", "config", "--get", "remote.origin.url"])
.stdout()
.await?;
let res = if remote_url.starts_with("http") {
let new_remote_url = format!(
"ssh://git@{}",
remote_url
.trim()
.trim_start_matches("https://")
.trim_start_matches("http://")
);
println!("new remote_url: {}", new_remote_url);
res.with_exec(vec!["git", "remote", "set-url", "origin", &new_remote_url])
} else {
res
};
let res = res.with_exec(vec![
"cuddle-please",
"release",
&format!(
"--engine={}",
match &args.server {
SrcServer::Gitea { .. } => "gitea",
SrcServer::GitHub { .. } => "github",
}
),
"--log-level",
match args.log_level.as_ref().unwrap_or(&LogLevel::Info) {
LogLevel::Trace => "trace",
LogLevel::Debug => "debug",
LogLevel::Info => "info",
LogLevel::Warn => "warn",
LogLevel::Error => "error",
},
]);
let exit_code = res.exit_code().await?;
if exit_code != 0 {

View File

@@ -0,0 +1,12 @@
[package]
name = "dagger-rust"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dagger-sdk.workspace = true
eyre.workspace = true
async-trait.workspace = true
tokio.workspace = true

View File

@@ -0,0 +1,364 @@
use std::{path::PathBuf, sync::Arc};
use crate::source::RustSource;
#[allow(dead_code)]
pub struct RustBuild {
client: Arc<dagger_sdk::Query>,
registry: Option<String>,
}
impl RustBuild {
pub fn new(client: Arc<dagger_sdk::Query>) -> Self {
Self {
client,
registry: None,
}
}
pub async fn build(
&self,
source_path: Option<impl Into<PathBuf>>,
rust_version: impl AsRef<RustVersion>,
target: impl AsRef<BuildTarget>,
profile: impl AsRef<BuildProfile>,
crates: &[&str],
extra_deps: &[&str],
) -> eyre::Result<dagger_sdk::Container> {
let rust_version = rust_version.as_ref();
let target = target.as_ref();
let profile = profile.as_ref();
let source_path = source_path.map(|s| s.into());
let source = source_path.clone().unwrap_or(PathBuf::from("."));
let rust_source = RustSource::new(self.client.clone());
let (src, dep_src) = rust_source
.get_rust_src(source_path, crates.to_vec())
.await?;
let mut deps = vec!["apt", "install", "-y"];
deps.extend(extra_deps);
let rust_build_image = self
.client
.container()
.from(rust_version.to_string())
.with_exec(vec!["rustup", "target", "add", &target.to_string()])
.with_exec(vec!["apt", "update"])
.with_exec(deps);
let target_cache = self.client.cache_volume(format!(
"rust_target_{}_{}",
profile.to_string(),
target.to_string()
));
let target_str = target.to_string();
let mut build_options = vec!["cargo", "build", "--target", &target_str, "--workspace"];
if matches!(profile, BuildProfile::Release) {
build_options.push("--release");
}
let rust_prebuild = rust_build_image
.with_workdir("/mnt/src")
.with_directory("/mnt/src", dep_src.id().await?)
.with_exec(build_options)
.with_mounted_cache("/mnt/src/target/", target_cache.id().await?);
let incremental_dir = rust_source
.get_rust_target_src(&source, rust_prebuild.clone(), crates.to_vec())
.await?;
let rust_with_src = rust_build_image
.with_workdir("/mnt/src")
.with_directory(
"/usr/local/cargo",
rust_prebuild.directory("/usr/local/cargo").id().await?,
)
.with_directory("/mnt/src/target", incremental_dir.id().await?)
.with_directory("/mnt/src/", src.id().await?);
Ok(rust_with_src)
}
pub async fn build_release(
&self,
source_path: Option<impl Into<PathBuf>>,
rust_version: impl AsRef<RustVersion>,
crates: &[&str],
extra_deps: &[&str],
images: impl IntoIterator<Item = SlimImage>,
bin_name: &str,
) -> eyre::Result<Vec<dagger_sdk::Container>> {
let images = images.into_iter().collect::<Vec<_>>();
let source_path = source_path.map(|s| s.into());
let mut containers = Vec::new();
for container_image in images {
let container = match &container_image {
SlimImage::Debian { image, deps, .. } => {
let target = BuildTarget::from_target(&container_image);
let build_container = self
.build(
source_path.clone(),
&rust_version,
BuildTarget::from_target(&container_image),
BuildProfile::Release,
crates,
extra_deps,
)
.await?;
let bin = build_container
.with_exec(vec![
"cargo",
"build",
"--target",
&target.to_string(),
"--release",
"-p",
bin_name,
])
.file(format!(
"target/{}/release/{}",
target.to_string(),
bin_name
));
self.build_debian_image(
bin,
image,
BuildTarget::from_target(&container_image),
deps.iter()
.map(|d| d.as_str())
.collect::<Vec<&str>>()
.as_slice(),
bin_name,
)
.await?
}
SlimImage::Alpine { image, deps, .. } => {
let target = BuildTarget::from_target(&container_image);
let build_container = self
.build(
source_path.clone(),
&rust_version,
BuildTarget::from_target(&container_image),
BuildProfile::Release,
crates,
extra_deps,
)
.await?;
let bin = build_container
.with_exec(vec![
"cargo",
"build",
"--target",
&target.to_string(),
"--release",
"-p",
bin_name,
])
.file(format!(
"target/{}/release/{}",
target.to_string(),
bin_name
));
self.build_alpine_image(
bin,
image,
BuildTarget::from_target(&container_image),
deps.iter()
.map(|d| d.as_str())
.collect::<Vec<&str>>()
.as_slice(),
bin_name,
)
.await?
}
};
containers.push(container);
}
Ok(containers)
}
async fn build_debian_image(
&self,
bin: dagger_sdk::File,
image: &str,
target: BuildTarget,
production_deps: &[&str],
bin_name: &str,
) -> eyre::Result<dagger_sdk::Container> {
let base_debian = self
.client
.container_opts(dagger_sdk::QueryContainerOpts {
id: None,
platform: Some(target.into_platform()),
})
.from(image);
let mut packages = vec!["apt", "install", "-y"];
packages.extend_from_slice(production_deps);
let base_debian = base_debian
.with_exec(vec!["apt", "update"])
.with_exec(packages);
let final_image = base_debian
.with_file(format!("/usr/local/bin/{}", bin_name), bin.id().await?)
.with_exec(vec![bin_name, "--help"]);
final_image.exit_code().await?;
Ok(final_image)
}
async fn build_alpine_image(
&self,
bin: dagger_sdk::File,
image: &str,
target: BuildTarget,
production_deps: &[&str],
bin_name: &str,
) -> eyre::Result<dagger_sdk::Container> {
let base_debian = self
.client
.container_opts(dagger_sdk::QueryContainerOpts {
id: None,
platform: Some(target.into_platform()),
})
.from(image);
let mut packages = vec!["apk", "add"];
packages.extend_from_slice(production_deps);
let base_debian = base_debian.with_exec(packages);
let final_image =
base_debian.with_file(format!("/usr/local/bin/{}", bin_name), bin.id().await?);
Ok(final_image)
}
}
pub enum RustVersion {
Nightly,
Stable(String),
}
impl AsRef<RustVersion> for RustVersion {
fn as_ref(&self) -> &RustVersion {
&self
}
}
impl ToString for RustVersion {
fn to_string(&self) -> String {
match self {
RustVersion::Nightly => "rustlang/rust:nightly".to_string(),
RustVersion::Stable(version) => format!("rust:{}", version),
}
}
}
pub enum BuildTarget {
LinuxAmd64,
LinuxArm64,
LinuxAmd64Musl,
LinuxArm64Musl,
MacOSAmd64,
MacOSArm64,
}
impl BuildTarget {
pub fn from_target(image: &SlimImage) -> Self {
match image {
SlimImage::Debian { architecture, .. } => match architecture {
BuildArchitecture::Amd64 => Self::LinuxAmd64,
BuildArchitecture::Arm64 => Self::LinuxArm64,
},
SlimImage::Alpine { architecture, .. } => match architecture {
BuildArchitecture::Amd64 => Self::LinuxAmd64Musl,
BuildArchitecture::Arm64 => Self::LinuxArm64Musl,
},
}
}
fn into_platform(&self) -> dagger_sdk::Platform {
let platform = match self {
BuildTarget::LinuxAmd64 => "linux/amd64",
BuildTarget::LinuxArm64 => "linux/arm64",
BuildTarget::LinuxAmd64Musl => "linux/amd64",
BuildTarget::LinuxArm64Musl => "linux/arm64",
BuildTarget::MacOSAmd64 => "darwin/amd64",
BuildTarget::MacOSArm64 => "darwin/arm64",
};
dagger_sdk::Platform(platform.into())
}
}
impl AsRef<BuildTarget> for BuildTarget {
fn as_ref(&self) -> &BuildTarget {
&self
}
}
impl ToString for BuildTarget {
fn to_string(&self) -> String {
let target = match self {
BuildTarget::LinuxAmd64 => "x86_64-unknown-linux-gnu",
BuildTarget::LinuxArm64 => "aarch64-unknown-linux-gnu",
BuildTarget::LinuxAmd64Musl => "x86_64-unknown-linux-musl",
BuildTarget::LinuxArm64Musl => "aarch64-unknown-linux-musl",
BuildTarget::MacOSAmd64 => "x86_64-apple-darwin",
BuildTarget::MacOSArm64 => "aarch64-apple-darwin",
};
target.into()
}
}
pub enum BuildProfile {
Debug,
Release,
}
impl AsRef<BuildProfile> for BuildProfile {
fn as_ref(&self) -> &BuildProfile {
&self
}
}
impl ToString for BuildProfile {
fn to_string(&self) -> String {
let profile = match self {
BuildProfile::Debug => "debug",
BuildProfile::Release => "release",
};
profile.into()
}
}
pub enum SlimImage {
Debian {
image: String,
deps: Vec<String>,
architecture: BuildArchitecture,
},
Alpine {
image: String,
deps: Vec<String>,
architecture: BuildArchitecture,
},
}
pub enum BuildArchitecture {
Amd64,
Arm64,
}

View File

@@ -0,0 +1,3 @@
pub mod build;
pub mod source;
pub mod test;

View File

@@ -0,0 +1,196 @@
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use eyre::Context;
pub struct RustSource {
client: Arc<dagger_sdk::Query>,
exclude: Vec<String>,
}
impl RustSource {
pub fn new(client: Arc<dagger_sdk::Query>) -> Self {
Self {
client,
exclude: vec!["node_modules/", ".git/", "target/", ".cuddle/"]
.into_iter()
.map(|s| s.to_string())
.collect(),
}
}
pub fn with_exclude(
&mut self,
exclude: impl IntoIterator<Item = impl Into<String>>,
) -> &mut Self {
self.exclude = exclude.into_iter().map(|s| s.into()).collect();
self
}
pub fn append_exclude(
&mut self,
exclude: impl IntoIterator<Item = impl Into<String>>,
) -> &mut Self {
self.exclude
.append(&mut exclude.into_iter().map(|s| s.into()).collect::<Vec<_>>());
self
}
pub async fn get_rust_src<T, I>(
&self,
source: Option<T>,
crate_paths: I,
) -> eyre::Result<(dagger_sdk::Directory, dagger_sdk::Directory)>
where
T: Into<PathBuf>,
T: Clone,
I: IntoIterator,
I::Item: Into<String>,
{
let source_path = match source.clone() {
Some(s) => s.into(),
None => PathBuf::from("."),
};
let (skeleton_files, _crates) = self
.get_rust_skeleton_files(&source_path, crate_paths)
.await?;
let src = self.get_src(source.clone()).await?;
let rust_src = self.get_rust_dep_src(source).await?;
let rust_src = rust_src.with_directory(".", skeleton_files.id().await?);
Ok((src, rust_src))
}
pub async fn get_src(
&self,
source: Option<impl Into<PathBuf>>,
) -> eyre::Result<dagger_sdk::Directory> {
let source = source.map(|s| s.into()).unwrap_or(PathBuf::from("."));
let directory = self.client.host().directory_opts(
source.display().to_string(),
dagger_sdk::HostDirectoryOptsBuilder::default()
.exclude(self.exclude.iter().map(|s| s.as_str()).collect::<Vec<_>>())
.build()?,
);
Ok(directory)
}
pub async fn get_rust_dep_src(
&self,
source: Option<impl Into<PathBuf>>,
) -> eyre::Result<dagger_sdk::Directory> {
let source = source.map(|s| s.into()).unwrap_or(PathBuf::from("."));
let directory = self.client.host().directory_opts(
source.display().to_string(),
dagger_sdk::HostDirectoryOptsBuilder::default()
.include(vec!["**/Cargo.toml", "**/Cargo.lock"])
.build()?,
);
Ok(directory)
}
pub async fn get_rust_target_src(
&self,
source_path: &Path,
container: dagger_sdk::Container,
crate_paths: impl IntoIterator<Item = impl Into<String>>,
) -> eyre::Result<dagger_sdk::Directory> {
let (_skeleton_files, crates) = self
.get_rust_skeleton_files(source_path, crate_paths)
.await?;
let exclude = crates
.iter()
.map(|c| format!("**/*{}*", c.replace('-', "_")))
.collect::<Vec<_>>();
let exclude = exclude.iter().map(|c| c.as_str()).collect();
let incremental_dir = self.client.directory().with_directory_opts(
".",
container.directory("target").id().await?,
dagger_sdk::DirectoryWithDirectoryOpts {
exclude: Some(exclude),
include: None,
},
);
return Ok(incremental_dir);
}
pub async fn get_rust_skeleton_files(
&self,
source_path: &Path,
crate_paths: impl IntoIterator<Item = impl Into<String>>,
) -> eyre::Result<(dagger_sdk::Directory, Vec<String>)> {
let paths = crate_paths
.into_iter()
.map(|s| s.into())
.collect::<Vec<String>>();
let mut crates = Vec::new();
for path in paths {
if path.ends_with("/*") {
let mut dirs = tokio::fs::read_dir(source_path.join(path.trim_end_matches("/*")))
.await
.context(format!("failed to find path: {}", path.clone()))?;
while let Some(entry) = dirs.next_entry().await? {
if entry.metadata().await?.is_dir() {
crates.push(entry.path());
}
}
} else {
crates.push(PathBuf::from(path));
}
}
fn create_skeleton_files(
directory: dagger_sdk::Directory,
path: &Path,
) -> eyre::Result<dagger_sdk::Directory> {
let main_content = r#"
#[allow(dead_code)]
fn main() { panic!("should never be executed"); }"#;
let lib_content = r#"
#[allow(dead_code)]
fn some() { panic!("should never be executed"); }"#;
let directory = directory.with_new_file(
path.join("src").join("main.rs").display().to_string(),
main_content,
);
let directory = directory.with_new_file(
path.join("src").join("lib.rs").display().to_string(),
lib_content,
);
Ok(directory)
}
let mut directory = self.client.directory();
let mut crate_names = Vec::new();
for rust_crate in crates.iter() {
if let Some(file_name) = rust_crate.file_name() {
crate_names.push(file_name.to_str().unwrap().to_string());
}
directory = create_skeleton_files(
directory,
rust_crate.strip_prefix(source_path).unwrap_or(&rust_crate),
)?;
}
Ok((directory, crate_names))
}
}

View File

@@ -0,0 +1,77 @@
use std::{path::PathBuf, sync::Arc};
use crate::{build::RustVersion, source::RustSource};
pub struct RustTest {
client: Arc<dagger_sdk::Query>,
registry: Option<String>,
}
impl RustTest {
pub fn new(client: Arc<dagger_sdk::Query>) -> Self {
Self {
client,
registry: None,
}
}
pub async fn test(
&self,
source_path: Option<impl Into<PathBuf>>,
rust_version: impl AsRef<RustVersion>,
crates: &[&str],
extra_deps: &[&str],
) -> eyre::Result<()> {
let rust_version = rust_version.as_ref();
let source_path = source_path.map(|s| s.into());
let source = source_path.clone().unwrap_or(PathBuf::from("."));
let rust_source = RustSource::new(self.client.clone());
let (src, dep_src) = rust_source
.get_rust_src(source_path, crates.to_vec())
.await?;
let mut deps = vec!["apt", "install", "-y"];
deps.extend(extra_deps);
let rust_build_image = self
.client
.container()
.from(rust_version.to_string())
.with_exec(vec!["apt", "update"])
.with_exec(deps);
let target_cache = self.client.cache_volume(format!("rust_target_test",));
let build_options = vec!["cargo", "build", "--workspace"];
let rust_prebuild = rust_build_image
.with_workdir("/mnt/src")
.with_directory("/mnt/src", dep_src.id().await?)
.with_exec(build_options)
.with_mounted_cache("/mnt/src/target/", target_cache.id().await?);
let incremental_dir = rust_source
.get_rust_target_src(&source, rust_prebuild.clone(), crates.to_vec())
.await?;
let rust_with_src = rust_build_image
.with_workdir("/mnt/src")
.with_directory(
"/usr/local/cargo",
rust_prebuild.directory("/usr/local/cargo").id().await?,
)
.with_directory("/mnt/src/target", incremental_dir.id().await?)
.with_directory("/mnt/src/", src.id().await?);
let test = rust_with_src.with_exec(vec!["cargo", "test"]);
let stdout = test.stdout().await?;
let stderr = test.stderr().await?;
println!("stdout: {}, stderr: {}", stdout, stderr);
if 0 != test.exit_code().await? {
eyre::bail!("failed rust:test");
}
Ok(())
}
}

View File

@@ -6,6 +6,8 @@ vars:
service: "dagger-components"
registry: kasperhermansen
cuddle_please_image: "kasperhermansen/cuddle-please:main-1691504183"
please:
project:
owner: kjuulh

View File

@@ -0,0 +1,13 @@
[package]
name = "rust-build"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dagger-rust.workspace = true
eyre.workspace = true
dagger-sdk.workspace = true
tokio.workspace = true

View File

@@ -0,0 +1,29 @@
use dagger_rust::build::{RustVersion, SlimImage};
#[tokio::main]
pub async fn main() -> eyre::Result<()> {
let client = dagger_sdk::connect().await?;
let rust_build = dagger_rust::build::RustBuild::new(client.clone());
let containers = rust_build
.build_release(
Some("testdata"),
RustVersion::Nightly,
&["crates/*"],
&["openssl"],
vec![SlimImage::Debian {
image: "debian:bookworm".into(),
deps: vec!["openssl".into()],
architecture: dagger_rust::build::BuildArchitecture::Amd64,
}],
"example_bin",
)
.await?;
for container in containers {
container.exit_code().await?;
}
Ok(())
}

View File

@@ -0,0 +1,3 @@
[workspace]
members = ["crates/*"]
resolver = "2"

View File

@@ -0,0 +1 @@
/target

View File

@@ -0,0 +1,8 @@
[package]
name = "example_bin"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

View File

@@ -0,0 +1,13 @@
[package]
name = "rust-src"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dagger-rust.workspace = true
eyre.workspace = true
dagger-sdk.workspace = true
tokio.workspace = true

View File

@@ -0,0 +1,16 @@
use std::path::PathBuf;
#[tokio::main]
pub async fn main() -> eyre::Result<()> {
let client = dagger_sdk::connect().await?;
let crates = ["some-crate"];
let dag = dagger_rust::source::RustSource::new(client.clone());
let (_src, _rust_src) = dag.get_rust_src(None::<PathBuf>, crates).await?;
let _full_src = dag
.get_rust_target_src(&PathBuf::from("."), client.container(), crates)
.await?;
Ok(())
}

View File

@@ -0,0 +1,13 @@
[package]
name = "rust-test"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dagger-rust.workspace = true
eyre.workspace = true
dagger-sdk.workspace = true
tokio.workspace = true

View File

@@ -0,0 +1,16 @@
use dagger_rust::{build::RustVersion, test::RustTest};
#[tokio::main]
pub async fn main() -> eyre::Result<()> {
let client = dagger_sdk::connect().await?;
RustTest::new(client.clone())
.test(
Some("testdata"),
RustVersion::Nightly,
&["crates/*"],
&["openssl"],
)
.await?;
Ok(())
}

7
examples/rust-test/testdata/Cargo.lock generated vendored Normal file
View File

@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "example_bin"
version = "0.1.0"

View File

@@ -0,0 +1,3 @@
[workspace]
members = ["crates/*"]
resolver = "2"

View File

@@ -0,0 +1 @@
/target

View File

@@ -0,0 +1,8 @@
[package]
name = "example_bin"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@@ -0,0 +1,11 @@
fn main() {
println!("Hello, world!");
}
#[cfg(test)]
mod tests {
#[test]
fn test_main() {
assert_eq!(1, 1)
}
}

3
renovate.json Normal file
View File

@@ -0,0 +1,3 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}

View File

@@ -9,4 +9,4 @@ if [[ -n "$CI_PREFIX" ]]; then
fi
$CMD_PREFIX main
$CMD_PREFIX main --cuddle-please-image="$CUDDLE_PLEASE_IMAGE"

View File

@@ -8,4 +8,4 @@ if [[ -n "$CI_PREFIX" ]]; then
CMD_PREFIX="$CI_PREFIX"
fi
$CMD_PREFIX pull-request
$CMD_PREFIX pull-request --cuddle-please-image="$CUDDLE_PLEASE_IMAGE"