Yapı Tabanlı Derleme Sistemleri

Sorun bildirin Kaynağı göster

Bu sayfada yapıya dayalı derleme sistemleri ve bunların yaratıldığı felsefe açıklanmaktadır. Bazel, yapı tabanlı bir derleme sistemidir. Görev tabanlı derleme sistemleri, derleme komut dosyalarının üzerinde iyi bir adım olsa da kendi işlerini tanımlamalarına izin vererek tek tek mühendislere çok fazla güç verir.

Yapı tabanlı derleme sistemleri, sistem tarafından tanımlanan ve mühendislerin sınırlı bir şekilde yapılandırabileceği az sayıda göreve sahiptir. Mühendisler sisteme hâlâ neler derleyeceklerini söylese de derleme sistemi nasıl oluşturulacağını belirler. Görev tabanlı derleme sistemlerinde olduğu gibi Bazel gibi yapı tabanlı derleme sistemlerinde hâlâ derleme dosyaları bulunur ancak bu derleme dosyalarının içeriği çok farklıdır. Çıkış oluşturmayı nasıl tanıtacağınızı anlatan, Turing tarafından tamamlanmış bir komut dosyası dilinde zorunlu komut kümesi olmaktan ziyade Bazel'daki derleme dosyaları, oluşturulacak yapıları, bağımlılıklarını ve nasıl oluşturulduklarını etkileyen sınırlı bir seçenek grubunu açıklayan bildirimli bir yönergedir. Mühendisler komut satırında bazel çalıştırdıklarında, oluşturulacak bir dizi hedefi (ne) belirtir. Derleme adımlarının yapılandırılması, çalıştırılması ve planlanmasından Bazel sorumludur (nasıl). Derleme sistemi artık hangi araçların ne zaman çalıştırılacağı üzerinde tam kontrole sahip olduğundan, doğruluk düzeyini korurken çok daha verimli olmasını sağlayan çok daha güçlü garantiler oluşturabilir.

İşlevsel bir bakış

Yapı tabanlı derleme sistemleri ile işlevsel programlama arasında bir karşılaştırma yapmak kolaydır. Geleneksel zorunlu programlama dilleri (Java, C ve Python gibi), görev temelli derleme sistemlerinin programcılara yürütülecek bir dizi adım tanımlamasına izin verdiği gibi art arda yürütülecek ifade listelerini belirtir. Fonksiyonel programlama dilleri (ör. Haskell ve ML), bunun aksine daha çok matematiksel denklemler gibi yapılandırılır. İşlevsel dillerde, programcı bir hesaplama yapılmasını ister ancak bu hesaplamanın derleyiciye tam olarak ne zaman ve nasıl yürütüldüğüyle ilgili ayrıntıları verir.

Bu, derleme tabanlı derleme sisteminde bir manifest belirtme ve sistemin derlemeyi nasıl yürüteceğini belirlemesine izin verme fikriyle eşlenir. İşlevsel programlama kullanarak birçok sorunu kolayca ifade edememekle birlikte, bu işlevden çok faydalanan dil de genellikle bu tür programları önemli ölçüde paralel hâle getirebiliyor ve doğruluk açısından zorunlu olduğu dilde güçlü bir garanti veriyor. İşlevsel programlama kullanarak ifade edilmesi en kolay sorunlar, bir dizi kuralı veya işlevi kullanarak bir veri parçasını diğerine dönüştürmeyle ilgili sorunlardır. Derleme sistemi tam olarak budur: Tüm sistem, kaynak dosyaları (ve derleyici gibi araçları) giriş olarak alıp çıkış olarak ikili programlar üreten matematiksel bir işlevdir. Dolayısıyla, bir derleme sistemini işlevsel programlama ilkelerine dayandırmak iyi bir sonuç değildir.

Yapı tabanlı derleme sistemlerini anlama

Google'ın derleme sistemi Blaze, yapı tabanlı ilk derleme sistemiydi. Bazel, Blaze'in açık kaynaklı sürümüdür.

Bazel'da bir derleme dosyasının (genellikle BUILD olarak adlandırılır) nasıl göründüğü aşağıda açıklanmıştı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'da 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 hedefler, doğrudan yürütülebilen ikili programlar üretir ve kitaplık hedefleri, ikili programlar veya diğer kitaplıklar tarafından kullanılabilecek kitaplıklar üretir. Her hedefte şunlar bulunur:

  • name: Hedefin komut satırında ve diğer hedeflerde nasıl referans verildiği
  • srcs: Hedef yapısını oluşturmak için derlenen kaynak dosyalar
  • deps: Bu hedeften önce oluşturulması ve bu hedefe bağlanması gereken diğer hedefler

