Skip to content

Commit

Permalink
Merge pull request #1225 from AmbientRun/package-versions
Browse files Browse the repository at this point in the history
Package versions
  • Loading branch information
philpax authored Dec 18, 2023
2 parents 61a060c + acf0ccf commit c8d4d3e
Show file tree
Hide file tree
Showing 68 changed files with 402 additions and 251 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ These PRs are not directly user-facing, but improve the development experience.

#### Headline features

- Packages can now be depended on by ID and version (i.e. `id = "viyiawgsl5lsiul6pup6pyv6bbt6o3vw", version = "0.3.2-nightly-2023-12-06"`) instead of by `deployment`. This is recommended for all future packages, as it makes it easier to understand which package is being used. See the [package documentation](https://ambientrun.github.io/Ambient/reference/package.html) for details.

#### Other

### Changed
Expand All @@ -51,6 +53,7 @@ These PRs are not directly user-facing, but improve the development experience.
ambient_package_projection::generate();
}
```
- Ambient will no longer update the `deployment` field of dependencies; instead, it will insert the version of that dependency, and that version is not automatically updated. The new `--version` argument can be used to update the versions of every package in your dependency tree: `ambient deploy --version 0.3`.

#### Non-breaking

Expand Down
133 changes: 72 additions & 61 deletions app/src/cli/package/deploy.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::{collections::HashMap, path::PathBuf, sync::Arc};

use ambient_native_std::asset_cache::{AssetCache, SyncAssetKeyExt};
use ambient_package::Manifest;
use ambient_settings::SettingsKey;
use anyhow::Context;
use clap::Parser;
Expand All @@ -13,6 +12,7 @@ use super::PackageArgs;
use colored::Colorize;

#[derive(Parser, Clone, Debug)]
#[command(disable_version_flag = true)]
/// Deploys the package
pub struct Deploy {
#[command(flatten)]
Expand All @@ -36,6 +36,10 @@ pub struct Deploy {
/// Context to run the package in
#[arg(long, requires("ensure_running"), default_value = "")]
pub context: String,
/// When supplied, updates the versions of all packages that will be deployed.
/// It is very likely that you will need to use this to deploy a tree of packages.
#[arg(long)]
pub version: Option<String>,
}

pub async fn handle(args: &Deploy, assets: &AssetCache, release_build: bool) -> anyhow::Result<()> {
Expand All @@ -47,6 +51,7 @@ pub async fn handle(args: &Deploy, assets: &AssetCache, release_build: bool) ->
force_upload,
ensure_running,
context,
version,
} = args;

if !release_build {
Expand Down Expand Up @@ -77,22 +82,21 @@ pub async fn handle(args: &Deploy, assets: &AssetCache, release_build: bool) ->
#[derive(Debug, Clone)]
enum Deployment {
Skipped,
Deployed {
deployment_id: String,
manifest: Manifest,
},
Deployed { package_id: String, version: String },
}
impl Deployment {
fn as_deployed(&self) -> Option<String> {
fn as_deployed(&self) -> Option<(String, String)> {
match self {
Self::Deployed { deployment_id, .. } => Some(deployment_id.clone()),
Self::Deployed {
package_id,
version,
} => Some((package_id.clone(), version.clone())),
_ => None,
}
}
}

let manifest_path_to_deployment_id =
Arc::new(Mutex::new(HashMap::<PathBuf, Deployment>::new()));
let manifest_path_to_version = Arc::new(Mutex::new(HashMap::<PathBuf, Deployment>::new()));

let Some(main_package_fs_path) = package.package_path()?.fs_path else {
anyhow::bail!("Can only deploy a local package");
Expand All @@ -109,13 +113,9 @@ pub async fn handle(args: &Deploy, assets: &AssetCache, release_build: bool) ->
all_packages
};

let mut first_deployment_id = None;
let mut first_root_deployment = None;
for package_path in all_package_paths {
let skip_building = manifest_path_to_deployment_id
.lock()
.keys()
.cloned()
.collect();
let skip_building = manifest_path_to_version.lock().keys().cloned().collect();

let result = build::build(
assets,
Expand All @@ -128,7 +128,9 @@ pub async fn handle(args: &Deploy, assets: &AssetCache, release_build: bool) ->
|package_manifest_path| {
// Before the build, rewrite all known dependencies to use their deployed version
// if available.
let manifest_path_to_deployment_id = manifest_path_to_deployment_id.clone();
//
// Additionally, update the package's version if required.
let manifest_path_to_version = manifest_path_to_version.clone();
async move {
let package_path = package_manifest_path.parent().unwrap();

Expand All @@ -137,45 +139,55 @@ pub async fn handle(args: &Deploy, assets: &AssetCache, release_build: bool) ->
.await?
.parse()?;

let Some(dependencies) = manifest.as_table_mut().get_mut("dependencies") else {
return Ok(());
};
let mut edited = false;

for (_, dependency) in dependencies.as_table_like_mut().unwrap().iter_mut() {
let Some(dependency) = dependency.as_table_like_mut() else {
continue;
};
let Some(dependency_path) = dependency.get("path").and_then(|i| i.as_str())
else {
continue;
};
if let Some(dependencies) = manifest.as_table_mut().get_mut("dependencies") {
for (_, dependency) in dependencies.as_table_like_mut().unwrap().iter_mut()
{
let Some(dependency) = dependency.as_table_like_mut() else {
continue;
};
let Some(dependency_path) =
dependency.get("path").and_then(|i| i.as_str())
else {
continue;
};

let dependency_manifest_path = ambient_std::path::normalize(
&package_path.join(dependency_path).join("ambient.toml"),
);
let dependency_manifest_path = ambient_std::path::normalize(
&package_path.join(dependency_path).join("ambient.toml"),
);

if let Some(deployment_id) = manifest_path_to_deployment_id
.lock()
.get(&dependency_manifest_path)
.cloned()
.and_then(|d| d.as_deployed())
{
dependency.insert("deployment", toml_edit::value(deployment_id));
if let Some((_, version)) = manifest_path_to_version
.lock()
.get(&dependency_manifest_path)
.and_then(|d| d.as_deployed())
{
dependency.insert("version", toml_edit::value(version));
}
}

edited = true;
}

tokio::fs::write(&package_manifest_path, manifest.to_string()).await?;
if let Some(version) = &version {
manifest["package"]["version"] = toml_edit::value(version.clone());
edited = true;
}

if edited {
tokio::fs::write(&package_manifest_path, manifest.to_string()).await?;
}

Ok(())
}
},
|package_manifest_path, build_path, was_built| {
// After build, deploy the package.
let manifest_path_to_deployment_id = manifest_path_to_deployment_id.clone();
let manifest_path_to_version = manifest_path_to_version.clone();
let package_path = package_manifest_path.parent().unwrap().to_owned();
async move {
let deployment = if was_built {
let deployment = ambient_deploy::deploy(
let (_deployment_id, manifest) = ambient_deploy::deploy(
api_server,
token,
&build_path,
Expand All @@ -184,8 +196,8 @@ pub async fn handle(args: &Deploy, assets: &AssetCache, release_build: bool) ->
)
.await?;
Deployment::Deployed {
deployment_id: deployment.0,
manifest: deployment.1,
package_id: manifest.package.id.expect("no package ID").to_string(),
version: manifest.package.version.to_string(),
}
} else {
// TODO: this check does not actually save much, as the process of deploying
Expand All @@ -198,7 +210,7 @@ pub async fn handle(args: &Deploy, assets: &AssetCache, release_build: bool) ->
Deployment::Skipped
};

manifest_path_to_deployment_id
manifest_path_to_version
.lock()
.insert(package_manifest_path.to_owned(), deployment);

Expand All @@ -210,48 +222,47 @@ pub async fn handle(args: &Deploy, assets: &AssetCache, release_build: bool) ->

let main_package_name = result.main_package_name;

let deployment_id = manifest_path_to_deployment_id
let deployment = manifest_path_to_version
.lock()
.get(&package_path.join("ambient.toml"))
.cloned()
.context("Main package was not processed; this is a bug")?;

match deployment_id {
match &deployment {
Deployment::Skipped => {
tracing::info!(
"Package \"{main_package_name}\" was already deployed, skipping deployment"
);
}
Deployment::Deployed {
deployment_id,
manifest,
package_id,
version,
} => {
let ensure_running_url = ambient_shared_types::urls::ensure_running_url(
ambient_shared_types::urls::ServerSelector::Deployment(&deployment_id),
);
let web_url = ambient_shared_types::urls::web_package_url(
manifest
.package
.id
.expect("no package ID - this is a bug")
.as_str(),
Some(&deployment_id),
ambient_shared_types::urls::ServerSelector::Package {
id: package_id,
version: Some(version.as_str()),
},
);
let web_url =
ambient_shared_types::urls::web_package_url(package_id, Some(version.as_str()));

tracing::info!("Package \"{main_package_name}\" deployed successfully!");
tracing::info!(" Deployment ID: {deployment_id}");
tracing::info!(" Join: ambient join '{ensure_running_url}'");
tracing::info!(" Web URL: '{}'", web_url.bright_green());

if first_deployment_id.is_none() {
first_deployment_id = Some(deployment_id);
if first_root_deployment.is_none() {
first_root_deployment = Some(deployment);
}
}
}
}

if let Some(deployment_id) = first_deployment_id.filter(|_| *ensure_running) {
let spec = ambient_cloud_client::ServerSpec::new_with_deployment(deployment_id)
if let Some((package_id, version)) = first_root_deployment
.filter(|_| *ensure_running)
.and_then(|d| d.as_deployed())
{
let spec = ambient_cloud_client::ServerSpec::new_with_package(package_id, version)
.with_context(context.to_string());
let server =
ambient_cloud_client::ensure_server_running(assets, api_server, token.into(), spec)
Expand Down
20 changes: 13 additions & 7 deletions campfire/src/join.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ pub struct Join {
/// The package to join
#[arg(long, short)]
pub package: Option<String>,
/// The version of the package to join. Only valid when `package` is specified.
#[arg(long, short)]
pub version: Option<String>,
/// The context ID to use while joining
#[arg(long, short)]
pub context: Option<String>,
Expand All @@ -27,16 +30,19 @@ pub struct Join {
pub fn main(join: &Join) -> anyhow::Result<()> {
let mut args = vec!["join"];

let mut url = match (&join.url, &join.deployment, &join.package) {
(Some(url), None, None) => Some(url.to_string()),
(None, Some(deployment), None) => Some(urls::ensure_running_url(
let mut url = match (&join.url, &join.deployment, &join.package, &join.version) {
(Some(url), None, None, None) => Some(url.to_string()),
(None, Some(deployment), None, None) => Some(urls::ensure_running_url(
urls::ServerSelector::Deployment(deployment),
)),
(None, None, Some(package)) => Some(urls::ensure_running_url(
urls::ServerSelector::Package(package),
)),
(None, None, Some(package), version) => {
Some(urls::ensure_running_url(urls::ServerSelector::Package {
id: package,
version: version.as_deref(),
}))
}

(None, None, None) => {
(None, None, None, None) => {
tracing::info!("no join method specified, joining local server");
None
}
Expand Down
22 changes: 12 additions & 10 deletions campfire/src/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,19 +284,21 @@ fn get_all_examples(include_testcases: bool) -> anyhow::Result<Vec<PathBuf>> {

for category in dirs {
for example in all_directories_in(&category.path())? {
examples.push(example.path());
let example_path = example.path();
examples.push(example_path.clone());

// Hacky workaround for dependencies example
{
let deps_path = example_path.join("deps");
if deps_path.is_dir() {
for deps_package in all_directories_in(&deps_path)? {
examples.push(deps_package.path());
}
}
}
}
}

for deps_example_deps in all_directories_in(
&examples_path
.join("intermediate")
.join("dependencies")
.join("deps"),
)? {
examples.push(deps_example_deps.path());
}

if include_testcases {
let testcases_path = guest.path().join("testcases");
if testcases_path.exists() {
Expand Down
42 changes: 42 additions & 0 deletions campfire/src/release.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,33 @@ fn update_version(
for path in get_all_packages(true, true, true)? {
edit_toml(path.join("ambient.toml"), |toml| {
add_ambient_version(toml, new_version);

let Some(dependencies) = toml
.get_mut("dependencies")
.and_then(|t| t.as_table_like_mut())
else {
return;
};

for (_, value) in dependencies.iter_mut() {
let Some(table) = value.as_table_like_mut() else {
continue;
};

let Some(dep_path) = table.get("path").and_then(|t| t.as_str()) else {
continue;
};

let dep_ambient_toml_path = path
.join(dep_path)
.join("ambient.toml")
.canonicalize()
.unwrap();
let dep_id = read_id_from_ambient_toml(&dep_ambient_toml_path).unwrap();

table.insert("id", toml_edit::value(dep_id));
table.insert("version", toml_edit::value(new_version));
}
})?;
}
}
Expand Down Expand Up @@ -531,6 +558,21 @@ fn edit_toml(
.with_context(|| format!("Failed to edit file {:?}", path.as_ref()))
}

fn read_id_from_ambient_toml(ambient_toml: &Path) -> anyhow::Result<String> {
let ambient_toml = std::fs::read_to_string(ambient_toml)?.parse::<toml_edit::Document>()?;
let package = ambient_toml
.get("package")
.context("no package in ambient.toml")?
.as_table_like()
.context("package is not a table")?;
let id = package
.get("id")
.context("no id in package")?
.as_str()
.context("id is not a string")?;
Ok(id.to_string())
}

fn check_docker_build() -> anyhow::Result<()> {
tracing::info!("Building Docker image...");
let success = Command::new("docker")
Expand Down
Loading

0 comments on commit c8d4d3e

Please sign in to comment.