Yapı Tabanlı Derleme Sistemleri

Sorun bildirin Kaynağı göster

Bu sayfada, yapı tabanlı derleme sistemleri ve bunların oluşturulmasının arkasındaki felsefe ele alınmaktadır. Bazel, yapı tabanlı bir derleme sistemidir. Görev tabanlı derleme sistemleri, derleme komut dosyalarından daha iyi bir adım olsa da mühendislere kendi görevlerini tanımlamalarına izin vererek çok fazla güç verir.

Yapı tabanlı derleme sistemlerinde, sistem tarafından tanımlanan ve mühendislerin sınırlı bir şekilde yapılandırabileceği az sayıda görev bulunur. Mühendisler sisteme yine de ne oluşturacağını söyler, ancak bunun nasıl yapılacağını derleme sistemi belirler. Görev tabanlı derleme sistemlerinde olduğu gibi, Bazel gibi yapı tabanlı derleme sistemlerinde de derleme dosyaları hâlâ bulunur ancak bu derleme dosyalarının içerikleri çok farklıdır. Turing'in eksiksiz bir kodlama dilinde çıkış üretmeyi açıklayan zorunlu bir komut dizisi olmak yerine, Bazel'deki derleme dosyaları, derlenecek bir dizi yapıyı, bunların bağımlılıklarını ve bunların oluşturulma şeklini etkileyen sınırlı sayıda seçeneği açıklayan bildirim temelli bir manifesttir. Mühendisler komut satırında bazel çalıştırdığında, derlemek için bir dizi hedef (ne) belirtir ve Bazel, derleme adımlarını (nasıl) yapılandırmaktan, çalıştırmaktan ve planlamaktan sorumludur. Derleme sistemi artık hangi araçların ne zaman çalıştırılacağı üzerinde tam kontrole sahip olduğundan, doğruluğu garanti ederken çok daha verimli olmasını sağlayan daha güçlü garantiler verebilir.

İşlevsel bakış açısı

Yapı tabanlı derleme sistemleri ile işlevsel programlama arasında karşılaştırma yapmak kolaydır. 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 olanak tanıdığı gibi birbiri ardına yürütülecek ifade listelerini belirtir. İşlevsel programlama dilleri (ör. Haskell ve ML) ise bunun aksine, bir dizi matematiksel denkleme benzer. Fonksiyonel dillerde, programcı gerçekleştirilecek bir hesaplamayı tanımlar ancak bu hesaplamanın ne zaman ve tam olarak nasıl yürütüldüğüne dair ayrıntıları derleyiciye bırakır.

Bu, yapı tabanlı bir derleme sisteminde manifest bildirme ve sistemin, derlemenin nasıl yürütüleceğini belirlemesine izin verme fikriyle uyumludur. Birçok sorun işlevsel programlama kullanılarak kolayca ifade edilemez, ancak bundan büyük faydası/menfaat sağlayacak sorunlar: Dil, bu tür programları çoğunlukla İşlevsel programlama kullanılarak ifade edilmesi en kolay sorunlar, bir veri parçasını bir dizi kural veya işlev kullanarak diğerine dönüştürmeyi içeren problemlerdir. Derleme sistemi de tam olarak bu şekildedir: Tüm sistem, kaynak dosyaları (ve derleyici gibi araçları) girdi olarak alıp çıktı olarak ikili programlar üreten matematiksel bir işlevdir. Dolayısıyla, bir derleme sistemini işlevsel programlama ilkeleri üzerine inşa etmenin iyi sonuç vermesi şaşırtıcı değildir.

Yapı tabanlı derleme sistemlerini anlama

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

Bazel'de bir derleme dosyasının (normal olarak adı BUILD) nasıl göründüğü aşağıda verilmiştir:

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şturulabilecek 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: Komut satırında ve diğer hedefler tarafından hedefe nasıl referans verildiği
  • srcs: hedef için yapı oluşturmak üzere derlenecek kaynak dosyalar
  • deps: bu hedeften önce oluşturulması ve bağlanması gereken diğer hedefler

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