Bağımlılıklar aynı paket içinde (ör. MyBinary ürününün :mylib alanına bağımlısı) veya aynı kaynak hiyerarşisinde farklı bir pakette (mylib hizmetinin //java/com/example/common adresine bağımlılığı) olabilir.

Görev tabanlı derleme sistemlerinde olduğu gibi, Bazel'ın komut satırı aracını kullanarak derlemeler gerçekleştirebilirsiniz. MyBinary hedefini oluşturmak için bazel build :MyBinary yöntemini kullanırsınız. Bu komutu temiz bir depoya ilk kez girdikten sonra Bazel:

  1. Çalışma alanlarındaki her bir BUILD dosyasını ayrıştırarak yapılar arasında bağımlılıklar grafiği oluşturur.
  2. MyBinary'in geçişli bağımlılıklarını belirlemek için grafiği kullanır. Yani MyBinary'in bağımlı olduğu her bir hedef ve bu hedeflerin yinelenen şekilde her bir hedefe bağımlı olduğu.
  3. Bu bağımlılıkların her birini sırayla oluşturur. Bazel, başka bir bağımlılık olmayan her bir hedefi oluşturarak işe başlar ve her bir hedef için hangi bağımlılıkların yine de oluşturulması gerektiğini izler. Bazel, bir hedefin tüm bağımlıları oluşturulur oluşturulmaz, söz konusu hedefi oluşturmaya başlar. Bu işlem, MyBinary'in geçişli bağımlılıklarının her biri oluşturulana kadar devam eder.
  4. MyBinary oluşturur, 3. adımda oluşturulan tüm bağımlıları birbirine bağlayan nihai bir yürütülebilir ikili program üretir.

Aslında, burada olan şey, göreve dayalı bir yapı sistemini kullanırken olanlardan çok daha farklı olmayabilir. Sonuç olarak aynı sonuç ikili programdır ve sonucu üretmek için kullanılan süreç, aralarındaki bağımlılıkları bulmak için bir dizi adımın analiz edilmesini ve ardından bu adımları sırayla yürütmeyi gerektirir. Ancak önemli farklar vardır. İlki 3. adımda görünür: Bazel her hedefin yalnızca bir Java kitaplığı ürettiğini bildiğinden, tek yapması gereken rastgele bir kullanıcı tanımlı komut dosyası yerine Java derleyiciyi çalıştırmaktır. Bu nedenle, bu adımları paralel olarak çalıştırmanın güvenli olduğunu bilir. Bu çok çekirdekli bir makinede hedefleri teker teker oluşturmaya kıyasla büyük bir performans artışı sağlayabilir ve yalnızca yapay zeka odaklı yaklaşım, derleme sistemini kendi yürütme stratejisinden sorumlu olduğu için paralellik ile ilgili daha güçlü garantiler verebilir.

Avantajlar ise paralelliğin ötesine geçer. Bu yaklaşımın bize sağladığı bir sonraki şey, geliştirici hiçbir değişiklik yapmadan ikinci kez bazel build :MyBinary yazdığında görünür: Bazel bir saniyeden kısa sürede hedefin güncel olduğunu belirten bir mesaj gönderiyor. Bu, daha önce bahsettiğimiz işlevsel programlama paradigması nedeniyle mümkün. Bu analiz her düzeyde işe yarar. MyBinary.java değişirse Bazel MyBinary öğesini yeniden oluşturmayı biliyor ancak mylib ürününü yeniden kullanıyor. //java/com/example/common için bir kaynak dosya değişirse Bazel, söz konusu kitaplığı (mylib ve MyBinary) yeniden oluşturmayı biliyor ancak //java/com/example/myproduct/otherlib adını yeniden kullanıyor. Bazel her adımda çalıştırdığı araçların özelliklerini bilir. Bu yüzden eski derlemeler oluşturmayacağını garanti ederken her seferinde yalnızca minimum yapı setini yeniden oluşturabilir.

Derleme sürecini görevler yerine yapı açısından yeniden şekillendirmek hafif ve etkilidir. Programcının maruz kaldığı esnekliği azaltarak derleme sistemi, derlemenin her adımında neler yapılabileceğiyle ilgili daha fazla bilgi edinebilir. Derleme süreçlerini paralelleştirerek ve çıktılarını yeniden kullanarak bu bilgiyi derlemeyi çok daha verimli hale getirmek için kullanabilir. Ancak bu sadece ilk adım. Paralellikle ilgili bu yapı taşları ve yeniden kullanım, dağıtılmış ve son derece ölçeklenebilir bir derleme sisteminin temelini oluşturuyor.

Diğer şık Bazel numaraları

Yapısal tabanlı derleme sistemleri, temelde paralellik gerektiren sorunları çözer ve göreve dayalı derleme sistemlerinde mevcut olan sorunları yeniden kullanır. Ancak daha önce ortaya çıkan birkaç bazı sorunumuz var. Bu sorunları gidermedik. Bazel bunların her birini akıllıca bir şekilde çözebiliyor. Devam etmeden önce bu konuları görüşmemiz gerekiyor.

Bağımlılık olarak araçlar

Daha önce karşılaştığımız bir sorun da, derlemelerin makinemize yüklenen araçlara bağlı olması ve derlemelerin farklı araçlardaki sürümleri veya konumlar nedeniyle sistem genelinde yeniden oluşturulmasıydı. Projeniz, üzerinde inşa edildiği veya derlendiği platforma (ör. Windows veya Linux) bağlı olarak farklı araçlar gerektiren diller kullanıyorsa ve bu platformların her birinin aynı işi yapmak için biraz farklı araçlara ihtiyacı varsa sorun daha da zorlaşır.

Bazel, araçları her hedefe bağımlı olarak değerlendirerek bu sorunun ilk kısmını çözer. Çalışma alanındaki her java_library, dolaylı olarak bir Java derleyicisine bağlıdır. Varsayılan derleyici varsayılan olarak kullanılır. Bazel her java_library geliştirdiğinde, belirtilen derleyicinin bilinen bir konumda kullanıma sunulup sunulmadığını kontrol eder. Diğer tüm bağımlılıklarda olduğu gibi, Java derleyicisi değişirse ona bağlı olan her yapı yeniden oluşturulur.

Bazel, yapılandırmalar ayarlayarak sorunun ikinci bölümü olan platform bağımsızlığını çözer. Hedefler, doğrudan araçlarına bağlı olarak değil, 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: Nihayetinde istediğiniz ikili programı oluşturma

Derleme sistemini genişletme

Bazel, popüler programlama dilleri için ayrı hedefler sunar ancak mühendisler her zaman daha fazlasını yapmak ister. Mühendisler, görev tabanlı sistemlerin avantajlarından biri de her türlü derleme sürecini destekleme esnekliğine sahip olmalarıdır. Bu yapıyı yapı tabanlı bir derleme sisteminde bırakmamak daha doğru olacaktır. Neyse ki Bazel, desteklenen özel türlerin özel kurallar eklenerek genişletilmesine izin veriyor.

Bazel'da bir kural tanımlamak için, kural yazarı, kuralın gerektirdiği girişleri (BUILD dosyasında iletilen özellikler biçiminde) ve kuralın oluşturduğu sabit çıktı kümesini tanımlar. Yazar, bu kural tarafından oluşturulacak işlemleri de tanımlar. Her işlem, girişlerini ve çıktılarını bildirir, belirli bir yürütülebilir dosyayı çalıştırır veya dosyaya belirli bir dize yazar. Ayrıca, giriş ve çıkışları kullanarak diğer işlemlere bağlanılabilir. Diğer bir deyişle, işlemler derleme sistemindeki en düşük seviyedeki birleştirilebilir birimdir. Bir işlem, yalnızca belirtilen giriş ve çıkışları kullandığı sürece istediği her şeyi yapabilir ve Bazel işlemleri planlama ve sonuçlarını uygun şekilde önbelleğe alma işlemini üstlenir.

Sistem, işlemin bir parçası olarak belirleyici olmayan bir işlem başlatmak gibi bir şey yapmasını engelleyen bir yöntem olmadığından, sisteme karşı kesin değildir. Ancak pratikte bu sık görülen bir durum değildir. Kötüye kullanım olasılığını eylem düzeyine düşürmek, hata fırsatlarını büyük ölçüde azaltır. Yaygın olarak kullanılan birçok dili ve aracı destekleyen kurallar internette geniş çapta kullanılabilir. Çoğu proje için hiçbir zaman kendi kurallarının tanımlanması gerekmez. Bu kuralların uygulanması durumunda bile, kural tanımları yalnızca depodaki tek bir merkezi yerde tanımlanmalıdır. Diğer bir deyişle, çoğu mühendis, bu kuralların uygulanması konusunda endişelenmeden bu kuralları kullanabilir.

Çevrenin izole edilmesi

İşlemler, diğer sistemlerdeki görevlerle aynı sorunlara benzeyebilir. Hem aynı dosyaya yazan hem de birbiriyle çakışan işlemler yazmak mümkün değil mi? Bazel aslında korumalı alan kullanarak bu çakışmaları imkansız hale getiriyor. Desteklenen sistemlerde, her işlem bir dosya sistemi korumalı alanı aracılığıyla diğer her işlemden izole edilir. Etkin bir şekilde her işlem, dosya sisteminin yalnızca bildirdiği girişleri ve sağladığı çıkışları içeren kısıtlanmış bir görünümünü görebilir. Bu yaptırım, Docker'ın arkasındaki teknoloji olan Linux'taki LXC gibi sistemler tarafından uygulanır. Bu, beyan ettikleri dosyaları okuyamadıkları için birbirleriyle birbirleriyle çakışmalarının imkansız olduğu ve yazmalarına rağmen beyan ettikleri dosyaların işlem tamamlandığında atılacağı anlamına gelir. Bazel, işlemlerin ağ üzerinden iletişim kurmasını kısıtlamak için de korumalı alanlar kullanır.

Harici bağımlılıkları belirleyici hale getirme

Kalan bir sorun var: Derleme sistemlerinin genellikle bağımlıları (araçlar veya kitaplıklar) doğrudan oluşturmak yerine harici kaynaklardan indirmesi gerekir. Bu durum, Maven'den JAR dosyası indiren @com_google_common_guava_guava//jar bağımlısı 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 bu da derleme sisteminin yeni olup olmadığını sürekli olarak kontrol etmesini gerektirebilir. Bir uzak dosya, çalışma alanı kaynak kodunda buna karşılık gelen bir değişiklik olmadan tekrarlanamazsa da yeniden üretilemez yapılara yol açabilir. Derleme, bir gün çalışabilir ve fark edilmeyen bağımsızlık nedeniyle bir sonraki adımda başarısız olabilir. Son olarak, harici bir bağımlı, üçüncü tarafa ait olduğunda büyük bir güvenlik riski oluşturabilir: Bir saldırgan söz konusu üçüncü taraf sunucusuna sızabilirse, bağımlılık dosyasını kendi tasarımıyla değiştirebilir. Böylece, derleme ortamınız ve çıkışı üzerinde tam kontrol sahibi olabilirler.

Temel sorun, derleme sisteminin bu dosyaları kaynak kontrolünden kontrol etmesine gerek kalmadan bilmesini istediğimizdir. Bağımlılığı güncellemek bilinçli bir seçim olmalıdır, ancak bu seçim tek tek mühendisler tarafından veya otomatik olarak sistem tarafından yönetilmek yerine merkezi bir yerde bir kez yapılmalıdır. Bunun nedeni, "Şu anda yayında" modelinin bile olsa derlemelerin belirleyici olmasını istememizdir. Yani, son haftaya ait bir taahhüte göz atarsanız bağımlılıklarınızı şu anda değil de oldukları gibi görmeniz gerekir.

Bazel ve diğer bazı derleme sistemleri, çalışma alanındaki her harici bağımlılık için şifreleme karması listeleyen bir çalışma alanı genelinde manifest dosyası gerektirerek bu sorunu ele almaktadır. Karma oluşturma, tüm dosyayı kaynak denetimine tabi tutmadan dosyayı benzersiz şekilde temsil etmenin özlü bir yoludur. Bir çalışma alanından yeni bir harici bağımlılığa başvurulduğunda, bu bağımlının karması manifest'e manuel veya otomatik olarak eklenir. Bazel bir derleme çalıştırdığında, önbellekteki bağımlılıknın gerçek karmasını manifestte tanımlanan beklenen karmaya göre kontrol eder ve yalnızca karma farklılık varsa dosyayı yeniden indirir.

İndirdiğimiz yapının manifestte belirtilenden farklı bir karması varsa manifest'teki 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ünden geçmesi gerekir. Bu, bir bağımlının ne zaman güncellendiğine ilişkin bir kayıtın her zaman mevcut olduğu ve harici bir bağımlılığın, çalışma alanı kaynağında buna karşılık gelen bir değişiklik olmadan değişmeyeceği anlamına gelir. Bu aynı zamanda, kaynak kodun eski bir sürümüne göz atarken derlemenin, sürümün kontrol edildiği noktada kullanmakta olduğu bağımlılıkları kullanacağı garanti edilir (aksi takdirde bu bağımlılıklar artık kullanılamıyorsa başarısız olur).

Elbette bir uzak sunucu kullanılamaz hale gelirse veya bozuk veriler yayınlamaya başlarsa bu da bir sorun oluşturabilir. Bu durum, bağımlılığın başka bir kopyasına sahip değilseniz tüm derlemelerinizin başarısız olmasına neden olabilir. Bu sorunu önlemek için önemli olmayan projelerin tüm bağımlılarını güvendiğiniz ve kontrol ettiğiniz sunuculara veya hizmetlere yansıtmanızı öneririz. Aksi halde, check-inlenen karmalar güvenliğinin güvenliğini garanti etse bile derleme sisteminizin kullanılabilirliği açısından her zaman bir üçüncü tarafın merhametinde olursunuz.