Skip to content

Commit de80d39

Browse files
authored
Merge branch 'main' into add-tests
2 parents 0561dcc + 4724a73 commit de80d39

File tree

10 files changed

+3713
-25
lines changed

10 files changed

+3713
-25
lines changed

.gitattributes

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# GitHub syntax highlighting
2+
pixi.lock linguist-language=YAML

.github/CODEOWNERS

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* @pavelzw @delsner @0xbe7a

.github/workflows/ci.yml

+50-8
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,58 @@ concurrency:
77
cancel-in-progress: true
88

99
jobs:
10-
test:
11-
name: Run tests
10+
pre-commit-checks:
11+
name: Pre-commit Checks
12+
timeout-minutes: 30
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout branch
16+
uses: actions/checkout@v4
17+
- name: Set up pixi
18+
uses: prefix-dev/setup-pixi@ba3bb36eb2066252b2363392b7739741bb777659
19+
with:
20+
environments: default lint
21+
- name: Set up cargo cache # use https://github.com/Swatinem/rust-cache once whitelisted
22+
uses: actions/cache@v3
23+
continue-on-error: false
24+
with:
25+
path: |
26+
~/.cargo/bin/
27+
~/.cargo/registry/index/
28+
~/.cargo/registry/cache/
29+
~/.cargo/git/db/
30+
target/
31+
key: ${{ runner.os }}-cargo-lint-${{ hashFiles('**/Cargo.lock') }}
32+
restore-keys: ${{ runner.os }}-cargo-
33+
- name: pre-commit
34+
run: pixi run pre-commit-run --color=always --show-diff-on-failure
35+
36+
unit-tests:
37+
name: test
38+
timeout-minutes: 30
1239
runs-on: ${{ matrix.os }}
1340
strategy:
1441
matrix:
1542
os: [ubuntu-latest, windows-latest, macos-latest]
16-
toolchain: [stable]
1743
steps:
18-
- uses: actions/checkout@v4
19-
- run: rustup update ${{ matrix.toolchain }}
20-
- run: rustup default ${{ matrix.toolchain }}
21-
- run: cargo build --verbose
22-
- run: cargo test --verbose
44+
- name: Checkout branch
45+
uses: actions/checkout@v4
46+
with:
47+
ref: ${{ github.head_ref }}
48+
fetch-depth: 0
49+
- name: Set up pixi
50+
uses: prefix-dev/setup-pixi@ba3bb36eb2066252b2363392b7739741bb777659
51+
- name: Set up cargo cache # use https://github.com/Swatinem/rust-cache once whitelisted
52+
uses: actions/cache@v3
53+
continue-on-error: false
54+
with:
55+
path: |
56+
~/.cargo/bin/
57+
~/.cargo/registry/index/
58+
~/.cargo/registry/cache/
59+
~/.cargo/git/db/
60+
target/
61+
key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }}
62+
restore-keys: ${{ runner.os }}-cargo-
63+
- name: Run test
64+
run: pixi run test

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,6 @@ env/
120120
unpack/
121121
cache/
122122
activate.*
123+
# pixi environments
124+
.pixi
125+
*.egg-info