Görev tabanlı derleme sistemlerinde olduğu gibi, derlemeleri de Bazel'in komut satırı aracını kullanarak gerçekleştirirsiniz. MyBinary hedefini oluşturmak için bazel build :MyBinary çalıştırıyorsunuz. Bu komutu temiz bir depoya ilk kez girdikten sonra Bazel:

  1. Yapılar arasındaki bağımlılıkların bir grafiğini oluşturmak için çalışma alanındaki her BUILD dosyasını ayrıştırır.
  2. Grafiği, MyBinary öğesinin geçişli bağımlılıklarını, yani MyBinary bağlı olduğu her hedefi ve bu hedeflerin bağımlı olduğu her hedefi yinelemeli olarak belirlemek için kullanır.
  3. Bu bağımlılıkların her birini sırayla oluşturur. Bazel başka hiçbir bağımlılığı olmayan hedefleri oluşturarak işe başlar ve her bir hedef için hâlâ hangi bağımlılıkların oluşturulması gerektiğini takip eder. Bir hedefin tüm bağımlılıkları oluşturulduğunda Bazel bu hedefi oluşturmaya başlar. Bu süreç, MyBinary ürününün geçişli bağımlılıklarının her biri oluşturulana kadar devam eder.
  4. 3. adımda derlenen tüm bağımlılıkları bağlayan yürütülebilir nihai bir ikili program oluşturmak için MyBinary yöntemini oluşturur.

Esasen, buradaki olay görev tabanlı bir derleme sistemi kullanıldığında olanlardan çok farklı görünmeyebilir. Gerçekten de, nihai sonuç aynı ikili programdır ve bu süreci oluşturma sürecinde, aralarındaki bağımlılıkları bulmak için bir dizi adımın analiz edilmesi ve ardından bu adımların sırayla uygulanması gerekir. Ancak kritik farklar var. İlki 3. adımda görünür: Bazel her hedefin yalnızca bir Java kitaplığı ürettiğini bildiği için tek yapması gereken, rastgele bir kullanıcı tanımlı komut dosyası yerine Java derleyiciyi çalıştırmak olduğunu bilmektedir, böylece bu adımları paralel olarak çalıştırmanın güvenli olduğunu bilir. Bu, çok çekirdekli bir makinede her defasında bir hedef oluşturmaya kıyasla önemli bir performans artışı sağlayabilir ve bu yalnızca yapı tabanlı yaklaşımın, derleme sistemini kendi yürütme stratejisinden sorumlu tutması ve böylece paralellikle ilgili daha güçlü garantiler vermesi nedeniyle mümkündür.

Ancak avantajlar paralelliğin ötesine geçiyor. Bu yaklaşımın bize sunduğu bir başka şey de, geliştirici hiçbir değişiklik yapmadan ikinci kez bazel build :MyBinary yazdığında belirgin hale gelir: Bazel bir saniyeden kısa sürede hedefin güncel olduğunu söyleyen bir mesaj ile çıkar. Bu, daha önce bahsettiğimiz işlevsel programlama paradigması nedeniyle mümkündür. Bazel her hedefin yalnızca bir Java derleyici çalıştırmanın bir sonucu olduğunu ve Java derleyicisinden gelen çıkışın sadece girişlere bağlı olduğunu bilir. Bu nedenle, girişler değişmediği sürece çıktı yeniden kullanılabilir. Ayrıca bu analiz her düzeyde işe yarar. MyBinary.java değişirse Bazel, MyBinary alanını yeniden inşa edip mylib alanını yeniden kullanabileceğini bilir. //java/com/example/common için kaynak dosyalardan biri değişirse Bazel, mylib ve MyBinary kitaplığını yeniden oluşturacağını biliyor ancak //java/com/example/myproduct/otherlib kitaplığını yeniden kullanıyor. 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şturabiliyor ve aynı zamanda eski derlemeler oluşturmayacağını garanti ediyor.

Derleme sürecini görevlerden ziyade eserler açısından yeniden çerçevelemek zor ancak güçlü bir yöntemdir. Programcının maruz kaldığı esnekliği azaltarak derleme sistemi, derlemenin her adımında yapılan işlemler hakkında daha fazla bilgi sahibi olabilir. Bu bilgiyi, derleme süreçlerini paralel hale getirip çıkışları yeniden kullanarak derlemeyi çok daha verimli hale getirmek için kullanabilir. Ancak bu sadece başlangıç. Paralellik ve yeniden kullanımla ilgili bu yapı taşları, dağıtılmış ve yüksek düzeyde ölçeklenebilir bir derleme sisteminin temelini oluşturur.

Diğer şık Bazel numaraları

Yapı tabanlı derleme sistemleri, görev tabanlı derleme sistemlerinde mevcut olan paralellik ve tekrar kullanımla ilgili sorunları temelden çözer. Ama hâlâ daha önce ortaya çıkan ve ele alamadığımız birkaç sorun var. Bazel'ın bunların her birini zekice çözümleri var, devam etmeden önce bunlar üzerine konuşmalıyız.

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

