Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Cargo Çalışma Alanları (Workspaces)

Bölüm 12’de hem bir ikili crate hem de bir kütüphane crate’i içeren bir paket oluşturmuştuk. Projeniz geliştikçe kütüphane crate’inin giderek büyüdüğünü ve paketinizi birden fazla kütüphane crate’ine bölmek istediğinizi fark edebilirsiniz. Cargo, eşzamanlı olarak birlikte geliştirilen birbiriyle ilişkili birden çok paketi yönetmeye yardımcı olan çalışma alanları (workspaces) adlı bir özellik sunar.

Çalışma Alanı Oluşturma

Çalışma alanı, aynı Cargo.lock dosyasını ve aynı çıktı dizinini paylaşan paketler kümesidir. Çalışma alanının yapısına odaklanabilmek için çok basit kod kullanan bir proje oluşturalım. Bir çalışma alanını yapılandırmanın birden fazla yolu vardır; biz burada yaygın kullanılan bir yöntemi göstereceğiz. Bir ikili crate ve iki kütüphane crate’i içeren bir çalışma alanımız olacak. Ana işlevselliği sunacak olan ikili crate, bu iki kütüphaneye bağımlı olacak. Kütüphanelerden biri bir_ekle fonksiyonunu, diğeri ise iki_ekle fonksiyonunu sağlayacak. Bu üç crate aynı çalışma alanının parçası olacak. İşe çalışma alanı için yeni bir dizin oluşturarak başlayalım:

$ mkdir topla
$ cd topla

Ardından topla dizininde, tüm çalışma alanını yapılandıracak Cargo.toml dosyasını oluşturuyoruz. Bu dosyada [package] bölümü yer almayacak; bunun yerine çalışma alanına üye paketler eklememizi sağlayan bir [workspace] bölümüyle başlayacak. Ayrıca çalışma alanımızda Cargo’nun bağımlılık çözümleyici algoritmasının en güncel ve gelişmiş sürümünü kullanmak için resolver değerini özellikle "3" olarak ayarlıyoruz:

Dosya adı: Cargo.toml

[workspace]
resolver = "3"

Not: Normalde tekli bir pakette edition = "2024" ayarı Cargo’nun 3. sürüm bağımlılık çözümleyicisini otomatik olarak etkinleştirir. Ancak başında [package] bölümü bulunmayan bu tür sanal çalışma alanı (virtual workspace) Cargo.toml dosyalarında edition alanı yer almadığından, resolver = "3" satırını açıkça belirtmezseniz Cargo geriye dönük uyumluluk amacıyla eski çözümleyiciye ("1") döner ve derleme sırasında uyarı üretir.

Şimdi topla dizini içinde cargo new çalıştırarak toplayici ikili crate’ini oluşturalım:

$ cargo new toplayici
     Created binary (application) `toplayici` package
      Adding `toplayici` as member of workspace at `file:///projects/topla`

Bir çalışma alanı içinde cargo new çalıştırmak, yeni oluşturulan paketi çalışma alanının Cargo.toml dosyasındaki [workspace] tanımında yer alan members anahtarına da otomatik olarak ekler:

[workspace]
resolver = "3"
members = ["toplayici"]

Bu noktada cargo build çalıştırarak çalışma alanını derleyebiliriz. topla dizininizdeki dosyalar şu şekilde görünmelidir:

├── Cargo.lock
├── Cargo.toml
├── toplayici
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

Çalışma alanının en üst düzeyde derlenmiş çıktıların yerleştirileceği tek bir target dizini vardır; toplayici paketinin kendine ait ayrı bir target dizini yoktur. Hatta cargo build komutunu toplayici dizininin içinden çalıştırsak bile derlenmiş çıktılar topla/toplayici/target yerine yine topla/target dizinine yerleştirilir. Cargo bir çalışma alanındaki target dizinini bu şekilde yapılandırır çünkü çalışma alanındaki crate’lerin birbirine bağımlı olması amaçlanır. Her crate’in kendi target dizini olsaydı, her crate derlenmiş çıktıları kendi target dizinine yerleştirmek için çalışma alanındaki diğer crate’lerin her birini yeniden derlemek zorunda kalırdı. Tek bir target dizinini paylaşarak crate’ler gereksiz yeniden derlemelerin önüne geçer.

