Nix Dili ve Özellikleri

Murat Çabuk
Kodcular
Published in
19 min readMar 27, 2024

--

Photo by Markus Spiske on Unsplash

Bir önceki makalemizde NisOs dünyasını biraz tanımaya çalıştık. bu yazımızda Nix dilinin syntax’ini ve özelliklerini öğrenmeye çalışacağız.

Makalenin diğer yazıları için alttaki linkleri takip edebilirsiniz.

  1. NixOS: İşletim Sistemlerine Fonksiyonel Yaklaşım
  2. Nix Dili ve Özellikleri
  3. Nix Dili ile ilgili Alıştırmalar
  4. Nix Dilinde Builtins Fonksiyonlar
  5. Nix Paket Yöneticisi
  6. Nix Paket Yöneticisi Shell, Profile Kavram ve Komutları
  7. Nix Flake Nedir?
  8. Birden Çok Paketi Aynı Repo Üzeriden Yayınlamak
  9. Nix Paket Yöneticisinde Override ve Overlay Kavramları
  10. Nix Paket Yöneticisi ile Developmen Ortamları ve Profile Oluşturmak
  11. Nix ile NixOs Konfigürasyonu
  12. NixOs Module ve Option Kullanımı
  13. NixOs Kurulumu ve Konfigürasyonu
  14. NixOs’u Cloud ve Uzak Ortamlara Deploy Etmek
  • Nix Dili: Nix dili C++ ile yazılmış fonksiyonel bir dildir. Yapı olarak Haskell diline benzer. Aynı amaçla geliştirilmiş bir de GNU Guile dili geliştirilmiştir bu dilde Lisp dilinden esinlenmiştir. Ancak Guile dili Nix’e göre daha karmaşıktır, öğrenmesi zordur.
  • Nix Paket Yöneticisi: Nix dili kullanılarka geliştirilmiş cross platform bir paket yöneticisidir. Aynı mantıkta geliştirilmiş bir de GNU Guix paket yöneticisi vardır (GNU Guile diliyle geliştirilmiş). Nix paket yöneticisi 2003 yılında geliştirilşmeye başlanmışken Guix 2013 yılında geliştirlmeye başlanmıştr, Nix’e göre topluluğu yok denecek kadar az.
  • NixOs İşletim Sistemi: Nix paket yönetcisini kullanan ve Nix dili ile konfigürasyou yapılan declarative bir işletim sistemidir.
  • Nixpkgs: Nix paket yöneticisinin paket deposudur (package repository).

Nix Dilinin Tanımı ve Amaçları

Nix, özellikle NixOS işletim sistemi ve Nix paket yöneticisi üzerinde odaklanan bir fonksiyonel dil ve sistem aracıdır. Nix dilinin temel amacı, paket yönetimi ve sistem konfigürasyonunu fonksiyonel programlama prensiplerini kullanarak ele almak ve bu işlemleri güvenli ve tekrarlanabilir bir şekilde gerçekleştirmektir. İşte Nix dilinin temel amaçlarından bazıları:

  1. Tekrarlanabilirlik ve İzolasyon: Nix, her paketin ve sistem konfigürasyonunun bağımlılıklarını ve versiyonlarını kesin bir şekilde belirtme yeteneği ile bilinir. Bu, herhangi bir ortamda aynı paket ve konfigürasyonun kurulmasını ve çalıştırılmasını sağlar.
  2. Fonksiyonel Paket Yönetimi: Nix, paketleri fonksiyonel programlama prensiplerine dayalı olarak yönetir. Her paket, dışsal bağımlılıkları ile birlikte bir fonksiyon olarak tanımlanır. Bu sayede paketler birbirinden izole edilir ve sistemdeki başka paketlerin durumunu etkilemez.
  3. İşletim Sistemi Konfigürasyonu: NixOS, Nix dilini kullanarak yapılandırılmış bir işletim sistemidir. Sistem konfigürasyonu, Nix dilinde ifade edilen bir fonksiyon olarak temsil edilir. Bu, sistem yapılandırmasının da tekrarlanabilir ve sürdürülebilir olmasını sağlar.
  4. Değişmez Paket Yönetimi: Nix, her paketin ve sistem konfigürasyonunun belirli bir versiyonunu ve durumunu saklar. Bu durum, değişmez paket yönetimini sağlar ve geriye dönük uyumluluğu artırır.
  5. Çoklu Ortam Desteği: Nix, aynı paketlerin farklı ortamlara (geliştirme, test, üretim) kurulmasını destekler. Her ortam, aynı paketin farklı versiyonlarına veya konfigürasyonlarına sahip olabilir.

Nix, genellikle yazılım geliştirme ve sistem yönetimi süreçlerini daha güvenli, tekrarlanabilir ve yönetilebilir hale getirmek için kullanılır.

Nix Language