.pre-commit-config.yaml

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
repos:
2+
- repo: local
3+
hooks:
4+
# prettier
5+
- id: prettier
6+
name: prettier
7+
entry: pixi run -e lint prettier --write --list-different --ignore-unknown
8+
language: system
9+
types: [text]
10+
files: \.(md|yml|yaml)$
11+
# pre-commit-hooks
12+
- id: trailing-whitespace-fixer
13+
name: trailing-whitespace-fixer
14+
entry: pixi run -e lint trailing-whitespace-fixer
15+
language: system
16+
types: [text]
17+
- id: end-of-file-fixer
18+
name: end-of-file-fixer
19+
entry: pixi run -e lint end-of-file-fixer
20+
language: system
21+
types: [text]
22+
- id: check-merge-conflict
23+
name: check-merge-conflict
24+
entry: pixi run -e lint check-merge-conflict --assume-in-merge
25+
language: system
26+
types: [text]
27+
# typos
28+
- id: typos
29+
name: typos
30+
entry: pixi run -e lint typos --force-exclude
31+
language: system
32+
types: [text]
33+
require_serial: true
34+
# cargo fmt and clippy
35+
- id: cargo-fmt-conda
36+
name: cargo-fmt-conda
37+
description: "Run `cargo fmt` for formatting rust sources."
38+
entry: pixi run -e default cargo fmt --
39+
language: system
40+
require_serial: false
41+
types: [rust]
42+
- id: clippy-conda
43+
name: clippy-conda
44+
description: "Run `clippy` to lint rust sources."
45+
entry: pixi run -e default cargo clippy --all-targets --all-features --workspace -- -D warnings
46+
pass_filenames: false
47+
language: system
48+
require_serial: false
49+
types: [rust]
50+
# taplo
51+
- id: taplo
52+
name: taplo
53+
entry: pixi run -e lint taplo format
54+
language: system
55+
types: [toml]

.vscode/launch.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,4 @@
9090
"cwd": "${workspaceFolder}"
9191
}
9292
]
93-
}
93+
}

pixi.lock

+3,513
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pixi.toml

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[project]
2+
name = "pixi-pack"
3+
version = "0.1.0"
4+
description = "A command line tool to pack and unpack conda environments for easy sharing."
5+
channels = ["conda-forge"]
6+
platforms = ["osx-arm64", "osx-64", "linux-64", "linux-aarch64", "win-64"]
7+
8+
[tasks]
9+
build = "cargo build --release"
10+
test = "cargo test"
11+
12+
[dependencies]
13+
rust = "1.77.2"
14+
15+
[feature.lint.dependencies]
16+
pre-commit = "*"
17+
prettier = "*"
18+
taplo = "*"
19+
pre-commit-hooks = "*"
20+
typos = "*"
21+
[feature.lint.tasks]
22+
pre-commit-install = "pre-commit install"
23+
pre-commit-run = "pre-commit run -a"
24+
25+
[environments]
26+
default = ["lint"]
27+
lint = ["lint"]

src/pack.rs

+59-14
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ use futures::{
1515
StreamExt, TryStreamExt,
1616
};
1717
use indicatif::ProgressStyle;
18-
use rattler_conda_types::Platform;
19-
use rattler_lock::{LockFile, Package};
18+
use rattler_conda_types::{PackageRecord, Platform};
19+
use rattler_lock::{CondaPackage, LockFile, Package};
2020
use rattler_networking::{AuthenticationMiddleware, AuthenticationStorage};
2121
use reqwest_middleware::ClientWithMiddleware;
2222
use tokio_tar::Builder;
@@ -65,9 +65,20 @@ pub async fn pack(options: PackOptions) -> Result<()> {
6565
options.platform.as_str()
6666
))?;
6767