Daha önce karşılaştığımız sorunlardan biri, derlemelerin makinemizde yüklü olan araçlara bağlı olması ve derlemeleri farklı araç sürümleri ya da konumlar nedeniyle sistemler arasında yeniden oluşturmanın zor olabilmesiydi. Projenizde, geliştirildiği veya derlendiği platforma bağlı olarak farklı araçlar gerektiren diller kullanıldığında (Windows ve Linux gibi) ve bu platformların her biri aynı işi yapmak için biraz farklı araçlar gerektirdiğinde sorun daha da zorlaşır.

Bazel, araçları her bir hedefe bağımlılık olarak değerlendirerek bu sorunun ilk kısmını çözer. Çalışma alanındaki her java_library dolaylı olarak bir Java derleyiciye bağlıdır. Java derleyici varsayılan olarak iyi bilinen bir derleyiciye ayarlanır. Bazel, bir java_library derlerken, belirtilen derleyicinin bilinen bir konumda bulunduğundan emin olmak için kontrol gerçekleştirir. Diğer bağımlılıklarda olduğu gibi, Java derleyicisi değişirse ona bağlı olan her eser yeniden oluşturulur.

Bazel, derleme yapılandırmaları belirleyerek sorunun ikinci kısmı olan platform bağımsızlığını çözer. Doğrudan araçlarına bağlı olan hedefler 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: Nihai olarak istediğiniz ikili programı oluşturma

Derleme sistemini genişletme

Bazel'in hemen birçok popüler programlama dili için hedefleri vardır, ancak mühendisler her zaman daha fazlasını yapmak isteyecektir. Görev tabanlı sistemlerin avantajı, her türlü derleme işlemini destekleme esnekliğidir ve yapı tabanlı bir derleme sisteminde bundan vazgeçmemek daha iyi olacaktır. Neyse ki Bazel, özel kurallar ekleyerek desteklenen hedef türlerinin genişletilmesine olanak tanıyor.

Kural yazarı, Bazel'de bir kural tanımlamak için kuralın gerektirdiği girişleri (BUILD dosyasında iletilen özellikler biçiminde) ve kuralın ürettiği sabit çıkış grubunu tanımlar. Yazar, bu kural tarafından oluşturulacak işlemleri de tanımlar. Her işlem; girdi ve çıkışlarını tanımlar, belirli bir yürütülebilir öğeyi çalıştırır veya bir dosyaya belirli bir dize yazar, ayrıca kendi giriş ve çıkışları aracılığıyla diğer işlemlere bağlanabilir. Bu, işlemlerin derleme sistemindeki en düşük seviyedeki composable birim olduğu anlamına gelir. Bir işlem yalnızca beyan edilen giriş ve çıkışlarını kullandığı sürece istediği her şeyi yapabilir. Bazel işlemleri planlamak ve sonuçlarını uygun şekilde önbelleğe almakla ilgilenir.

Sistem, işlem geliştiricilerinin eylemlerinin bir parçası olarak deterministik olmayan bir süreç başlatmak gibi bir şey yapmalarını engellemenin mümkün olmadığı düşünülerek hatasız değildir. Ancak bu durum pratikte pek yaşanmaz. Kötüye kullanım olasılıklarını işlem düzeyine kadar sermek, hata olasılığını önemli ölçüde azaltır. Birçok yaygın dili ve aracı destekleyen kurallar internette yaygın olarak kullanılabilir ve çoğu projede hiçbir zaman kendi kurallarını tanımlaması gerekmez. Tanımlanmış olanlar bile kural tanımlarının depoda tek bir merkezi yerde tanımlanmalıdır. Böylece çoğu mühendis, bu kuralları uygulama konusunda endişe duymadan kullanabilir.

Ortamı izole etme

Eylemler, diğer sistemlerdeki görevlerle aynı sorunlarla karşılaşacak gibi görünüyor. Aynı dosyaya yazılan ve birbiriyle çakışan işlemler yazmak yine de mümkün değil mi? Aslında Bazel, korumalı alan kullanarak bu çakışmaları imkansız hale getirir. Desteklenen sistemlerde her işlem, bir dosya sistemi korumalı alanı aracılığıyla diğer tüm işlemlerden izole edilir. Her işlem, etkili bir şekilde, bildirdiği girişleri ve ürettiği çıkışları içeren dosya sisteminin yalnızca kısıtlanmış bir görünümünü görebilir. Bu, Docker'ın arkasındaki teknoloji olan Linux'taki LXC gibi sistemler tarafından uygulanır. Yani, beyan edilmeyen dosyaları okuyamadıkları için eylemlerin birbirleriyle çelişmesi imkansızdır ve yazdıkları ancak beyan etmedikleri dosyalar, işlem tamamlandığında atılır. Bazel, işlemlerin ağ üzerinden iletişim kurmasını kısıtlamak için de korumalı alanları kullanır.