Nix, bir fonksiyonel dil olan bir paket yönetim sistemi ve dilidir.

  1. Fonksiyonel Dil: Nix, fonksiyonel programlama paradigmasını benimser. Bu, değişmezlik ve fonksiyonların birinci sınıf vatandaşlar olarak kabul edilmesi anlamına gelir. Nix ifadeleri de fonksiyonlar olarak düşünülür ve değişmezlik ilkesine uyar.
  2. Değişmezlik: Nix dilinde, bir kere tanımlandıktan sonra değerler değiştirilemez. Bu, bir paket ya da ifade bir kere oluşturulduğunda, onunla ilişkilendirilen değerlerin değişmeyeceği anlamına gelir. Bu özellik, sistem durumunun tahmin edilebilir ve tekrar üretilebilir olmasını sağlar.
  3. Tek Bir Dil: Nix hem bir paket yönetimi sistemi hem de bir dil olduğu için, aynı dilin paket tanımlamalarında ve konfigürasyon dosyalarında kullanılmasına izin verir. Bu, dilin genel bir tutarlılık ve bütünlük sağlamasına yardımcı olur.
  4. İfade Dilinde Tanımlama: Nix ifadeleri, paketlerin, ortamların ve diğer sistem bileşenlerinin tanımlandığı bir dilde yazılır. Bu ifadeler, bir dilin içinde bir başka dil gibi ifade edilebilir ve yorumlanabilir. Bu, Nix’in esnekliğini artırır.
  5. Lazy Evaluation (Gecikmeli Değerlendirme): Nix, gecikmeli değerlendirme kullanarak sadece ihtiyaç duyulduğunda bir ifadenin değerlendirilmesini sağlar. Bu, kaynakları verimli bir şekilde kullanmayı ve gereksiz işlemleri önlemeyi amaçlar.
  6. Bağımsız ve Taşınabilir Paketler: Nix paketleri, sistem bağımlılıklarını izole edilmiş bir şekilde içerir. Bu, bir paketin bir sistemden diğerine taşınabilmesini sağlar. Her paket, kendi bağımlılıklarını ve sistem kütüphanelerini içerir.
  7. Çoğaltılabilir Paket Kurulumları: Nix, paketlerin belirli sürümlerini ve bağımlılıklarını çoğaltılabilir şekilde tanımlamak için kullanılır. Bu, projenin farklı sistemlerde tutarlı bir şekilde çalıştırılmasına olanak tanır.
  8. NixOps ve NixOS: Nix, ayrıca NixOS adlı bir işletim sistemi ve NixOps adlı bir sistem yönetimi aracıyla birleştirildiğinde güçlü bir sistem yönetim çözümü sunar. NixOps, Nix dilini kullanarak ölçeklenebilir sistem yapılandırmalarını yönetmeye olanak tanır.

Bu özellikler, Nix’in güçlü bir paket yönetim sistemi ve sistem konfigürasyon dilini tanımlayan temel karakteristikleridir. Bu özellikler, sistemlerin tekrar üretilebilir ve güvenilir bir şekilde yapılandırılmasına, paketlenmesine ve yönetilmesine olanak tanır.

Nix Kurulum

Mümkünse NixOs’a geçene kadar NixOs işletim sistemi dışında başka bir Linux dağıtımı kullanın. Bu nedenle Doğrudan NixOs kurulumu değil Nix paket yöneticisini kuracağız. Resmi sayfasından kurulumları yapabilirsiniz. Zeten her işletim sistemi için bir script var onu çalıştırmanız yeterli.

Sadece kurulum bittikten sonra flake’i enable etmemiz gerekiyor. Bunun için ~/.config/nix/nix.conf veya /etc/nix/nix.conf dosyasına aşağıdaki satırı ekleyin.

experimental-features = nix-command flakes

Veri Tipleri

Nix dili, NixOS ve Nix paket yöneticisi tarafından kullanılan özel bir dildir. Nix dilinde, bir dizi farklı veri tipi bulunur. İşte temel veri tipleri:

1. Atoms (Atomlar):

  • true: Mantıksal doğru.
  • false: Mantıksal yanlış.
  • null: Boş değer.
true
false
null

2. Sayılar:

  • Tam sayılar (integer): 1, 42, -10 gibi.
  • Ondalıklı sayılar (float): 3.14, -0.5 gibi.
42
3.14