68+
let mut conda_packages = Vec::new();
69+
70+
for package in packages {
71+
match package {
72+
Package::Conda(p) => conda_packages.push(p),
73+
Package::Pypi(_) => {
74+
anyhow::bail!("pypi packages are not supported in pixi-pack");
75+
}
76+
}
77+
}
78+
6879
// Download packages to temporary directory.
69-
tracing::info!("Downloading {} packages", packages.len());
70-
let bar = indicatif::ProgressBar::new(packages.len() as u64);
80+
tracing::info!("Downloading {} packages", conda_packages.len());
81+
let bar = indicatif::ProgressBar::new(conda_packages.len() as u64);
7182
bar.set_style(
7283
ProgressStyle::with_template(
7384
"[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}",
@@ -81,7 +92,7 @@ pub async fn pack(options: PackOptions) -> Result<()> {
8192

8293
let channel_dir = output_folder.path().join(CHANNEL_DIRECTORY_NAME);
8394

84-
stream::iter(packages)
95+
stream::iter(conda_packages.iter())
8596
.map(Ok)
8697
.try_for_each_concurrent(50, |package| async {
8798
download_package(&client, package, &channel_dir).await?;
@@ -105,6 +116,13 @@ pub async fn pack(options: PackOptions) -> Result<()> {
105116
let metadata = serde_json::to_string_pretty(&options.metadata)?;
106117
metadata_file.write_all(metadata.as_bytes()).await?;
107118

119+
// Create environment file.
120+
create_environment_file(
121+
output_folder.path(),
122+
conda_packages.iter().map(|p| p.package_record()),
123+
)
124+
.await?;
125+
108126
// Pack = archive + compress the contents.
109127
archive_directory(output_folder.path(), &options.output_file, options.level)
110128
.await
@@ -143,25 +161,21 @@ fn reqwest_client_from_auth_storage(auth_file: Option<PathBuf>) -> Result<Client
143161
/// Download a conda package to a given output directory.
144162
async fn download_package(
145163
client: &ClientWithMiddleware,
146-
package: Package,
164+
package: &CondaPackage,
147165
output_dir: &Path,
148166
) -> Result<()> {
149-
let conda_package = package
150-
.as_conda()
151-
.ok_or(anyhow!("package is not a conda package"))?; // TODO: we might want to skip here
152-
153-
let output_dir = output_dir.join(&conda_package.package_record().subdir);
167+
let output_dir = output_dir.join(&package.package_record().subdir);
154168
create_dir_all(&output_dir)
155169
.await
156170
.map_err(|e| anyhow!("could not create download directory: {}", e))?;
157171

158-
let file_name = conda_package
172+
let file_name = package
159173
.file_name()
160174
.ok_or(anyhow!("could not get file name"))?;
161175
let mut dest = File::create(output_dir.join(file_name)).await?;
162176

163-
tracing::debug!("Fetching package {}", conda_package.url());
164-
let mut response = client.get(conda_package.url().to_string()).send().await?;
177+
tracing::debug!("Fetching package {}", package.url());
178+
let mut response = client.get(package.url().to_string()).send().await?;
165179

166180
while let Some(chunk) = response.chunk().await? {
167181
dest.write_all(&chunk).await?;
@@ -208,3 +222,34 @@ async fn archive_directory(
208222

209223
Ok(())
210224
}
225+
226+
async fn create_environment_file(
227+
destination: &Path,
228+
packages: impl IntoIterator<Item = &PackageRecord>,
229+
) -> Result<()> {
230+
let environment_path = destination.join("environment.yml");
231+
232+
let mut environment = String::new();
233+
234+
environment.push_str("channels:\n");
235+
environment.push_str(&format!(" - ./{CHANNEL_DIRECTORY_NAME}\n",));
236+
environment.push_str(" - nodefaults\n");
237+
environment.push_str("dependencies:\n");
238+
239+
for package in packages {
240+
let match_spec_str = format!(
241+
"{}={}={}",
242+
package.name.as_normalized(),
243+
package.version,
244+
package.build,
245+
);
246+
247+
environment.push_str(&format!(" - {}\n", match_spec_str));
248+
}
249+
250+
fs::write(environment_path, environment)
251+
.await
252+
.map_err(|e| anyhow!("Could not write environment file: {}", e))?;
253+
254+
Ok(())
255+
}

src/unpack.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,9 @@ async fn unarchive(archive_path: &Path, target_dir: &Path) -> Result<()> {
138138

139139
let reader = tokio::io::BufReader::new(file);
140140

141-
let decocder = ZstdDecoder::new(reader);
141+
let decoder = ZstdDecoder::new(reader);
142142

143-
let mut archive = Archive::new(decocder);
143+
let mut archive = Archive::new(decoder);
144144

145145
archive
146146
.unpack(target_dir)

0 commit comments

Comments
 (0)