Skip to content

Commit 939c7f1

Browse files
authored
Add include field to the manifest (#1842)
fixes #1770 --- **Stack**: - #1846 - #1836 - #1842⚠️ *Part of a stack created by [spr](https://github.com/ejoffe/spr). Do not merge manually using the UI - doing so may have unexpected results.*
1 parent 328062c commit 939c7f1

File tree

9 files changed

+234
-15
lines changed

9 files changed

+234
-15
lines changed

scarb/src/core/manifest/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ pub struct ManifestMetadata {
7575
pub license_file: Option<Utf8PathBuf>,
7676
pub readme: Option<Utf8PathBuf>,
7777
pub repository: Option<String>,
78+
pub include: Option<Vec<Utf8PathBuf>>,
7879
#[serde(rename = "tool")]
7980
pub tool_metadata: Option<BTreeMap<SmolStr, Value>>,
8081
pub cairo_version: Option<VersionReq>,

scarb/src/core/manifest/toml_manifest.rs

+2
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ pub struct TomlPackage {
197197
pub license_file: Option<MaybeWorkspaceField<Utf8PathBuf>>,
198198
pub readme: Option<MaybeWorkspaceField<PathOrBool>>,
199199
pub repository: Option<MaybeWorkspaceField<String>>,
200+
pub include: Option<Vec<Utf8PathBuf>>,
200201
/// **UNSTABLE** This package does not depend on Cairo's `core`.
201202
pub no_core: Option<bool>,
202203
pub cairo_version: Option<MaybeWorkspaceField<VersionReq>>,
@@ -571,6 +572,7 @@ impl TomlManifest {
571572
.clone()
572573
.map(|mw| mw.resolve("repository", || inheritable_package.repository()))
573574
.transpose()?,
575+
include: package.include.clone(),
574576
cairo_version: package
575577
.cairo_version
576578
.clone()

scarb/src/core/package/mod.rs

+22-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::fmt;
22
use std::ops::Deref;
33
use std::sync::Arc;
44

5-
use anyhow::{anyhow, Result};
5+
use anyhow::{anyhow, Context, Result};
66
use camino::{Utf8Path, Utf8PathBuf};
77
use serde::Deserialize;
88

@@ -12,6 +12,7 @@ use scarb_ui::args::WithManifestPath;
1212

1313
use crate::core::manifest::Manifest;
1414
use crate::core::{Target, TargetKind};
15+
use crate::internal::fsx;
1516

1617
mod id;
1718
mod name;
@@ -105,6 +106,26 @@ impl Package {
105106
.get(tool_name)
106107
}
107108

109+
pub fn include(&self) -> Result<Vec<Utf8PathBuf>> {
110+
self.manifest
111+
.as_ref()
112+
.metadata
113+
.include
114+
.as_ref()
115+
.map(|include| {
116+
include
117+
.iter()
118+
.map(|path| {
119+
let path = self.root().join(path);
120+
let path = fsx::canonicalize_utf8(&path)
121+
.with_context(|| format!("failed to find included file at {path}"))?;
122+
Ok(path)
123+
})
124+
.collect::<Result<Vec<_>>>()
125+
})
126+
.unwrap_or_else(|| Ok(Vec::new()))
127+
}
128+
108129
pub fn fetch_tool_metadata(&self, tool_name: &str) -> Result<&toml::Value> {
109130
self.tool_metadata(tool_name)
110131
.ok_or_else(|| anyhow!("package manifest `{self}` has no [tool.{tool_name}] section"))

scarb/src/core/publishing/manifest_normalization.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
use std::collections::BTreeMap;
22

3-
use anyhow::{bail, Result};
4-
use camino::Utf8PathBuf;
5-
use indoc::formatdoc;
6-
73
use crate::core::{TomlCairoPluginTargetParams, TomlTarget};
84
use crate::{
95
core::{
@@ -13,6 +9,10 @@ use crate::{
139
},
1410
DEFAULT_LICENSE_FILE_NAME, DEFAULT_README_FILE_NAME,
1511
};
12+
use anyhow::{bail, Result};
13+
use camino::Utf8PathBuf;
14+
use indoc::formatdoc;
15+
use itertools::Itertools;
1616

1717
pub fn prepare_manifest_for_publish(pkg: &Package) -> Result<TomlManifest> {
1818
let package = Some(generate_package(pkg));
@@ -73,6 +73,10 @@ fn generate_package(pkg: &Package) -> Box<TomlPackage> {
7373
.clone()
7474
.map(|_| MaybeWorkspace::Defined((Utf8PathBuf::from(DEFAULT_README_FILE_NAME)).into())),
7575
repository: metadata.repository.clone().map(MaybeWorkspace::Defined),
76+
include: metadata.include.as_ref().map(|x| {
77+
// Sort for stability.
78+
x.iter().sorted().cloned().collect_vec()
79+
}),
7680
no_core: summary.no_core.then_some(true),
7781
cairo_version: metadata.cairo_version.clone().map(MaybeWorkspace::Defined),
7882
experimental_features: pkg.manifest.experimental_features.clone(),

scarb/src/core/publishing/source.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,11 @@ fn push_worktree_files(pkg: &Package, ret: &mut Vec<Utf8PathBuf>) -> Result<()>
7777
true
7878
}
7979
};
80-
81-
WalkBuilder::new(pkg.root())
80+
let mut builder = WalkBuilder::new(pkg.root());
81+
for path in pkg.include()? {
82+
builder.add(&path);
83+
}
84+
builder
8285
.follow_links(true)
8386
.standard_filters(true)
8487
.parents(false)

scarb/src/ops/package.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
22
use std::fs::File;
33
use std::io::{Seek, SeekFrom, Write};
44

5-
use anyhow::{bail, ensure, Context, Result};
5+
use anyhow::{anyhow, bail, ensure, Context, Result};
66
use camino::Utf8PathBuf;
77
use indoc::{formatdoc, indoc, writedoc};
88

@@ -402,7 +402,10 @@ fn source_files(pkg: &Package) -> Result<ArchiveRecipe> {
402402
list_source_files(pkg)?
403403
.into_iter()
404404
.map(|on_disk| {
405-
let path = on_disk.strip_prefix(pkg.root())?.to_owned();
405+
let path = on_disk
406+
.strip_prefix(pkg.root())
407+
.map_err(|_| anyhow!("file `{on_disk}` is not part of `{}`", pkg.id.name))?
408+
.to_owned();
406409
Ok(ArchiveFile {
407410
path,
408411
contents: ArchiveFileContents::OnDisk(on_disk),

scarb/tests/package.rs

+151
Original file line numberDiff line numberDiff line change
@@ -1525,3 +1525,154 @@ fn package_with_publish_disabled() {
15251525
[..]Packaged [..] files, [..] ([..] compressed)
15261526
"#});
15271527
}
1528+
1529+
#[test]
1530+
fn can_include_additional_files() {
1531+
let t = TempDir::new().unwrap();
1532+
simple_project()
1533+
.manifest_package_extra(indoc! {r#"
1534+
include = ["target/file.txt", "target/some/", "other/file.txt", "other/some"]
1535+
"#})
1536+
.build(&t);
1537+
1538+
t.child("target/file.txt")
1539+
.write_str("some file content")
1540+
.unwrap();
1541+
t.child("target/some/file.txt")
1542+
.write_str("some file content")
1543+
.unwrap();
1544+
t.child("other/file.txt")
1545+
.write_str("some file content")
1546+
.unwrap();
1547+
t.child("other/some/dir/file.txt")
1548+
.write_str("some file content")
1549+
.unwrap();
1550+
t.child("other/some/dir/other.txt")
1551+
.write_str("some file content")
1552+
.unwrap();
1553+
1554+
t.child(".gitignore").write_str("target").unwrap();
1555+
t.child(".scarbignore").write_str("other").unwrap();
1556+
1557+
Scarb::quick_snapbox()
1558+
.arg("package")
1559+
.arg("--no-metadata")
1560+
.current_dir(&t)
1561+
.assert()
1562+
.success()
1563+
.stdout_matches(indoc! {r#"
1564+
[..] Packaging foo v1.0.0 [..]
1565+
[..] Verifying foo-1.0.0.tar.zst
1566+
[..] Compiling foo v1.0.0 ([..])
1567+
[..] Finished `dev` profile target(s) in [..]
1568+
[..] Packaged [..] files, [..] ([..] compressed)
1569+
"#});
1570+
1571+
PackageChecker::assert(&t.child("target/package/foo-1.0.0.tar.zst"))
1572+
.name_and_version("foo", "1.0.0")
1573+
.contents(&[
1574+
"VERSION",
1575+
"Scarb.orig.toml",
1576+
"Scarb.toml",
1577+
"src/lib.cairo",
1578+
"src/foo.cairo",
1579+
"other/some/dir/other.txt",
1580+
"other/some/dir/file.txt",
1581+
"other/file.txt",
1582+
"target/some/file.txt",
1583+
"target/file.txt",
1584+
])
1585+
.file_eq("VERSION", "1")
1586+
.file_eq_path("Scarb.orig.toml", t.child("Scarb.toml"))
1587+
.file_eq_path("src/lib.cairo", t.child("src/lib.cairo"))
1588+
.file_eq_path("src/foo.cairo", t.child("src/foo.cairo"))
1589+
.file_eq_path(
1590+
"other/some/dir/other.txt",
1591+
t.child("other/some/dir/other.txt"),
1592+
)
1593+
.file_eq_path(
1594+
"other/some/dir/file.txt",
1595+
t.child("other/some/dir/file.txt"),
1596+
)
1597+
.file_eq_path("other/file.txt", t.child("other/file.txt"))
1598+
.file_eq_path("target/some/file.txt", t.child("target/some/file.txt"))
1599+
.file_eq_path("target/file.txt", t.child("target/file.txt"))
1600+
.file_matches_nl(
1601+
"Scarb.toml",
1602+
indoc! {r#"
1603+
# Code generated by scarb package -p foo; DO NOT EDIT.
1604+
#
1605+
# When uploading packages to the registry Scarb will automatically
1606+
# "normalize" Scarb.toml files for maximal compatibility
1607+
# with all versions of Scarb and also rewrite `path` dependencies
1608+
# to registry dependencies.
1609+
#
1610+
# If you are reading this file be aware that the original Scarb.toml
1611+
# will likely look very different (and much more reasonable).
1612+
# See Scarb.orig.toml for the original contents.
1613+
1614+
[package]
1615+
name = "foo"
1616+
version = "1.0.0"
1617+
edition = "2023_01"
1618+
include = [
1619+
"other/file.txt",
1620+
"other/some",
1621+
"target/file.txt",
1622+
"target/some/",
1623+
]
1624+
1625+
[dependencies]
1626+
"#},
1627+
);
1628+
}
1629+
1630+
#[test]
1631+
fn files_outside_package_cannot_be_included() {
1632+
let t = TempDir::new().unwrap();
1633+
let pkg = t.child("pkg");
1634+
simple_project()
1635+
.manifest_package_extra(indoc! {r#"
1636+
include = ["../some/file.txt"]
1637+
"#})
1638+
.build(&pkg);
1639+
t.child("some/file.txt")
1640+
.write_str("some file content")
1641+
.unwrap();
1642+
Scarb::quick_snapbox()
1643+
.arg("package")
1644+
.arg("--no-metadata")
1645+
.current_dir(&pkg)
1646+
.assert()
1647+
.failure()
1648+
.stdout_matches(indoc! {r#"
1649+
[..] Packaging foo v1.0.0 [..]
1650+
error: file `[..]file.txt` is not part of `foo`
1651+
"#});
1652+
}
1653+
1654+
#[test]
1655+
fn files_that_dont_exist_during_packaging_cannot_be_included() {
1656+
let t = TempDir::new().unwrap();
1657+
let pkg = t.child("pkg");
1658+
simple_project()
1659+
.manifest_package_extra(indoc! {r#"
1660+
include = ["some/file.txt"]
1661+
"#})
1662+
.build(&pkg);
1663+
Scarb::quick_snapbox()
1664+
.arg("package")
1665+
.arg("--no-metadata")
1666+
.current_dir(&pkg)
1667+
.assert()
1668+
.failure()
1669+
.stdout_matches(indoc! {r#"
1670+
[..] Packaging foo v1.0.0 [..]
1671+
error: failed to list source files in: [..]pkg
1672+
1673+
Caused by:
1674+
0: failed to find included file at [..]file.txt
1675+
1: failed to get absolute path of `[..]file.txt`
1676+
2: [..]
1677+
"#});
1678+
}

website/docs/reference/manifest.md

+14
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,20 @@ Setting the `cairo-version` key in `[package]` will affect all targets in the pa
100100
The value in this field will not affect the version of the compiler run by Scarb.
101101
Scarb always uses its built-in version of the Cairo compiler.
102102

103+
### `include`
104+
105+
When packaging a package with `scarb package` command (see
106+
[packaging your package](../registries/publishing.md#packaging-your-package)), all files excluded with rules from
107+
`.gitignore` or `.scarbignore` files are not included in the resulting package tarball.
108+
This field can be used mark files and subdirectories that should be included in the package tarball, even if those files
109+
would be excluded by rules from ignore files.
110+
The paths are relative to the package root and cannot point to files outside the package.
111+
112+
```toml
113+
[package]
114+
include = ["target/some/file.txt"]
115+
```
116+
103117
### `authors`
104118

105119
This optional field lists the people or organizations that are considered the "authors" of the package.

website/docs/registries/publishing.md

+26-6
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ Once uploaded, it will be available for other users to download and use.
77

88
To upload your package, use the scarb publish command.
99
By default, this command will publish your package to the official [scarbs.xyz](https://scarbs.xyz) registry.
10-
The publish command automatically [packages and verifies](#packaging-your-package) your package, so there is no need to run `scarb package` beforehand.
10+
The publish command automatically [packages and verifies](#packaging-your-package) your package, so there is no need to
11+
run `scarb package` beforehand.
1112

12-
To publish your package to a registry that supports package publishing, you need to authenticate using an API token with the `publish` scope.
13+
To publish your package to a registry that supports package publishing, you need to authenticate using an API token with
14+
the `publish` scope.
1315
First, log in to the registry and [in the dashboard](https://scarbs.xyz/dashboard) generate the API token.
1416
Scarb will use the token to authenticate and complete the publishing process.
1517
The token must be provided via the `SCARB_REGISTRY_AUTH_TOKEN` environment variable.
@@ -44,17 +46,35 @@ publish = false
4446

4547
Use the `scarb package` command to create an archive of your package.
4648
You can read about the package compression algorithm and contents in the [Package tarball](./package-tarball) section.
47-
Basically when you run the command, Scarb gathers the source code of your package along with metadata files, such as the manifest file, and places them in an archive in `target/package` directory.
49+
Basically when you run the command, Scarb gathers the source code of your package along with metadata files, such as the
50+
manifest file, and places them in an archive in `target/package` directory.
4851

49-
If you are in a Git repository, Scarb will first check if the repo state is clean and error out in case of any changes present in the Git working directory.
52+
If you are in a Git repository, Scarb will first check if the repo state is clean and error out in case of any changes
53+
present in the Git working directory.
5054
To bypass this check, you can use the `--allow-dirty` flag.
5155

5256
The next step is package verification.
53-
After creating the initial archive, Scarb will attempt to unpack it and compile to check for any corruptions in the packaging process.
57+
After creating the initial archive, Scarb will attempt to unpack it and compile to check for any corruptions in the
58+
packaging process.
5459
If you want to speed up the packaging process, you can disable this step using the `--no-verify` flag.
5560

5661
> [!WARNING]
5762
> This is a dangerous operation as it can lead to uploading a corrupted package to the registry.
5863
> Please use with caution.
5964
60-
After successfully completing the whole process, the `{name}-{version}.tar.zst` archive waits in the `target/package` directory for being uploaded, where both `name` and `version` correspond to the values in `Scarb.toml`.
65+
After successfully completing the whole process, the `{name}-{version}.tar.zst` archive waits in the `target/package`
66+
directory for being uploaded, where both `name` and `version` correspond to the values in `Scarb.toml`.
67+
68+
### Files included in the package
69+
70+
All files in the package directory are included in the resulting tarball, except for the following:
71+
72+
- Files excluded with rules defined in any `.scarbignore`, `.gitignore` or `.ignore` files.
73+
- The `<package root>/target` directory.
74+
- Any subdirectories containing `Scarb.toml` file.
75+
- The `.git` directory.
76+
- Symlinks within the package directory are followed, while symlinks outside are ignored.
77+
- File system boundaries are not crossed.
78+
79+
Files that would be otherwise ignored by the rules listed above, can still be included
80+
with [include](../reference/manifest.md#include) field.

0 commit comments

Comments
 (0)