3. Dizgiler (Strings):

  • Tek tırnak (') veya çift tırnak (") içinde ifade edilir.
"Merhaba, dünya!"
'This is a string.'

4. Listeler:

  • Elemanları boşlukla ayrılmış, köşeli parantez içinde ifade edilir.
[1 2 3 4]
["elma" "armut" "kiraz"]

5. Setler:

  • Anahtar-değer çiftlerini içerir. Anahtarlar benzersiz olmalıdır.
{ anahtar = "değer"; başkaAnahtar = 42; }

6. Fonksiyonlar:

  • Fonksiyonlar, arg1: body şeklinde ifade edilir.
arg: arg * 2

7. Built-in Fonksiyonlar:

  • builtins modülü içinde bir dizi yerleşik fonksiyon bulunur. Örneğin, length, map, filter, throw, vb.
builtins.length [1 2 3]
builtins.map (x: x * 2) [1 2 3]

8. Path (Yol):

  • Bir dosya veya dizin yolu ifade eder.
/home/kullanici/dosya.txt
./proje

Bu temel veri tipleri, Nix dilindeki temel yapı taşlarını oluşturur. Nix dilinde daha karmaşık veri yapıları ve modüller de bulunabilir, ancak bu, temel veri tipleridir.

Operators

Tablo Kaynak

Değişken Tanımlama

Evet, Nix dilinde bir değişkeni tanımlamak ve kullanmak için let anahtar kelimesi kullanılır. let ifadesi, bir blok içinde geçici değişkenler tanımlamak için kullanılır ve bu değişkenler, sadece in anahtar kelimesinden sonraki kapsamda geçerlidir.

İşte bir örnek:

let
myVariable = "Merhaba, Dünya!";
in
myVariable

Bu örnekte, myVariable adlı bir değişken tanımlanmış ve bu değişken "Merhaba, Dünya!" değeri ile initialize edilmiştir. Ardından in anahtar kelimesi ile bu değişkenin kullanıldığı bir kapsam (scope) tanımlanmıştır. myVariable, bu kapsamın içinde geçerli bir değişken haline gelir.

Bir başka örnek ile bir dizi tanımlama ve bu dizi üzerinde işlem yapma:

let
myNumbers = [1 2 3 4 5];
doubledNumbers = builtins.map (x: x * 2) myNumbers;
in
doubledNumbers

Bu örnekte, myNumbers adlı bir dizi tanımlanmış ve ardından bu dizi üzerinde bir map işlemi gerçekleştirilerek her elemanı iki ile çarpılmış yeni bir dizi olan doubledNumbers oluşturulmuştur.

let ifadesi, Nix dilinde birçok durumda kullanılabilir ve özellikle fonksiyonlar veya derivasyonlar (derivation) içinde geçici değişkenler tanımlamak için sıklıkla kullanılır.

fonksiyon Tanımlama

Tabii ki, Nix dilinde fonksiyonlar, genellikle pkgs (paketler), lib (standart kütüphane) ve diğer Nix modüllerinde yaygın olarak kullanılır. Bir fonksiyonu kullanımını anlamak için, bir örnek üzerinden gitmek faydalı olabilir.

Öncelikle, basit bir fonksiyon tanımlayalım. Aşağıdaki örnekte, bir sayının karesini bulan bir fonksiyon tanımlanmıştır:

# Fonksiyon tanımlama
square = x: x * x;
# Fonksiyonu kullanma
result = square 5; # result şimdi 25 değerine sahip

Bu örnekte square adında bir fonksiyon tanımlanmıştır. Fonksiyon, bir argüman olan x'i alır ve x * x işlemi ile karesini hesaplar. Daha sonra square fonksiyonu, 5 sayısı ile çağrılarak result değişkenine atanır ve result değeri 25 olur.

Nix dilinde fonksiyonlar genellikle lib modülündeki fonksiyonlar gibi standart kütüphane tarafından sağlanır. Aşağıda, lib modülündeki concatStrings fonksiyonunu kullanarak bir örnek bulunmaktadır:

{ lib }:
# Fonksiyonu kullanma
result = lib.concatStringsSep ", " ["merhaba", "dünya"];

Bu örnekte, lib.concatStringsSep fonksiyonu, bir liste içindeki dizeleri belirtilen ayırıcı (sep) ile birleştirir. Sonuç olarak, result değişkeni "merhaba, dünya" değerine sahip olacaktır.

Fonksiyonlar, Nix dilinde ifadeleri daha modüler ve yeniden kullanılabilir hale getirmek için kullanılır. Bu fonksiyonlar genellikle özellikle paket tanımlamalarında, sistem konfigürasyonunda ve diğer Nix ifadelerinde yaygın olarak kullanılır.

Biraz daha karmaşık bir örnek. Bu örnekte, iki sayının toplamını, farkını, çarpımını ve bölümünü hesaplayan bir fonksiyon tanımlayacağız:

# Fonksiyon tanımlama
calculate = { a, b }:
let
sum = a + b;
difference = a - b;
product = a * b;
division = if b != 0 then a / b else "Bölme hatası: b sıfıra bölünemez!";
in
{ sum = sum; difference = difference; product = product; division = division; };
# Fonksiyonu kullanma
result = calculate { a = 8; b = 4; };

Bu örnekte, calculate adlı bir fonksiyon tanımlanmıştır. Bu fonksiyon, { a, b } şeklinde iki parametre alır. Ardından, let bloğu içinde toplam, fark, çarpım ve bölme işlemleri gerçekleştirilir. Bölme işleminde if ifadesi kullanılarak sıfıra bölme hatası kontrol edilir. Son olarak, bir tuple içinde hesaplanan değerler döndürülür.

Bu fonksiyonu kullanmak için, calculate fonksiyonuna bir parametre objesi gönderilir ve dönen değer result değişkenine atanır. result değişkeni, hesaplamaların sonuçlarını içeren bir tuple'ı temsil eder.

{
sum = 12;
difference = 4;
product = 32;
division = 2;
}

Bu örnekteki fonksiyon, Nix dilinde daha karmaşık ifadeler ve kontroller içeren bir fonksiyonun nasıl tanımlanabileceğini göstermektedir. Nix dilinde fonksiyonlar, özellikle sistem konfigürasyonu veya paket tanımlamaları gibi karmaşık senaryolarda kullanılarak kodu modüler ve yeniden kullanılabilir hale getirmenin bir yolu olarak değerlendirilir.

Örnekte, calculate ifadesi bir fonksiyonu tanımlar. Nix dilinde fonksiyonlar, genellikle parametre listesi ve fonksiyonun gövdesini içeren bir ifade ile tanımlanır.

calculate = { a, b }:
let
sum = a + b;
difference = a - b;
product = a * b;
division = if b != 0 then a / b else "Bölme hatası: b sıfıra bölünemez!";
in
{ sum = sum; difference = difference; product = product; division = division; };

Yukarıdaki örnekte, calculate fonksiyonu { a, b } adlı bir parametre listesi alır. Daha sonra, let bloğu içinde sum, difference, product, ve division değişkenleri tanımlanır. in anahtar kelimesiyle ayrılan bölümde ise fonksiyonun geri dönüş değeri belirlenir. Bu durumda, hesaplanan değerleri içeren bir tuple döndürülür.

Bu tarz bir fonksiyon, calculate { a = 8; b = 4; } gibi bir şekilde çağrılabilir. Bu çağrı, a ve b parametrelerini içeren bir obje ile fonksiyonu çağırır ve sonuç olarak hesaplamaların yapıldığı tuple'ı döndürür.

let ve in ifadeleri, Nix dilinde bir bloğu (scope) tanımlamak için kullanılır. Bu blok içinde tanımlanan değişkenler sadece bu blok içinde geçerlidir ve dışarıdan erişilemez. Dolayısıyla, let ve in ifadeleri, calculate fonksiyonunun gövdesini oluşturur.

İşte detaylar:

calculate = { a, b }:
let
sum = a + b;
difference = a - b;
product = a * b;
division = if b != 0 then a / b else "Bölme hatası: b sıfıra bölünemez!";
in
{ sum = sum; difference = difference; product = product; division = division; };
  • let ifadesi ile başlayan kısım, değişkenlerin tanımlandığı bloğu oluşturur.
  • in ifadesi ise bu bloğun sona erdiğini ve bu blok içinde tanımlanan değişkenlerin artık kullanılabileceğini belirtir.

Bu yapı sayesinde, fonksiyonun içindeki hesaplamalara özgü değişkenler (sum, difference, product, division) sadece let ve in bloğu içinde geçerli olur ve fonksiyonun dışında görünmez.

Daha sade bir örnek ile fonksiyon tanımı

f = { a, b }: { result = a+b; };
f {a=1;b=2;}
# sonuç { result = 3; }

Fonksiyonlarda geri değer döndürmek

Fonksiyonlardan değer döndürmek mümkündür. Nix dilinde fonksiyonlar, son değeri otomatik olarak döndürür. Bu, fonksiyonun sonunda yer alan ifade veya bloğun değeridir. Önceki örneklerde de görüldüğü gibi, fonksiyon son değeri belirli bir değer veya ifade olabilir.

Örneğin, basit bir fonksiyonun tanımlanması ve değer döndürmesi:

# Fonksiyon tanımlama
double = x: x * 2;
# Fonksiyonu çağırma ve değer döndürme
result = double 5; # result şimdi 10 değerine sahip

Bu örnekte, double fonksiyonu bir sayının iki katını hesaplar ve bu değeri otomatik olarak döndürür.

Fonksiyonlar, let ve in blokları içinde daha karmaşık yapılarda da kullanılabilir. Örneğin, iki sayı arasındaki asal sayıları bulan bir fonksiyon:

findPrimes = { start, end }:
let
isPrime = n: builtins.all (i: n % i != 0) (builtins.range 2 n);
primes = builtins.filter isPrime (builtins.range start end);
in
primes;

# result şimdi [11 13 17 19 23 29] değerine sahip
result = findPrimes { start = 10; end = 30; };

Bu örnekte, findPrimes fonksiyonu iki sayı arasındaki asal sayıları bulur ve bu asal sayıları içeren bir liste olarak döndürür. result değişkeni, fonksiyonun döndürdüğü değeri içerir.

Fonksiyonlarda çoklu arguman kullanımı

let
multiply = a: b: a * b;
doubleIt = multiply 2; # at this point we have passed in the value for 'a' and
# receive back another function that still expects 'b'
in
doubleIt 15
# yields 30

doubleIt fonksiyonu, multiply fonksiyonuna 2 değerini gönderir ve multiply fonksiyonundan bir fonksiyon döndürür. in ifadesi içinde çağrılan doubleIt 15 satırında ise multiply fonksiyonunun döndürdüğü fonksiyon 15 değerini alır ve 30 değerini döndürür.

sayısı belli olmayan arguman tanımı için … kullanılır

let greeter = { name, age, ... }: "${name} is ${toString age} years old";
person = {
name = "Slartibartfast";
age = 42;
# the 'email' attribute is not expected by the 'greeter' function ...
email = "slartibartfast@magrath.ea";
};
in greeter person # ... but the call works due to the ellipsis.

greeter adında bir fonksiyon name ve age adında iki arguman alıyor. ün nokta ile eğer daha fazla arguman gelirse onları da alıyor. person adında bir obje tanımlanıyor. bu obje name ve age adında iki attribute içeriyor. email attribute’ü ise greeter fonksiyonu tarafından beklenmiyor. fakat greeter fonksiyonu person objesini arguman olarak alıyor ve çalışıyor. çünkü person objesindeki attribute’lerin hepsi greeter fonksiyonu tarafından beklenen argumanlar arasında yer alıyor. bu durumda email attribute’ü greeter fonksiyonu tarafından kullanılmıyor. ancak sistem üç nokta kullanıldığı için hata vermiyor.

Diğer bir örnekte bütün argumanların adını yazalım

let func = { name, age, ... }@args: builtins.attrNames args;
in func {
name = "Slartibartfast";
age = 42;
email = "slartibartfast@magrath.ea";
}

# sonuç: [ "age" "email" "name" ]

şimdi de değerleri yazdılarım

let func = { name, age, ... }@args: builtins.attrValues args;
in func {
name = "Slartibartfast";
age = 42;
email = "slartibartfast@magrath.ea";
}
# sonuç : [ 42 "slartibartfast@magrath.ea" "Slartibartfast" ]

Conditionals

Nix dilinde koşul ifadeleri kullanmak için if ve else anahtar kelimeleri kullanılır. Koşullar, genellikle bir değerin doğru veya yanlış olup olmadığını kontrol etmek amacıyla kullanılır. İşte basit bir örnek:

# Örnek 1: Bir koşulu kontrol etmek
{
condition = true;
result = if condition then "Doğru durum" else "Yanlış durum";
}

Bu örnekte, condition adlı bir değişken tanımlanmış ve if ifadesi ile kontrol edilmiştir. Eğer condition doğru ise "Doğru durum", değilse "Yanlış durum" dönecektir.

Ayrıca, bir koşulu daha karmaşık hale getirmek için else if de kullanılabilir:

# Örnek 2: Birden fazla koşulu kontrol etmek
{
value = 10;
result =
if value < 0 then "Negatif"
else if value == 0 then "Sıfır"
else "Pozitif";
}

Bu örnekte, value adlı bir değişkenin değerine bağlı olarak bir durum kontrol edilmektedir. Eğer value negatifse "Negatif", sıfırsa "Sıfır", pozitifse "Pozitif" dönecektir.

Koşullar genellikle fonksiyonel bir dilde kullanılan ifade yöntemine benzer şekilde çalışır ve sadece belirli bir durum sağlandığında veya sağlanmadığında değeri değerlendirirler.

Inherit kullanımı

Bu aslında çok basit bir iş yapar. Normalde değişkenler bildiğimiz gibi in bloğu dışında kullanılmaz. Ancak bazen bunları dışarı aktarmamız gerekir. Bundan dolayı da alttaki örnekte olduğu gibi bunları in bloğundan tekrar eşitleriz.

let
name = "Slartibartfast";
# ... other variables
in {
name = name; # set the attribute set key 'name' to the value of the 'name' var
# ... other attributes
}

tabi değişken asıyı çok olduğunda bun yazmak sıkıcı olabilir. Inherit keyword’ü bunu kolaylaştırır.

let
name = "Slartibartfast";
# ... other variables
in {
inherit name;
# ... other attributes
}

bu tabi sadece let içinde tanımlı olanları taşımak için kullanılmaz. Henüz görmedik ancak yine bahsetmek gerekiyor.Import ettiğimiz modüllerdeki attribute’leri de taşımak için kullanılır. Örneğin pkgs modülünü import edelim. Bu modüldeki bütün attribute’leri taşımak için inherit kullanabiliriz.

inherit, bir özellik veya değeri başka bir ifadeden veya bir modülden miras almak için kullanılır. İşte birkaç örnek:

Örnek 1: Modüldeki Bir Özelliği Miras Almak:

# Bir modül tanımla
myModule = {
foo = "Merhaba, Dünya!";
};
# inherit kullanarak modüldeki özelliği miras al
myDerivation = {
inherit (myModule) foo;
buildInputs = [ foo ]; # Foo'yu kullanabilirsin
};

Bu örnekte, myModule adlı bir modül tanımlanmış ve foo adlı özelliği içermektedir. Daha sonra, inherit ifadesi ile myDerivation adlı bir derivasyon tanımlanmıştır ve bu derivasyon, myModule modülünden foo özelliğini miras almıştır.

Örnek 2: Paket Tanımlamasında inherit Kullanımı:

# inherit kullanarak bir paket tanımla
myPackage = pkgs.stdenv.mkDerivation rec {
pname = "myPackage";
version = "1.0";

inherit (pkgs) fetchurl; # fetchurl'ü miras al
src = fetchurl {
url = "https://example.com/myPackage-1.0.tar.gz";
sha256 = "..."; # Gerçek bir değer kullanılmalıdır.
};
};

Bu örnekte, myPackage adlı bir paket tanımlaması yapılmıştır. inherit ifadesi, pkgs modülünden fetchurl'ü miras alarak, src özelliğini tanımlamak için kullanılmıştır. fetchurl fonksiyonu, belirtilen URL'den kaynak dosyasını çekmek için Nix içinde bulunan bir fonksiyondur.

inherit ifadesi, Nix dilinde modülerlik ve tekrar kullanılabilirlik sağlamak için sıkça kullanılır, özellikle de derivasyon tanımlamaları ve paket ifadeleri içinde.

with kullanımı

with anahtar kelimesi çok fazla tekrar etmemizi önler.

# İç içe geçmiş set'ler
outerSet = {
innerSet1 = {
a = 1;
b = 2;
};

innerSet2 = {
c = 3;
d = 4;
};
innerSet3 = {
e = 5;
f = 6;
};
};
# `with` kullanarak iç içe geçmiş set'lerdeki özelliklere erişim
with outerSet; innerSet1.a + innerSet2.d - innerSet3.f

Bu örnekte, outerSet adlı bir set içinde innerSet1, innerSet2 ve innerSet3 adlı üç ayrı set bulunmaktadır. Daha sonra with outerSet; ifadesi ile innerSet1, innerSet2 ve innerSet3 set'lerine doğrudan erişim sağlanmaktadır. Bu set'lerin içindeki özelliklere innerSet1.a, innerSet2.d ve innerSet3.f şeklinde erişim sağlayarak bir matematiksel işlem gerçekleştirilmiştir.

Path veri türü

Nix dilinde path veri tipi, dosya yollarını temsil etmek için kullanılır. Bu veri tipi, bir dizeden ziyade bir dosya yolunu daha güvenli ve taşınabilir bir şekilde ifade etmek için tasarlanmıştır. path tipi, dosya yollarının karakter kodlaması, kök dizin ağacı (root) ve diğer özellikleri konusunda tutarlılığı sağlamak amacıyla geliştirilmiştir.

İşte path veri tipiyle ilgili temel bilgiler:

1. Yol Oluşturma: path tipinde bir yol oluşturmak için builtins.path fonksiyonu kullanılır.

myPath = builtins.path "/etc";

2. Yol İşlemleri: path tipi, standart dosya işlemlerini gerçekleştirmek için kullanılabilir. Örneğin, myPath adlı bir path nesnesinin içindeki dosya ve dizinleri listelemek için:

filesInPath = builtins.readDir myPath;

3. Güvenli Dizin Birleştirme: path tipi, dizinleri güvenli bir şekilde birleştirmek için // operatörünü kullanır. Bu, dosya yollarının sistem bağımsız bir şekilde birleştirilmesini sağlar.

combinedPath = "/etc" // "nginx" // "conf.d";

4. Karakter Kodlaması: path tipi, dosya yollarının karakter kodlamasını sağlamak için özel bir işleme tabi tutulur. Bu, dosya adlarının yanlışlıkla değişmesini önler.

encodedPath = builtins.path "/home/user/ürün";

Bu özellikler sayesinde path veri tipi, dosya işlemlerini daha güvenli ve taşınabilir bir şekilde gerçekleştirmenizi sağlar. Özellikle dosya yollarının taşınabilirliği ve sistem bağımsızlığı önemliyse, path tipini kullanmak iyi bir uygulamadır.

Import, NIX_PATH ve kavramları hakkında

import, NIX_PATH, ve <entry> kavramları, Nix dilinde paket ve modül yönetimi için kullanılan önemli kavramlardır. İşte bu kavramlarla ilgili açıklamalar:

1. Import:

  • import ifadesi, bir dosyada veya URL'de bulunan Nix ifadesini içe aktarmak için kullanılır.
  • Örneğin, import ./myFile.nix ifadesi, mevcut dizindeki myFile.nix dosyasındaki Nix ifadesini içe aktarır.
  • import ile uzak bir kaynaktan da içe aktarım yapılabilir, örneğin: import https://example.com/myFile.nix.

2. NIX_PATH:

  • NIX_PATH, Nix dilinde modüllerin ve diğer kaynakların bulunduğu dizinleri belirten bir ortam değişkenidir.
  • Bu değişken, Nix’in modül sistemini ve kaynakların yerini bulmasını sağlar.
  • Örneğin, NIX_PATH=nixpkgs=/path/to/nixpkgs ifadesi, nixpkgs modülünün /path/to/nixpkgs dizininde bulunduğunu belirtir.
  • NIX_PATH kullanılarak, Nix dilinde farklı modüller ve kaynaklar arasında geçiş yapabilirsiniz.

3. <entry>:

  • <entry>, NIX_PATH içinde belirtilmiş olan bir modülün veya kaynağın adıdır.
  • Örneğin, import <nixpkgs> ifadesi, NIX_PATH içinde belirtilmiş olan nixpkgs modülünü içe aktarır.

İlişki:

  • import ifadesi ile bir dosya veya URL içeri aktarılırken, NIX_PATH içinde belirtilen modüller veya kaynaklar da kullanılabilir.
  • <entry> kullanılarak NIX_PATH içinde belirtilen modüller veya kaynaklar, import ifadesi içinde referans olarak kullanılabilir.
  • Örneğin, import <nixpkgs> ifadesi, NIX_PATH içindeki nixpkgs modülünü içe aktarır.

Bu kavramlar, Nix dilinde kod organizasyonunu ve modülerliği destekleyen önemli bileşenlerdir. import, NIX_PATH, ve <entry> kullanımı, Nix projelerinde kodun düzenlenmesi ve paylaşılabilirliği açısından önemlidir.

Modül Kavramı

Nix dilindeki modül kavramı, Nix kodunu organize etmek, paylaşmak ve tekrar kullanmak için kullanılan bir yapıdır. Modüller, Nix ifadelerini içeren ve genellikle belirli bir konsepti veya işlevi temsil eden dosyalardır. Modüller, Nix projelerini modüler hale getirerek kodun düzenlenmesini ve yönetilmesini kolaylaştırır.

İşte Nix modülleri hakkında temel bilgiler:

1. Modül Tanımlama:

  • Modüller, .nix uzantılı dosyalarda tanımlanır.
  • Genellikle, bir modül, bir fonksiyon, bir paket tanımlaması veya belirli bir konsepti temsil eden bir grup ifade içerir.

2. Modül İçindeki Kodun Kullanımı:

  • Modül içinde tanımlanan ifadeler, diğer Nix dosyalarında import ifadesi kullanılarak içe aktarılabilir ve kullanılabilir.
  • İçe aktarılan modüldeki fonksiyonlar, paket tanımlamaları veya diğer ifadeler, kullanıldıkları dosyada kullanılabilir.

3. Modül İmport Etme:

  • Başka bir Nix dosyasında bir modülü içe aktarmak için import ifadesi kullanılır.
  • Modülün bulunduğu dosyanın yolunu veya NIX_PATH içinde belirtilen bir modülü içe aktarmak mümkündür.
# Modül içindeki fonksiyonları içe aktarma
let
myModule = import ./myModule.nix;
in
myModule.myFunction

4. Modüler Kod Organizasyonu:

  • Modüller, kodu mantıklı bloklara bölerek ve belirli işlevleri temsil ederek projeyi daha düzenli hale getirir.
  • Örneğin, bir modül içinde paket tanımlamaları, özel fonksiyonlar veya sistem konfigürasyonu ile ilgili ifadeler bulunabilir.

Örnek bir modül tanımı:

# myModule.nix

{ lib, buildInputs ? [] }:

{
myFunction = args: {
inherit (args) packageName;
buildInputs = [ lib.iconv ] ++ buildInputs;
};
}

Bu örnek modül, bir fonksiyon içerir ve bu fonksiyon, gelen argümanlara göre bir paket tanımlaması yapar. Başka bir dosyada bu modülü içe aktarıp, myFunction'ı kullanabilirsiniz. Bu şekilde, kodunuzu modüler ve yeniden kullanılabilir hale getirebilirsiniz.

Overrride kullanımı

Nix dilinde override ifadesi, var olan bir ifadeyi veya set'i değiştirmek veya genişletmek için kullanılır. Bu, özellikle paket tanımlamalarını değiştirmek veya var olan bir konfigürasyonu güncellemek için sıkça kullanılır. override ifadesi, bir set'in belirli özelliklerini güncellemenin yanı sıra, bir set'i genişletmek veya daraltmak için de kullanılabilir.

override ifadesinin temel kullanımı şu şekildedir:

# Set içindeki bir özelliği değiştirme
originalSet // { key = newValue; }

# Set'i genişletme
originalSet // { newKey = newValue; }

# Set'ten bir özelliği kaldırma
originalSet // { key = null; }

İşte birkaç örnek:

1. Set içindeki bir özelliği değiştirme:

# Bir set tanımla
mySet = {
key1 = "value1";
key2 = "value2";
};

# key1'i "new_value" ile değiştir
updatedSet = mySet // { key1 = "new_value"; };

2. Set’i genişletme:

# Bir set tanımla
mySet = {
key1 = "value1";
};
# Yeni bir özellik ekleyerek set'i genişlet
extendedSet = mySet // { key2 = "value2"; };

3. Set’ten bir özelliği kaldırma:

# Bir set tanımla
mySet = {
key1 = "value1";
key2 = "value2";
};
# key1'i set'ten kaldır
removedSet = mySet // { key1 = null; };

override ifadesi, özellikle Nix paket tanımlamalarında ve konfigürasyonlarında güncellemeler yapmak için kullanılır. Bu sayede var olan bir set üzerinde değişiklik yapmak, yeni özellikler eklemek veya mevcut özellikleri değiştirmek mümkün olur.

overrideAttrs fonksiyonu, Nix dilinde paket tanımlamalarını değiştirmek için kullanılan özel bir fonksiyondur. Bu fonksiyon, bir paketin tanımındaki özellikleri değiştirmek ve güncellemek için kullanılır. overrideAttrs fonksiyonu, paketin derleme (build) süreci, bağımlılıklar, isim ve diğer özellikleri üzerinde değişiklikler yapma yeteneği sağlar.

Temel kullanım şu şekildedir:

overrideAttrs (oldAttrs: {
// Yeni özellikleri veya değişiklikleri tanımla
})

Bu ifadede:

  • oldAttrs: Orijinal paket tanımındaki özellikleri temsil eden bir değişken.
  • { ... }: Değiştirilecek veya eklenen özellikleri içeren bir set.

İşte bir örnek:

# Orijinal paket tanımı
myPackage = pkgs.stdenv.mkDerivation {
name = "myPackage";
version = "1.0";
src = ./mySource;
};

# overrideAttrs ile paket tanımını değiştirme
myUpdatedPackage = myPackage // (oldAttrs: {
name = "myUpdatedPackage";
buildInputs = [ pkgs.gcc ];
});