Dış bağımlılıkları belirleyici hale getirme

Hâlâ bir sorun vardır: Derleme sistemlerinin bağımlılıkları (araç veya kitaplık) doğrudan oluşturmak yerine genellikle harici kaynaklardan indirmesi gerekir. Bu örnek, Maven'den JAR dosyası indiren @com_google_common_guava_guava//jar bağımlılığı aracılığıyla görülebilir.

Mevcut çalışma alanının dışındaki dosyalara bağlı olarak risklidir. Bu dosyalar her an değişebilir ve muhtemelen derleme sisteminin, dosyaların güncel olup olmadığını sürekli olarak kontrol etmesi gerekebilir. Uzak bir dosya, çalışma alanı kaynak kodunda karşılık gelen bir değişiklik olmadan değişirse, yeniden oluşturulamayan derlemelere de yol açabilir. Bir derleme bir gün çalışabilir ve fark edilmeyen bir bağımlılık değişikliği nedeniyle bariz bir neden olmadan bir sonraki gün başarısız olabilir. Son olarak, dış bağımlılık, üçüncü bir tarafa ait olduğunda büyük bir güvenlik riskine yol açabilir. Bir saldırgan, ilgili üçüncü taraf sunucusuna sızma imkanına sahip olursa bağımlılık dosyasını kendi tasarımıyla değiştirebilir. Böylece, derleme ortamınız ve çıktısı üzerinde tam kontrol sahibi olur.

Temel sorun, derleme sisteminin bu dosyaları kaynak kontrolüne sokmak zorunda kalmadan bunlardan haberdar olmasını istememizdir. Bağımlılık güncellemek bilinçli bir seçim olmalıdır ancak bu seçim, tek tek mühendisler veya sistem tarafından otomatik olarak yönetmek yerine merkezi bir yerde bir kez yapılmalıdır. Çünkü "Canlı Yayında" modelinde bile yapıların deterministik olmasını isteriz. Bu da geçen haftanın bir kaydını incelediğinizde bağımlılıklarınızı şu anda değil, o anki haliyle 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 şifreleme karması listeleyen, çalışma alanı genelinde bir manifest dosyası gerektirerek bu sorunu ele alır. Karma oluşturma, tüm dosyayı kaynak kontrolünde kontrol etmeden dosyayı benzersiz bir şekilde temsil etmenin kısa bir yoludur. Bir çalışma alanından yeni bir harici bağımlılığa referans verildiğinde, söz konusu bağımlılığın karması manifeste manuel veya otomatik olarak eklenir. Bazel bir derleme çalıştırdığında, önbelleğe alınan bağımlılığın gerçek karma değerini, manifestte tanımlanmış beklenen karmayla karşılaştırarak kontrol eder ve yalnızca karma farklıysa dosyayı yeniden indirir.

İndirdiğimiz yapı manifestte belirtilenden farklı bir karmaya sahipse manifest'teki karma güncellenmediği sürece derleme başarısız olur. Bu otomatik olarak yapılabilir ancak derlemenin yeni bağımlılığı kabul etmesi için önce bu değişikliğin onaylanması ve kaynak kontrolüne kontrol edilmesi gerekir. Bu, bir bağımlılığın ne zaman güncellendiğine ilişkin her zaman bir kayıt olduğu anlamına gelir ve çalışma alanı kaynağında karşılık gelen bir değişiklik yapılmadan harici bağımlılık değiştirilemez. Bu aynı zamanda, kaynak kodun eski bir sürümünü kontrol ederken derlemenin, bu sürümün kontrol edildiği anda kullandığı bağımlılıkların aynısını kullanmasının garanti edileceği (veya bu bağımlılıklar artık kullanılamadığında başarısız olacağı) anlamına gelir.

Uzak sunucu kullanılamaz hale gelirse veya bozuk veri sunmaya başlarsa bu durum yine de sorun teşkil edebilir. Bu, söz konusu bağımlılığın başka bir kopyası elinizde yoksa tüm derlemelerinizin başarısız olmasına neden olabilir. Bu sorunun önüne geçmek için önemsiz projeler için tüm bağımlılıklarını, güvendiğiniz ve kontrol ettiğiniz sunuculara veya hizmetlere yansıtmanızı öneririz. Aksi takdirde, giriş yapılan karmalar güvenliğini garanti etse bile derleme sisteminizin kullanılabilirliği konusunda her zaman üçüncü bir tarafın yardımıyla olursunuz.