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

feat: Add support for additional package manifest keys #6868

Merged
merged 6 commits into from
Jan 29, 2025
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
17 changes: 16 additions & 1 deletion docs/book/src/forc/manifest_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ The `Forc.toml` (the _manifest_ file) is a compulsory file for each package and

* [`[project]`](#the-project-section) — Defines a sway project.
* `name` — The name of the project.
* `version` — The version of the project.
* `description` — A description of the project.
* `authors` — The authors of the project.
* `organization` — The organization of the project.
* `license`— The project license.
* `license` — The project license.
* `homepage` — URL of the project homepage.
* `repository` — URL of the project source repository.
* `documentation` — URL of the project documentation.
* `entry` — The entry point for the compiler to start parsing from.
* For the recommended way of selecting an entry point of large libraries please take a look at: [Libraries](./../sway-program-types/libraries.md)
* `implicit-std` - Controls whether provided `std` version (with the current `forc` version) will get added as a dependency _implicitly_. _Unless you know what you are doing, leave this as default._
Expand All @@ -29,6 +34,11 @@ An example `Forc.toml` is shown below. Under `[project]` the following fields ar

* `authors`
* `organization`
* `version`
* `description`
* `homepage`
* `repository`
* `documentation`

Also for the following fields, a default value is provided so omitting them is allowed:

Expand All @@ -39,6 +49,11 @@ Also for the following fields, a default value is provided so omitting them is a
[project]
authors = ["user"]
entry = "main.sw"
description = "Wallet contract"
version = "1.0.0"
homepage = "https://example.com/"
repository = "https://example.com/"
documentation = "https://example.com/"
organization = "Fuel_Labs"
license = "Apache-2.0"
name = "wallet_contract"
Expand Down
81 changes: 66 additions & 15 deletions forc-pkg/src/manifest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use crate::pkg::{manifest_file_missing, parsing_failed, wrong_program_type};
use anyhow::{anyhow, bail, Context, Result};
use forc_tracing::println_warning;
use forc_util::{validate_name, validate_project_name};
use serde::{Deserialize, Serialize};
use semver::Version;
use serde::{de, Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr};
use std::{
collections::{BTreeMap, HashMap},
Expand All @@ -19,6 +20,7 @@ use sway_utils::{
constants, find_nested_manifest_dir, find_parent_manifest_dir,
find_parent_manifest_dir_with_check,
};
use url::Url;

use self::build_profile::BuildProfile;

Expand Down Expand Up @@ -193,9 +195,15 @@ pub struct PackageManifest {
#[serde(rename_all = "kebab-case")]
pub struct Project {
pub authors: Option<Vec<String>>,
#[serde(deserialize_with = "validate_package_name")]
pub name: String,
pub version: Option<Version>,
pub description: Option<String>,
pub organization: Option<String>,
pub license: String,
pub homepage: Option<Url>,
pub repository: Option<Url>,
pub documentation: Option<Url>,
#[serde(default = "default_entry")]
pub entry: String,
pub implicit_std: Option<bool>,
Expand All @@ -205,6 +213,18 @@ pub struct Project {
pub metadata: Option<toml::Value>,
}

// Validation function for the `name` field
fn validate_package_name<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: de::Deserializer<'de>,
{
let name: String = Deserialize::deserialize(deserializer)?;
match validate_project_name(&name) {
Ok(_) => Ok(name),
Err(e) => Err(de::Error::custom(e.to_string())),
}
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct Network {
Expand Down Expand Up @@ -318,6 +338,14 @@ impl Dependency {
Self::Detailed(ref det) => det.package.as_deref(),
}
}

/// The string of the `version` field if specified.
pub fn version(&self) -> Option<&str> {
match *self {
Self::Simple(ref version) => Some(version),
Self::Detailed(ref det) => det.version.as_deref(),
}
}
}

impl PackageManifestFile {
Expand Down Expand Up @@ -571,10 +599,25 @@ impl PackageManifest {
// package or a workspace. While doing so, we should be printing the warnings if the given
// file parses so that we only see warnings for the correct type of manifest.
let path = path.as_ref();
let mut warnings = vec![];
let manifest_str = std::fs::read_to_string(path)
let contents = std::fs::read_to_string(path)
.map_err(|e| anyhow!("failed to read manifest at {:?}: {}", path, e))?;
let toml_de = toml::de::Deserializer::new(&manifest_str);
Self::from_string(contents)
}

/// Given a path to a `Forc.toml`, read it and construct a `PackageManifest`.
///
/// This also `validate`s the manifest, returning an `Err` in the case that invalid names,
/// fields were used.
///
/// If `core` and `std` are unspecified, `std` will be added to the `dependencies` table
/// implicitly. In this case, the git tag associated with the version of this crate is used to
/// specify the pinned commit at which we fetch `std`.
pub fn from_string(contents: String) -> Result<Self> {
// While creating a `ManifestFile` we need to check if the given path corresponds to a
// package or a workspace. While doing so, we should be printing the warnings if the given
// file parses so that we only see warnings for the correct type of manifest.
let mut warnings = vec![];
let toml_de = toml::de::Deserializer::new(&contents);
let mut manifest: Self = serde_ignored::deserialize(toml_de, |path| {
let warning = format!("unused manifest key: {path}");
warnings.push(warning);
Expand Down Expand Up @@ -1338,6 +1381,11 @@ mod tests {
let project = Project {
authors: Some(vec!["Test Author".to_string()]),
name: "test-project".to_string(),
version: Some(Version::parse("0.1.0").unwrap()),
description: Some("test description".to_string()),
homepage: None,
documentation: None,
repository: None,
organization: None,
license: "Apache-2.0".to_string(),
entry: "main.sw".to_string(),
Expand All @@ -1359,6 +1407,11 @@ mod tests {
let project = Project {
authors: Some(vec!["Test Author".to_string()]),
name: "test-project".to_string(),
version: Some(Version::parse("0.1.0").unwrap()),
description: Some("test description".to_string()),
homepage: Some(Url::parse("https://example.com").unwrap()),
documentation: Some(Url::parse("https://docs.example.com").unwrap()),
repository: Some(Url::parse("https://example.com").unwrap()),
organization: None,
license: "Apache-2.0".to_string(),
entry: "main.sw".to_string(),
Expand All @@ -1372,6 +1425,11 @@ mod tests {
let deserialized: Project = toml::from_str(&serialized).unwrap();

assert_eq!(project.name, deserialized.name);
assert_eq!(project.version, deserialized.version);
assert_eq!(project.description, deserialized.description);
assert_eq!(project.homepage, deserialized.homepage);
assert_eq!(project.documentation, deserialized.documentation);
assert_eq!(project.repository, deserialized.repository);
assert_eq!(project.metadata, deserialized.metadata);
assert_eq!(project.metadata, None);
}
Expand All @@ -1383,13 +1441,11 @@ mod tests {
license = "Apache-2.0"
entry = "main.sw"
authors = ["Test Author"]

[metadata]
description = "A test project"
version = "1.0.0"
homepage = "https://example.com"
documentation = "https://docs.example.com"
repository = "https://github.com/example/test-project"

[metadata]
mykey = "https://example.com"
keywords = ["test", "project"]
categories = ["test"]
"#;
Expand All @@ -1401,12 +1457,7 @@ mod tests {
let table = metadata.as_table().unwrap();

assert_eq!(
table.get("description").unwrap().as_str().unwrap(),
"A test project"
);
assert_eq!(table.get("version").unwrap().as_str().unwrap(), "1.0.0");
assert_eq!(
table.get("homepage").unwrap().as_str().unwrap(),
table.get("mykey").unwrap().as_str().unwrap(),
"https://example.com"
);

Expand Down
Loading