Bu örnekte, overrideAttrs fonksiyonu, myPackage adlı paketin tanımını alır ve name özelliğini "myUpdatedPackage" olarak, buildInputs özelliğini ise [ pkgs.gcc ] olarak değiştirir.

overrideAttrs fonksiyonu, paket tanımlamalarını güncelleme ve özelleştirme açısından oldukça güçlü bir araçtır. Bu sayede paketlerin derleme süreçleri, bağımlılıkları veya diğer özellikleri üzerinde ihtiyaç duyulan değişiklikler kolaylıkla yapılabilmektedir.

Nix dilinde override fonksiyonu kullanımına benzer şekilde başka override fonksiyonları da bulunmaktadır. Bunlar, özellikle paket tanımlamalarını, derivasyonları (build), bağımlılıkları ve diğer özellikleri değiştirmek veya genişletmek için kullanılır. İşte bazı örnekler:

1. overrideDerivation: Bu fonksiyon, derivasyon tanımını değiştirmek için kullanılır. Derivasyon, bir paketin derleme sürecini ve diğer ilgili özellikleri tanımlayan bir yapıdır.

myDerivation = pkgs.stdenv.mkDerivation {
name = "myPackage";
version = "1.0";
src = ./mySource;
};

myUpdatedDerivation = pkgs.stdenv.overrideDerivation myDerivation (drv: {
builder = ./myCustomBuilder.sh;
});

