Bu sayfada, yapay nesne tabanlı derleme sistemleri ve bunların oluşturulmasının ardındaki felsefe ele alınmaktadır. Bazel, artefakt tabanlı bir derleme sistemidir. Göreve dayalı derleme sistemleri, derleme komut dosyalarına kıyasla daha iyi olsa da mühendislerin kendi görevlerini tanımlamasına izin vererek onlara çok fazla güç verir.
Yapı sistemleri, mühendislerin sınırlı şekilde yapılandırabileceği, sistem tarafından tanımlanan az sayıda göreve sahiptir. Mühendisler, sisteme ne oluşturacağını söylemeye devam eder ancak derleme sistemi, nasıl oluşturulacağını belirler. Görev tabanlı derleme sistemlerinde olduğu gibi, Bazel gibi yapıt tabanlı derleme sistemlerinde de derleme dosyaları bulunur ancak bu derleme dosyalarının içerikleri çok farklıdır. Bazel'deki derleme dosyaları, bir çıktının nasıl üretileceğini açıklayan Turing-complete komut dosyası dilindeki zorunlu bir komut kümesi olmaktan ziyade, oluşturulacak bir dizi yapıyı, bunların bağımlılıklarını ve nasıl oluşturulduklarını etkileyen sınırlı bir seçenek kümesini açıklayan bildirimsel bir manifesttir. Mühendisler komut satırında bazel
çalıştırdığında oluşturulacak bir hedef grubu (ne) belirtir ve derleme adımlarını (nasıl) yapılandırma, çalıştırma ve planlama sorumluluğu Bazel'e aittir. Derleme sistemi artık hangi araçların ne zaman çalıştırılacağı üzerinde tam kontrole sahip olduğundan, doğruluğu garanti etmeye devam ederken çok daha verimli olmasını sağlayan çok daha güçlü garantiler verebilir.
İşlevsel bakış açısı
Yapı tabanlı derleme sistemleri ile işlevsel programlama arasında kolayca benzetme yapılabilir. Geleneksel zorunlu programlama dilleri (ör. Java, C ve Python), görev tabanlı derleme sistemlerinin programcıların yürütülecek bir dizi adım tanımlamasına izin vermesi gibi, art arda yürütülecek ifadelerin listelerini belirtir. Buna karşılık, işlevsel programlama dilleri (ör. Haskell ve ML) daha çok bir dizi matematiksel denklem gibi yapılandırılmıştır. Fonksiyonel dillerde programcı, yapılacak bir hesaplamayı tanımlar ancak bu hesaplamanın ne zaman ve tam olarak nasıl yürütüleceğiyle ilgili ayrıntıları derleyiciye bırakır.
Bu, yapay ürün tabanlı bir derleme sisteminde manifest bildirme ve derlemenin nasıl yürütüleceğini sistemin belirlemesine izin verme fikrine karşılık gelir. Birçok sorun, işlevsel programlama kullanılarak kolayca ifade edilemez ancak bu sorunlar, işlevsel programlamadan büyük ölçüde yararlanır: Dil, bu tür programları genellikle kolayca paralelleştirebilir ve zorunlu bir dilde mümkün olmayacak şekilde doğruluklarıyla ilgili güçlü garantiler verebilir. Fonksiyonel programlama kullanılarak ifade edilmesi en kolay olan problemler, bir dizi kural veya işlev kullanarak bir veri parçasını başka bir veri parçasına dönüştürmeyi içeren problemlerdir. Derleme sistemi tam olarak budur: Tüm sistem, kaynak dosyaları (ve derleyici gibi araçları) giriş olarak alan ve çıkış olarak ikili dosyalar üreten matematiksel bir işlevdir. Bu nedenle, derleme sistemini işlevsel programlamanın ilkeleri üzerine kurmanın iyi sonuç vermesi şaşırtıcı değildir.
Yapı sistemlerini anlamak
Google'ın derleme sistemi Blaze, yapay nesne tabanlı ilk derleme sistemiydi. Bazel, Blaze'in açık kaynaklı sürümüdür.
Bazel'de derleme dosyası (normalde BUILD
olarak adlandırılır) şu şekilde görünür:
java_binary(
name = "MyBinary",
srcs = ["MyBinary.java"],
deps = [
":mylib",
],
)
java_library(
name = "mylib",
srcs = ["MyLibrary.java", "MyHelper.java"],
visibility = ["//java/com/example/myproduct:__subpackages__"],
deps = [
"//java/com/example/common",
"//java/com/example/myproduct/otherlib",
],
)
Bazel'de BUILD
dosyaları hedefleri tanımlar. Buradaki iki hedef türü java_binary
ve java_library
'dir. Her hedef, sistem tarafından oluşturulabilen bir yapıya karşılık gelir: İkili hedef, doğrudan yürütülebilen ikili programlar üretir ve kitaplık hedefi, ikili programlar veya diğer kitaplıklar tarafından kullanılabilen kitaplıklar üretir. Her hedefte:
name
: Hedefin komut satırında ve diğer hedefler tarafından nasıl referans verildiğisrcs
: Hedef için yapıyı oluşturmak üzere derlenen kaynak dosyalardeps
: Bu hedef oluşturulmadan önce oluşturulması ve buna bağlanması gereken diğer hedefler
Bağımlılıklar aynı paket içinde (ör. MyBinary
'nın :mylib
'ye bağımlılığı) veya aynı kaynak hiyerarşisindeki farklı bir pakette (ör. mylib
'nin //java/com/example/common
'ye bağımlılığı) olabilir.
Göreve dayalı derleme sistemlerinde olduğu gibi, Bazel'in komut satırı aracını kullanarak derlemeler gerçekleştirirsiniz. MyBinary
Hedefini oluşturmak için bazel build :MyBinary
komutunu çalıştırırsınız. Bu komutu temiz bir depoda ilk kez girdikten sonra Bazel:
- Çalışma alanındaki her
BUILD
dosyasını ayrıştırarak yapay nesneler arasındaki bağımlılıkların grafiğini oluşturur. MyBinary
öğesinin geçişli bağımlılıklarını belirlemek için grafiği kullanır. YaniMyBinary
öğesinin bağlı olduğu her hedef ve bu hedeflerin bağlı olduğu her hedef, yinelemeli olarak.- Bu bağımlılıkların her birini sırayla oluşturur. Bazel, başka bağımlılığı olmayan her hedefi oluşturarak başlar ve her hedef için hangi bağımlılıkların hâlâ oluşturulması gerektiğini takip eder. Bir hedefin tüm bağımlılıkları oluşturulur oluşturulmaz Bazel, bu hedefi oluşturmaya başlar. Bu işlem,
MyBinary
'nın tüm geçişli bağımlılıkları oluşturulana kadar devam eder. - 3. adımda oluşturulan tüm bağımlılıkları bağlayan son bir yürütülebilir ikili dosya oluşturmak için
MyBinary
oluşturur.
Temelde, burada olanların görev tabanlı bir derleme sistemi kullanırken olanlardan çok farklı olmadığı düşünülebilir. Gerçekten de nihai sonuç aynı ikili dosyadır ve bu dosyayı oluşturma süreci, adımlar arasındaki bağımlılıkları bulmak için bir dizi adımı analiz etmeyi ve ardından bu adımları sırayla çalıştırmayı içerir. Ancak önemli farklılıklar vardır. Birincisi 3. adımda görünür: Bazel, her hedefin yalnızca bir Java kitaplığı ürettiğini bildiği için yapması gereken tek şeyin, rastgele bir kullanıcı tanımlı komut dosyası yerine Java derleyicisini çalıştırmak olduğunu bilir. Bu nedenle, bu adımları paralel olarak çalıştırmanın güvenli olduğunu bilir. Bu, çok çekirdekli bir makinede hedefleri tek tek oluşturmaya kıyasla bir büyüklük sırası performans iyileşmesi sağlayabilir ve yalnızca yapıt tabanlı yaklaşım, paralelizm hakkında daha güçlü garantiler verebilmesi için derleme sisteminin kendi yürütme stratejisinden sorumlu olmasını sağladığı için mümkündür.
Ancak avantajlar paralelliğin ötesine uzanır. Bu yaklaşımın bize sağladığı bir sonraki avantaj, geliştirici herhangi bir değişiklik yapmadan bazel
build :MyBinary
karakterini ikinci kez yazdığında ortaya çıkar: Bazel, hedefin güncel olduğunu belirten bir mesajla bir saniyeden kısa bir süre içinde çıkar. Bu, daha önce bahsettiğimiz işlevsel programlama paradigması sayesinde mümkündür. Bazel, her hedefin yalnızca bir Java derleyicisi çalıştırmanın sonucu olduğunu ve Java derleyicisinin çıktısının yalnızca girişlerine bağlı olduğunu bilir. Bu nedenle, girişler değişmediği sürece çıkış yeniden kullanılabilir.
Bu analiz her düzeyde çalışır. MyBinary.java
değişirse Bazel, MyBinary
öğesini yeniden oluşturması gerektiğini ancak mylib
öğesini yeniden kullanması gerektiğini bilir. //java/com/example/common
kaynak dosyası değişirse Bazel, mylib
ve MyBinary
kitaplıklarını yeniden oluşturması gerektiğini, ancak //java/com/example/myproduct/otherlib
kitaplığını yeniden kullanması gerektiğini bilir.
Bazel, her adımda çalıştırdığı araçların özelliklerini bildiği için her seferinde yalnızca minimum sayıda yapıyı yeniden oluşturabilir ve eski derlemeler üretmeyeceğini garanti eder.
Derleme sürecini görevler yerine yapılar açısından yeniden çerçevelemek ince ama güçlü bir yaklaşımdır. Derleme sistemi, programcıya sunulan esnekliği azaltarak derlemenin her adımında neler yapıldığı hakkında daha fazla bilgi edinebilir. Bu bilgiyi, derleme süreçlerini paralel hale getirerek ve çıktılarını yeniden kullanarak derlemeyi çok daha verimli hale getirmek için kullanabilir. Ancak bu yalnızca ilk adımdır. Paralellik ve yeniden kullanımın bu yapı taşları, dağıtılmış ve yüksek oranda ölçeklenebilir bir derleme sisteminin temelini oluşturur.
Diğer kullanışlı Bazel püf noktaları
Yapı sistemleri, görev tabanlı yapı sistemlerinde bulunan paralellik ve yeniden kullanımla ilgili sorunları temelden çözer. Ancak daha önce ortaya çıkan ve henüz ele almadığımız birkaç sorun var. Bazel'in bunların her birini çözmek için akıllı yöntemleri vardır ve devam etmeden önce bunları tartışmamız gerekir.
Bağımlılık olarak araçlar
Daha önce karşılaştığımız bir sorun, derlemelerin makinemize yüklenen araçlara bağlı olmasıydı. Farklı araç sürümleri veya konumları nedeniyle derlemelerin sistemler arasında yeniden üretilmesi zor olabiliyordu. Projenizde, oluşturuldukları veya derlendikleri platforma (ör. Windows ve Linux) göre farklı araçlar gerektiren diller kullanılıyorsa ve bu platformların her biri aynı işi yapmak için biraz farklı bir araç seti gerektiriyorsa sorun daha da zorlaşır.
Bazel, araçları her hedef için bağımlılık olarak ele alarak bu sorunun ilk kısmını çözer. Çalışma alanındaki her java_library
, varsayılan olarak iyi bilinen bir derleyici olan Java derleyicisine örtülü olarak bağlıdır. Bazel her java_library
oluşturduğunda, belirtilen derleyicinin bilinen bir konumda kullanılabilir olduğundan emin olmak için kontrol yapar. Diğer tüm bağımlılıklarda olduğu gibi, Java derleyicisi değişirse buna bağlı olan her yapıt yeniden oluşturulur.
Bazel, derleme yapılandırmaları ayarlayarak platformdan bağımsızlık olan ikinci bölümdeki sorunu çözer. Hedefler doğrudan araçlarına bağlı olmak yerine yapılandırma türlerine bağlıdır:
- Ana makine yapılandırması: Derleme sırasında çalışan araçlar oluşturma
- Hedef yapılandırma: Sonuçta istediğiniz ikili dosyayı oluşturma
Derleme sistemini genişletme
Bazel, kutudan çıktığı haliyle birkaç popüler programlama dili için hedeflerle birlikte gelir ancak mühendisler her zaman daha fazlasını yapmak ister. Göreve dayalı sistemlerin avantajlarından biri, her türlü derleme sürecini destekleme esnekliğidir. Bu nedenle, artefakta dayalı bir derleme sisteminde bu esnekliği kaybetmemek daha iyi olur. Neyse ki Bazel, desteklenen hedef türlerinin özel kurallar eklenerek genişletilmesine olanak tanır.
Bazel'de bir kural tanımlamak için kural yazarı, kuralın gerektirdiği girişleri (BUILD
dosyasında iletilen özellikler şeklinde) ve kuralın ürettiği sabit çıkış kümesini bildirir. Yazar, bu kural tarafından oluşturulacak işlemleri de tanımlar. Her işlem, giriş ve çıkışlarını bildirir, belirli bir yürütülebilir dosyayı çalıştırır veya belirli bir dizeyi dosyaya yazar ve girişleri ile çıkışları aracılığıyla diğer işlemlere bağlanabilir. Bu, işlemlerin derleme sistemindeki en düşük düzeyde birleştirilebilir birim olduğu anlamına gelir. Bir işlem, yalnızca belirtilen giriş ve çıkışlarını kullandığı sürece istediği her şeyi yapabilir. Bazel ise işlemleri planlama ve sonuçlarını uygun şekilde önbelleğe alma konusunda yardımcı olur.
İşlem geliştiricilerin, işlemlerine deterministik olmayan bir süreç eklemesini engellemenin bir yolu olmadığından sistem tamamen güvenli değildir. Ancak bu durum pratikte pek sık yaşanmaz ve kötüye kullanım olasılıklarını işlem düzeyine kadar düşürmek hata fırsatlarını büyük ölçüde azaltır. Birçok yaygın dili ve aracı destekleyen kurallar internette yaygın olarak bulunur ve çoğu proje kendi kurallarını tanımlamak zorunda kalmaz. Bu tür araçlar için bile kural tanımlarının yalnızca depodaki merkezi bir yerde tanımlanması gerekir. Bu da çoğu mühendisin bu kuralları uygulamaları konusunda endişelenmeden kullanabileceği anlamına gelir.
Ortamı yalıtma
İşlemlerin, diğer sistemlerdeki görevlerle aynı sorunları yaşayabileceği anlaşılıyor. Aynı dosyaya yazan ve birbiriyle çakışan işlemler yazmak hâlâ mümkün değil mi? Aslında Bazel, sandbox kullanarak bu çakışmaları imkansız hale getirir. Desteklenen sistemlerde, her işlem bir dosya sistemi sanal alanı aracılığıyla diğer tüm işlemlerden izole edilir. Aslında her işlem, yalnızca bildirdiği girişleri ve oluşturduğu çıkışları içeren dosya sisteminin kısıtlanmış bir görünümünü görebilir. Bu, Linux'taki LXC gibi sistemler tarafından zorunlu kılınır. Bu sistemler, Docker'ın kullandığı teknolojiyle aynıdır. Bu, işlemlerin birbirleriyle çakışmasının imkansız olduğu anlamına gelir. Çünkü işlemler, bildirilmeyen dosyaları okuyamaz. Ayrıca, yazılan ancak bildirilmeyen dosyalar, işlem tamamlandığında silinir. Bazel, işlemleri ağ üzerinden iletişim kurmaktan kısıtlamak için de korumalı alanları kullanır.
Harici bağımlılıkları belirleyici hale getirme
Ancak bir sorun devam ediyor: Derleme sistemlerinin, bağımlılıkları (araçlar veya kitaplıklar) doğrudan derlemek yerine genellikle harici kaynaklardan indirmesi gerekiyor. Bu, Maven'den JAR
dosyası indiren @com_google_common_guava_guava//jar
bağımlılığı aracılığıyla örnekte görülebilir.
Mevcut çalışma alanının dışındaki dosyalara bağlı olmak risklidir. Bu dosyalar herhangi bir zamanda değişebilir ve derleme sisteminin sürekli olarak güncel olup olmadıklarını kontrol etmesini gerektirebilir. Uzak bir dosya, çalışma alanındaki kaynak kodunda karşılık gelen bir değişiklik olmadan değişirse bu durum, yeniden üretilemeyen derlemelere de yol açabilir. Bir derleme, fark edilmeyen bir bağımlılık değişikliği nedeniyle bir gün çalışıp ertesi gün belirgin bir neden olmadan başarısız olabilir. Son olarak, üçüncü tarafa ait harici bir bağımlılık büyük bir güvenlik riski oluşturabilir. Saldırgan, üçüncü taraf sunucusuna sızmayı başarırsa bağımlılık dosyasını kendi tasarımıyla değiştirebilir. Bu da saldırgana derleme ortamınız ve çıktısı üzerinde tam kontrol sağlayabilir.
Temel sorun, derleme sisteminin bu dosyaları kaynak kontrolüne işlemeye gerek kalmadan bilmesini istememizdir. Bağımlılıkları güncellemek bilinçli bir seçim olmalıdır ancak bu seçim, tek tek mühendisler tarafından yönetilmek veya sistem tarafından otomatik olarak yapılmak yerine merkezi bir yerde bir kez yapılmalıdır. Bunun nedeni, "Live at Head" modeliyle bile derlemelerin deterministik olmasını istememizdir. Bu, geçen haftadan bir taahhüt kontrol ederseniz bağımlılıklarınızı o zamanki gibi görmeniz gerektiği anlamına gelir.
Bazel ve diğer bazı derleme sistemleri, çalışma alanındaki her harici bağımlılık için bir şifreli karma listeleyen çalışma alanı genelinde bir manifest dosyası gerektirerek bu sorunu ele alır. Karma, dosyanın tamamını kaynak denetimine dahil etmeden dosyayı benzersiz bir şekilde temsil etmenin kısa bir yoludur. Bir çalışma alanından her yeni harici bağımlılık referans alındığında, bu bağımlılığın karması, manuel olarak veya otomatik olarak manifeste eklenir. Bazel bir derleme çalıştırdığında, önbelleğe alınmış bağımlılığının gerçek karma değerini manifestte tanımlanan beklenen karma değerle karşılaştırır ve dosyayı yalnızca karma değer farklıysa yeniden indirir.
İndirdiğimiz yapının karması, manifest dosyasında belirtilenden farklıysa manifest dosyasındaki karma güncellenmediği sürece derleme başarısız olur. Bu işlem otomatik olarak yapılabilir ancak derlemenin yeni bağımlılığı kabul etmesi için bu değişikliğin onaylanması ve kaynak kontrolüne dahil edilmesi gerekir. Bu sayede, bir bağımlılığın ne zaman güncellendiğine dair her zaman bir kayıt bulunur ve çalışma alanı kaynağında ilgili bir değişiklik yapılmadan harici bir bağımlılık değiştirilemez. Ayrıca, kaynak kodun eski bir sürümü kontrol edilirken derlemenin, bu sürümün kontrol edildiği sırada kullandığı bağımlılıkları kullanacağı (veya bu bağımlılıklar artık kullanılamıyorsa başarısız olacağı) anlamına da gelir.
Elbette, uzak bir sunucu kullanılamaz hale gelirse veya bozuk veriler sunmaya başlarsa bu durum sorun olmaya devam edebilir. Bu durumda, söz konusu bağımlılığın başka bir kopyası yoksa tüm derlemeleriniz başarısız olmaya başlayabilir. Bu sorunu önlemek için önemli projelerin tüm bağımlılıklarını güvendiğiniz ve kontrol ettiğiniz sunuculara veya hizmetlere yansıtmanızı öneririz. Aksi takdirde, kontrol edilen karmalar güvenliğini garanti etse bile, derleme sisteminizin kullanılabilirliği için her zaman üçüncü bir tarafın insafına kalırsınız.