Çalışma Alanında İkinci Paketi Oluşturma

Şimdi çalışma alanında bir üye paket daha oluşturalım ve adını bir_ekle koyalım. bir_ekle adında yeni bir kütüphane crate’i üretin:

$ cargo new bir_ekle --lib
     Created library `bir_ekle` package
      Adding `bir_ekle` as member of workspace at `file:///projects/topla`

Üst düzey Cargo.toml dosyası artık members listesinde bir_ekle yolunu da içerecektir:

Dosya adı: Cargo.toml

[workspace]
resolver = "3"
members = ["toplayici", "bir_ekle"]

topla dizininizde artık şu dizin ve dosyalar bulunmalıdır:

├── Cargo.lock
├── Cargo.toml
├── bir_ekle
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
├── toplayici
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

bir_ekle/src/lib.rs dosyasına bir bir_ekle fonksiyonu ekleyelim:

Dosya adı: bir_ekle/src/lib.rs

pub fn bir_ekle(x: i32) -> i32 {
    x + 1
}

Artık ikili crate’imizi barındıran toplayici paketinin, kütüphanemizi içeren bir_ekle paketine bağımlı olmasını sağlayabiliriz. Öncelikle toplayici/Cargo.toml dosyasına bir_ekle için bir yol bağımlılığı (path dependency) eklememiz gerekecek.

Dosya adı: toplayici/Cargo.toml

[dependencies]
bir_ekle = { path = "../bir_ekle" }

Cargo bir çalışma alanındaki crate’lerin birbirine bağımlı olacağını kendiliğinden varsaymaz; bu nedenle bağımlılık ilişkilerini açıkça belirtmemiz gerekir.

Şimdi (bir_ekle crate’inden gelen) bir_ekle fonksiyonunu toplayici crate’inde kullanalım. toplayici/src/main.rs dosyasını açın ve main fonksiyonunu Liste 14-7’deki gibi bir_ekle fonksiyonunu çağıracak şekilde değiştirin.

Dosya adı: toplayici/src/main.rs
fn main() {
    let sayi = 10;
    println!(
        "Merhaba, dünya! {sayi} artı bir {} eder!",
        bir_ekle::bir_ekle(sayi)
    );
}
Liste 14-7: bir_ekle kütüphane crate’ini toplayici crate’i içinden kullanmak

Üst düzey topla dizininde cargo build çalıştırarak çalışma alanını derleyelim!