2. overrideAttrs: Paket tanımındaki özellikleri değiştirmek için kullanılır. Bu, override ifadesiyle benzerdir.

myPackage = pkgs.stdenv.mkDerivation {
name = "myPackage";
version = "1.0";
src = ./mySource;
};

myUpdatedPackage = myPackage // (oldAttrs: {
name = "myUpdatedPackage";
buildInputs = [ pkgs.gcc ];
});

3. overrideDrv: Bu fonksiyon, derivasyon tanımını (build sürecini) değiştirmek için kullanılır.

myDerivation = pkgs.stdenv.mkDerivation {
name = "myPackage";
version = "1.0";
src = ./mySource;
};

myUpdatedDerivation = pkgs.stdenv.overrideDrv myDerivation (drv: {
builder = ./myCustomBuilder.sh;
});

Bu fonksiyonlar, Nix dilinde paket tanımlamalarını daha esnek ve özelleştirilebilir hale getirmek için kullanılır. Aslında normal bir programalama dilinde bir listeye elemen ekleme, çıkartma ve ya değiştirme işlemleri gibi düşünebiliriz. Override kelimesi genellikle standart programlama dillerinde bir fonksiyonun ezilmesi/yeniden tanımlanası olarak kullanılır.

Nix tabi farkettiğiniz gibi tamamen bir paket yonetimi ve sistem konfigürasyonu üzerine geliştirilmiş bir dil. Dolayısıyla buradaki kavramlar aslında çok iş yapmasına rağmen aslında kullanımı ve anlaması çok kolay. Ancak şu bir gerçek ki bizim çok basit bir şekilde değiştirdiğimiz (override) bir attribute bütün bir sistemin tekrar oluşturulmasına sebep olabilir. Bu nedenle bu tabirler büyük büyük kelimelerle anlatılıyor. Birde sonuçta bir object-oriented bir dil değil Nix. Ancak geliştirdikleri kurgulara da bir isim vermeleri gerekiyor ve burada amaç sadece bir değişkenin değerini değiştirmiş olmak değil.

