Stop using cargo internal API, too complicated. Switch to programmatically parsing metadata output. Add CI (untested, probably doesn't work), create dockerfile
This commit is contained in:
parent
99e9aad687
commit
6191fd1858
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
target/
|
||||||
46
.gitea/workflows/harmony_composer.yaml
Normal file
46
.gitea/workflows/harmony_composer.yaml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
name: Compile and package harmony_composer
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
runs-on: rust-cargo
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build for Linux x86_64
|
||||||
|
run: cargo build --release --bin harmony_composer --target x86_64-unknown-linux-gnu
|
||||||
|
|
||||||
|
- name: Build for Linux ARM64
|
||||||
|
run: cargo build --release --bin harmony_composer --target aarch64-unknown-linux-gnu
|
||||||
|
|
||||||
|
- name: Build for Windows x86_64 GNU
|
||||||
|
run: cargo build --release --bin harmony_composer --target x86_64-pc-windows-gnu
|
||||||
|
|
||||||
|
# - name: Build for MacOS ARM64
|
||||||
|
# run: cargo build --release --bin harmony_composer --target aarch64-apple-darwin
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binaries
|
||||||
|
path: |
|
||||||
|
target/x86_64-unknown-linux-gnu/release/harmony_composer
|
||||||
|
target/aarch64-unknown-linux-gnu/release/harmony_composer
|
||||||
|
target/x86_64-pc-windows-gnu/release/harmony_composer.exe
|
||||||
|
|
||||||
|
- name: Setup log into hub.nationtech.io
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: hub.nationtech.io
|
||||||
|
username: ${{ secrets.HUB_BOT_USERNAME }}
|
||||||
|
password: ${{ secrets.HUB_BOT_PASSWORD }}
|
||||||
|
|
||||||
|
# TODO: build ARM images too
|
||||||
|
- name: Build docker container
|
||||||
|
run: docker build . -t hub.nationtech.io/harmony/harmony_composer
|
||||||
|
|
||||||
|
- name: Push docker container
|
||||||
|
run: docker push hub.nationtech.io/harmony/harmony_composer
|
||||||
1859
Cargo.lock
generated
1859
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
15
Dockerfile
Normal file
15
Dockerfile
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
FROM docker.io/rust:1.87.0 AS build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN cargo build --release --bin harmony_composer
|
||||||
|
|
||||||
|
FROM docker.io/rust:1.87.0
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=build /app/target/release/harmony_composer .
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/harmony_composer"]
|
||||||
@ -6,14 +6,12 @@ readme.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
assert_cmd = "2.0.17"
|
|
||||||
clap = { version = "4.5.35", features = ["derive"] }
|
clap = { version = "4.5.35", features = ["derive"] }
|
||||||
inquire.workspace = true
|
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
cargo = "0.88.0"
|
|
||||||
bollard = "0.19.0"
|
bollard = "0.19.0"
|
||||||
current_platform = "0.2.0"
|
current_platform = "0.2.0"
|
||||||
futures-util = "0.3.31"
|
futures-util = "0.3.31"
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
|
cargo_metadata = "0.20.0"
|
||||||
|
|||||||
@ -4,13 +4,14 @@ use bollard::query_parameters::{
|
|||||||
RemoveContainerOptions, StartContainerOptions, WaitContainerOptions,
|
RemoveContainerOptions, StartContainerOptions, WaitContainerOptions,
|
||||||
};
|
};
|
||||||
use bollard::secret::HostConfig;
|
use bollard::secret::HostConfig;
|
||||||
|
use cargo_metadata::{Message, MetadataCommand};
|
||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
use futures_util::{StreamExt, TryStreamExt};
|
use futures_util::{StreamExt, TryStreamExt};
|
||||||
use log::info;
|
use log::info;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::format;
|
use std::fmt::format;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::{Command, Stdio};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@ -32,6 +33,7 @@ struct GlobalArgs {
|
|||||||
#[derive(Subcommand, Clone, Debug)]
|
#[derive(Subcommand, Clone, Debug)]
|
||||||
enum Commands {
|
enum Commands {
|
||||||
Check(CheckArgs),
|
Check(CheckArgs),
|
||||||
|
Compile,
|
||||||
Deploy(DeployArgs),
|
Deploy(DeployArgs),
|
||||||
All(AllArgs),
|
All(AllArgs),
|
||||||
}
|
}
|
||||||
@ -97,9 +99,7 @@ async fn main() {
|
|||||||
false => todo!("implement couldn't find path logic"),
|
false => todo!("implement couldn't find path logic"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let check_output = Command::new("sh")
|
let check_output = Command::new(check_script)
|
||||||
.arg("-c")
|
|
||||||
.arg(check_script)
|
|
||||||
.output()
|
.output()
|
||||||
.expect("failed to run check script");
|
.expect("failed to run check script");
|
||||||
info!(
|
info!(
|
||||||
@ -129,6 +129,7 @@ async fn main() {
|
|||||||
Commands::All(_args) => todo!(
|
Commands::All(_args) => todo!(
|
||||||
"take all previous match arms and turn them into separate functions, and call them all one after the other"
|
"take all previous match arms and turn them into separate functions, and call them all one after the other"
|
||||||
),
|
),
|
||||||
|
Commands::Compile => return,
|
||||||
},
|
},
|
||||||
None => todo!("run interactively, ask for info on CLI"),
|
None => todo!("run interactively, ask for info on CLI"),
|
||||||
}
|
}
|
||||||
@ -150,14 +151,17 @@ async fn compile_harmony(
|
|||||||
None => current_platform::CURRENT_PLATFORM.to_string(),
|
None => current_platform::CURRENT_PLATFORM.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let gctx = cargo::util::context::GlobalContext::default().unwrap();
|
let cargo_exists = Command::new("which")
|
||||||
let cargo_exists = gctx.cargo_exe().unwrap().exists();
|
.arg("cargo")
|
||||||
|
.status()
|
||||||
|
.expect("couldn't get `which cargo` status")
|
||||||
|
.success();
|
||||||
|
|
||||||
let method = match method {
|
let method = match method {
|
||||||
Some(m) => m,
|
Some(m) => m,
|
||||||
None => {
|
None => {
|
||||||
if cargo_exists {
|
if cargo_exists {
|
||||||
return compile_cargo(gctx, harmony_location).await;
|
return compile_cargo(platform, harmony_location).await;
|
||||||
} else {
|
} else {
|
||||||
return compile_docker(platform, harmony_location).await;
|
return compile_docker(platform, harmony_location).await;
|
||||||
}
|
}
|
||||||
@ -165,39 +169,64 @@ async fn compile_harmony(
|
|||||||
};
|
};
|
||||||
|
|
||||||
match method {
|
match method {
|
||||||
CompileMethod::LocalCargo => return compile_cargo(gctx, harmony_location).await,
|
CompileMethod::LocalCargo => return compile_cargo(platform, harmony_location).await,
|
||||||
CompileMethod::Docker => return compile_docker(platform, harmony_location).await,
|
CompileMethod::Docker => return compile_docker(platform, harmony_location).await,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn compile_cargo(
|
async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf {
|
||||||
gctx: cargo::util::context::GlobalContext,
|
let metadata = MetadataCommand::new()
|
||||||
harmony_location: String,
|
.manifest_path(format!("{}/Cargo.toml", harmony_location))
|
||||||
) -> PathBuf {
|
.exec()
|
||||||
let cargo_build = cargo::ops::compile(
|
.unwrap();
|
||||||
&cargo::core::Workspace::new(
|
|
||||||
&Path::new(&format!("{}/Cargo.toml", harmony_location))
|
|
||||||
.canonicalize()
|
|
||||||
.expect("Couldn't find Cargo.toml in harmony dir"),
|
|
||||||
&gctx,
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
&cargo::ops::CompileOptions::new(&gctx, cargo::core::compiler::CompileMode::Build).unwrap(),
|
|
||||||
)
|
|
||||||
.expect("build didn't go successfully");
|
|
||||||
|
|
||||||
let bin = cargo_build.binaries.first().expect("no binaries built");
|
let mut cargo_build = Command::new("cargo")
|
||||||
|
.current_dir(&harmony_location)
|
||||||
|
.args(vec![
|
||||||
|
"build",
|
||||||
|
format!("--target={}", platform).as_str(),
|
||||||
|
"--release",
|
||||||
|
"--message-format=json-render-diagnostics",
|
||||||
|
])
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.expect("run cargo command failed");
|
||||||
|
|
||||||
|
let mut bin = cargo_metadata::camino::Utf8PathBuf::new();
|
||||||
|
let reader = std::io::BufReader::new(cargo_build.stdout.take().unwrap());
|
||||||
|
for message in cargo_metadata::Message::parse_stream(reader) {
|
||||||
|
match message.unwrap() {
|
||||||
|
Message::CompilerMessage(_msg) => (),
|
||||||
|
Message::CompilerArtifact(artifact) => {
|
||||||
|
// println!("{:?}", artifact);
|
||||||
|
if artifact.package_id.eq(&metadata
|
||||||
|
.root_package()
|
||||||
|
.expect("failed to get root package")
|
||||||
|
.id)
|
||||||
|
{
|
||||||
|
bin = artifact
|
||||||
|
.filenames
|
||||||
|
.first()
|
||||||
|
.expect("couldn't get artifact filename")
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::BuildScriptExecuted(_script) => (),
|
||||||
|
Message::BuildFinished(finished) => {
|
||||||
|
println!("{:?}", finished);
|
||||||
|
}
|
||||||
|
_ => (), // Unknown message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let bin = cargo_build.binaries.first().expect("no binaries built");
|
||||||
let bin_out;
|
let bin_out;
|
||||||
if let Some(ext) = bin.path.extension() {
|
if let Some(ext) = bin.extension() {
|
||||||
bin_out = PathBuf::from(format!(
|
bin_out = PathBuf::from(format!("{}/harmony.{}", harmony_location, ext));
|
||||||
"{}/harmony.{}",
|
let _copy_res = fs::copy(&bin, &bin_out).await;
|
||||||
harmony_location,
|
|
||||||
ext.to_str().expect("couldn't convert OsStr to str")
|
|
||||||
));
|
|
||||||
let _copy_res = fs::copy(bin.path.clone(), bin_out.clone()).await;
|
|
||||||
} else {
|
} else {
|
||||||
bin_out = PathBuf::from(format!("{}/harmony", harmony_location));
|
bin_out = PathBuf::from(format!("{}/harmony", harmony_location));
|
||||||
let _copy_res = fs::copy(bin.path.clone(), bin_out.clone()).await;
|
let _copy_res = fs::copy(&bin, &bin_out).await;
|
||||||
}
|
}
|
||||||
return bin_out;
|
return bin_out;
|
||||||
}
|
}
|
||||||
@ -232,14 +261,12 @@ async fn compile_docker(platform: String, harmony_location: String) -> PathBuf {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let config = ContainerCreateBody {
|
let config = ContainerCreateBody {
|
||||||
image: Some("docker.io/rust:1.87.0".to_string()),
|
image: Some("hub.nationtech.io/harmony/harmony_composer".to_string()),
|
||||||
working_dir: Some("/mnt".to_string()),
|
working_dir: Some("/mnt".to_string()),
|
||||||
entrypoint: Some(vec![
|
cmd: Some(vec![
|
||||||
"cargo".to_string(),
|
format!("--compile-platform={}", platform),
|
||||||
"build".to_string(),
|
format!("--harmony-path=/mnt"),
|
||||||
format!("--target={}", platform),
|
"compile".to_string(),
|
||||||
"--release".to_string(),
|
|
||||||
"--message-format=json".to_string(),
|
|
||||||
]),
|
]),
|
||||||
host_config: Some(HostConfig {
|
host_config: Some(HostConfig {
|
||||||
binds: Some(vec![format!("{}:/mnt", harmony_location)]),
|
binds: Some(vec![format!("{}:/mnt", harmony_location)]),
|
||||||
@ -276,49 +303,18 @@ async fn compile_docker(platform: String, harmony_location: String) -> PathBuf {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut bin_out = PathBuf::from(format!("{}/harmony", harmony_location));
|
|
||||||
|
|
||||||
while let Some(l) = logs_stream.next().await {
|
while let Some(l) = logs_stream.next().await {
|
||||||
let l_str = l.expect("couldn't unwrap logoutput").to_string();
|
let l_str = l.expect("couldn't unwrap logoutput").to_string();
|
||||||
println!("{}", l_str);
|
println!("{}", l_str);
|
||||||
|
|
||||||
let l_json: Option<serde_json::Value> =
|
|
||||||
serde_json::from_str(l_str.as_str()).unwrap_or(None);
|
|
||||||
|
|
||||||
match l_json {
|
|
||||||
Some(j) => match j.get("manifest_path") {
|
|
||||||
Some(p) => {
|
|
||||||
if p == "/mnt/Cargo.toml" {
|
|
||||||
let bin = PathBuf::from(format!(
|
|
||||||
"{}/{}",
|
|
||||||
harmony_location,
|
|
||||||
j.get("executable")
|
|
||||||
.expect("couldn't get json executable")
|
|
||||||
.as_str()
|
|
||||||
.expect("couldn't get json executable as str")
|
|
||||||
.replacen("/mnt/", "", 1)
|
|
||||||
));
|
|
||||||
if let Some(ext) = bin.extension() {
|
|
||||||
bin_out = PathBuf::from(format!(
|
|
||||||
"{}/harmony.{}",
|
|
||||||
harmony_location,
|
|
||||||
ext.to_str().expect("couldn't convert OsStr to str")
|
|
||||||
));
|
|
||||||
let _copy_res = fs::copy(bin.clone(), bin_out.clone()).await;
|
|
||||||
} else {
|
|
||||||
bin_out = PathBuf::from(format!("{}/harmony", harmony_location));
|
|
||||||
let _copy_res = fs::copy(bin.clone(), bin_out.clone()).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => (),
|
|
||||||
},
|
|
||||||
None => (),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait until container is no longer running
|
// wait until container is no longer running
|
||||||
while let Some(_) = wait.next().await {}
|
while let Some(_) = wait.next().await {}
|
||||||
|
|
||||||
return bin_out;
|
// hack that should be cleaned up
|
||||||
|
if platform.contains("windows") {
|
||||||
|
return PathBuf::from(format!("{}/harmony.exe", harmony_location));
|
||||||
|
} else {
|
||||||
|
return PathBuf::from(format!("{}/harmony", harmony_location));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user