$ cargo build
   Compiling bir_ekle v0.1.0 (file:///projects/topla/bir_ekle)
   Compiling toplayici v0.1.0 (file:///projects/topla/toplayici)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.22s

İkili crate’i topla dizininden çalıştırmak için cargo run komutuyla birlikte -p argümanını ve paket adını kullanarak çalışma alanındaki hangi paketi çalıştırmak istediğimizi belirtebiliriz:

$ cargo run -p toplayici
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/toplayici`
Merhaba, dünya! 10 artı bir 11 eder!

Bu komut, toplayici/src/main.rs içindeki ve bir_ekle crate’ine bağımlı olan kodu çalıştırır.

Not: Kök Cargo.toml dosyasında [package] bölümü bulunmayan böyle bir sanal çalışma alanında (virtual workspace) kök dizinden çalıştırdığınız cargo build, cargo check ve cargo test gibi komutlar varsayılan olarak tüm üye paketlere uygulanır. cargo run ise hangi ikili dosyayı çalıştıracağını bilmek zorunda olduğundan, çalışma alanında birden fazla ikili crate bulunduğunda -p bayrağı verilmezse hata verir. Üye paketlerden birinin dizinine girip orada komut çalıştırdığınızda yalnızca o paket hedeflenir; o konumdan tüm çalışma alanını derlemek için cargo build --workspace kullanabilirsiniz. Kök dizinde varsayılan olarak hangi paketlerin hedefleneceğini ise [workspace] bölümündeki default-members anahtarıyla belirleyebilirsiniz.

Harici Bir Pakete Bağımlı Olma

Çalışma alanında her crate’in kendi dizininde ayrı bir Cargo.lock bulunması yerine, en üst düzeyde yalnızca tek bir Cargo.lock dosyası bulunduğuna dikkat edin. Bu yapı, tüm crate’lerin bütün bağımlılıkların aynı sürümünü kullanmasını güvence altına alır. rand paketini hem toplayici/Cargo.toml hem de bir_ekle/Cargo.toml dosyalarına eklersek, Cargo her ikisini de tek bir rand sürümüne çözümler ve bunu tek Cargo.lock dosyasına kaydeder. Çalışma alanındaki tüm crate’lerin aynı bağımlılıkları kullanmasını sağlamak, bu crate’lerin birbirleriyle her zaman uyumlu olacağı anlamına gelir. Şimdi rand crate’ini bir_ekle crate’inde kullanabilmek için bir_ekle/Cargo.toml dosyasındaki [dependencies] bölümüne ekleyelim:

Dosya adı: bir_ekle/Cargo.toml

[dependencies]
rand = "0.10.1"

Artık bir_ekle/src/lib.rs dosyasına use rand; ekleyebiliriz; topla dizininde cargo build çalıştırarak tüm çalışma alanını derlemek rand crate’ini indirip derleyecektir. Kapsama dahil ettiğimiz rand crate’ine kodda başvurmadığımız için bir uyarı alırız:

$ cargo build
    Updating crates.io index
  Downloaded rand v0.10.1
   --snip--
   Compiling rand v0.10.1
   Compiling bir_ekle v0.1.0 (file:///projects/topla/bir_ekle)
warning: unused import: `rand`
 --> bir_ekle/src/lib.rs:1:5
  |
1 | use rand;
  |     ^^^^
  |
  = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default

warning: `bir_ekle` (lib) generated 1 warning (run `cargo fix --lib -p bir_ekle` to apply 1 suggestion)
   Compiling toplayici v0.1.0 (file:///projects/topla/toplayici)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.95s

Üst düzey Cargo.lock dosyası artık bir_ekle crate’inin rand bağımlılığına ilişkin bilgileri içerir. Bununla birlikte, rand çalışma alanının bir yerinde kullanılıyor olsa bile, diğer crate’lerin Cargo.toml dosyalarına da rand eklemediğimiz sürece onu çalışma alanındaki diğer crate’lerde kullanamayız. Örneğin toplayici paketine ait toplayici/src/main.rs dosyasına use rand; eklersek bir hata alırız:

$ cargo build
  --snip--
   Compiling toplayici v0.1.0 (file:///projects/topla/toplayici)
error[E0432]: unresolved import `rand`
 --> toplayici/src/main.rs:2:5
  |
2 | use rand;
  |     ^^^^ no external crate `rand`

Bunu düzeltmek için toplayici paketinin Cargo.toml dosyasını düzenleyip rand paketinin onun için de bir bağımlılık olduğunu belirtin. toplayici paketini derlemek, Cargo.lock dosyasında toplayici paketinin bağımlılık listesine rand paketini ekleyecektir; ancak rand paketinin ek bir kopyası indirilmeyecektir. Paketler birbiriyle uyumlu rand sürümleri belirttiği sürece Cargo, çalışma alanında rand kullanan tüm crate’lerin aynı sürümü paylaşmasını sağlar; bu hem disk alanından tasarruf ettirir hem de çalışma alanındaki crate’lerin birbiriyle uyumlu kalmasını güvence altına alır.

Çalışma alanındaki crate’ler aynı bağımlılığın birbiriyle uyumsuz sürümlerini belirtirse Cargo bunların her birini ayrı ayrı çözümler, ancak yine de mümkün olan en az sayıda sürümü çözümlemeye çalışır.

Not: Çok sayıda paketten oluşan projelerde aynı bağımlılık sürümünü veya ortak paket meta verilerini (version, edition, license vb.) her üye paketin Cargo.toml dosyasında tekrar tekrar yazmak ve güncel tutmak sürüm uyuşmazlıklarına yol açabilir. Cargo, bunu tek merkezden yönetmek için çalışma alanı mirası (workspace inheritance) özelliğini sunar. Üst düzey Cargo.toml dosyasında ortak paket alanlarını [workspace.package] altında, ortak bağımlılıkları ise [workspace.dependencies] altında bir kez tanımlayabilirsiniz:

[workspace]
resolver = "3"
members = ["toplayici", "bir_ekle"]

[workspace.package]
version = "0.1.0"
edition = "2024"

[workspace.dependencies]
rand = "0.10.1"
bir_ekle = { path = "bir_ekle" }

Ardından üye paketlerin kendi Cargo.toml dosyalarında (örneğin toplayici/Cargo.toml) sürüm numarası veya dosya yolu tekrarlamak yerine workspace = true anahtarını kullanması yeterlidir:

[package]
name = "toplayici"
version.workspace = true
edition.workspace = true

[dependencies]
rand.workspace = true
bir_ekle.workspace = true

Böylece bir bağımlılığı güncellemeniz gerektiğinde yalnızca kök Cargo.toml dosyasındaki tek bir satırı değiştirirsiniz ve çalışma alanındaki tüm crate’lerin aynı sürüm tanımını paylaşması garanti altına alınır.

Çalışma Alanına Test Ekleme

Bir iyileştirme daha yapalım ve bir_ekle crate’i içinde bir_ekle::bir_ekle fonksiyonunun bir testini ekleyelim:

Dosya adı: bir_ekle/src/lib.rs

pub fn bir_ekle(x: i32) -> i32 {
    x + 1
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn calisir() {
        assert_eq!(3, bir_ekle(2));
    }
}

Şimdi üst düzey topla dizininde cargo test çalıştırın. Bu şekilde yapılandırılmış bir çalışma alanında cargo test çalıştırmak, çalışma alanındaki tüm crate’lerin testlerini çalıştıracaktır:

$ cargo test
   Compiling bir_ekle v0.1.0 (file:///projects/topla/bir_ekle)
   Compiling toplayici v0.1.0 (file:///projects/topla/toplayici)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.20s
     Running unittests src/lib.rs (target/debug/deps/bir_ekle-93c49ee75dc46543)

running 1 test
test tests::calisir ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/toplayici-3a47283c568d2b6a)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests bir_ekle

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Çıktının ilk bölümü, bir_ekle crate’indeki calisir testinin geçtiğini gösterir. Bir sonraki bölüm toplayici crate’inde sıfır test bulunduğunu, son bölüm ise bir_ekle crate’inde sıfır belgelendirme testi bulunduğunu gösterir.

Üst düzey dizinden -p bayrağını kullanıp test etmek istediğimiz crate’in adını belirterek çalışma alanındaki belirli bir crate’in testlerini de çalıştırabiliriz:

$ cargo test -p bir_ekle
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running unittests src/lib.rs (target/debug/deps/bir_ekle-93c49ee75dc46543)

running 1 test
test tests::calisir ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests bir_ekle

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Bu çıktı, cargo test komutunun yalnızca bir_ekle crate’i için testleri çalıştırdığını ve toplayici crate’inin testlerini çalıştırmadığını gösterir.

Çalışma alanındaki crate’leri crates.io üzerinde yayınlarsanız, çalışma alanındaki her crate’in ayrı ayrı yayınlanması gerekecektir. Tıpkı cargo test komutunda olduğu gibi, -p bayrağını kullanıp yayınlamak istediğimiz crate’in adını belirterek çalışma alanımızdaki belirli bir crate’i yayınlayabiliriz.

Ek pratik yapmak için bu çalışma alanına bir_ekle crate’ine benzer bir şekilde iki_ekle crate’ini ekleyin!

Projeniz büyüdükçe bir çalışma alanı kullanmayı değerlendirin: Tek parça büyük bir kod yığını yerine daha küçük ve anlaşılması daha kolay bileşenlerle çalışmanıza olanak tanır. Üstelik crate’ler sıklıkla aynı anda değiştiriliyorsa, onları bir çalışma alanında tutmak crate’ler arasındaki eşgüdümü de kolaylaştırabilir.