Overlay

Override sadece local modülü ve dosyayı değiştirebilir. Overlay ise daha global’dir birden fazla modülü veya paketi değiştirebilir .Genellikle overlay.nix dosyalarına yazılarak import edilir.

overlay kavramını örneklerle açıklayalım. overlay genellikle birden fazla set veya modül tanımını birleştirmek veya bir konfigürasyonu değiştirmek için kullanılır. İşte birkaç örnek:

1. Birden Fazla Set’i Birleştirme:

# Birinci set tanımı
firstSet = {
package1 = {
name = "package1";
version = "1.0";
};
};

# İkinci set tanımı
secondSet = {
package2 = {
name = "package2";
version = "2.0";
};
};

# overlay ile set'leri birleştirme
mergedSets = pkgs.lib.composeExtensions [
firstSet
secondSet
];

# mergedSets içindeki paketlere erişim
mergedSets.package1.version # "1.0"
mergedSets.package2.version # "2.0"

Bu örnekte, pkgs.lib.composeExtensions fonksiyonu ile iki set (firstSet ve secondSet) birleştirilmiş ve mergedSets adlı yeni bir set oluşturulmuştur.

2. Paket Tanımlamalarını Değiştirme:

# Orijinal paket tanımlamaları
package1 = pkgs.stdenv.mkDerivation {
name = "package1";
version = "1.0";
};

package2 = pkgs.stdenv.mkDerivation {
name = "package2";
version = "2.0";
};

# overlay ile paket tanımlamalarını değiştirme
overlayPackages = pkgs.lib.composeExtensions [
(self: super: {
package1 = super.package1 // (oldAttrs: {
version = "1.1";
});
package2 = super.package2 // (oldAttrs: {
version = "2.1";
});
})
];

# overlayPackages içindeki paketlere erişim
overlayPackages.package1.version # "1.1"
overlayPackages.package2.version # "2.1"

Bu örnekte, pkgs.lib.composeExtensions fonksiyonu ile iki paket tanımı (package1 ve package2) birleştirilmiş ve overlayPackages adlı yeni bir set oluşturulmuştur. Her iki paketin versiyonu overlay ile değiştirilmiştir.

overlay kullanımı, Nix dilinde modüler ve özelleştirilebilir konfigürasyonlar oluşturmak için önemli bir araçtır. Bu, farklı paket tanımlamalarını birleştirerek veya değiştirerek projenin ihtiyaçlarına uygun bir yapı oluşturmayı sağlar.

Overlay’ler genellikle dış nix dosylaları import edilerek kullanılır.

genellikle overlay.nix dosyaları, Nix projelerinde overlay'lerin tanımlandığı ve organizasyon sağladığı yerlerdir. Bu dosyalar, projenin kök dizininde yer alabilir ve projenin genel konfigürasyonunu, paket tanımlamalarını veya diğer özellikleri değiştirmek için kullanılabilir. overlay.nix dosyalarının adı genellikle standartlaşmış bir şekilde kullanılır, ancak adlandırma konvansiyonlarına katılmak zorunlu değildir.

Aşağıda, bir projede overlay.nix dosyasını kullanarak overlay tanımlamak için basit bir örnek bulunmaktadır:

1. overlay.nix Dosyası:

# overlay.nix

self: super: {
package1 = super.package1 // (oldAttrs: {
version = "1.1";
});

package2 = super.package2 // (oldAttrs: {
version = "2.1";
});
}

Bu overlay.nix dosyası, package1 ve package2 adlı iki paketin versiyonlarını değiştirmek için bir overlay tanımlar.

2. Projedeki Kullanım:

Proje kök dizininde bu overlay.nix dosyası bulunuyorsa, projenin diğer bölümlerinde bu overlay'leri kullanmak oldukça kolaydır.

# default.nix veya başka bir nix dosyası
let
overlays = import ./overlay.nix;
in
{
inherit (overlays) package1 package2;
}

Bu örnekte, import ./overlay.nix; ifadesi ile overlay.nix dosyasındaki overlay'leri içe aktarıyoruz ve ardından bu overlay'leri inherit ifadesi ile projenin genel konfigürasyonuna ekliyoruz.

Bu kullanım, projenin temel konfigürasyonunu overlay.nix dosyasına taşıyarak, projenin çeşitli bölümlerinde bu overlay'leri kullanmayı kolaylaştırır ve organizasyonu artırır. Ayrıca, bu sayede overlay'leri projenin diğer kısımlarına uygulamak daha tutarlı ve yönetilebilir hale gelir.

Originally published at https://github.com.

--

--