Yapı Tabanlı Derleme Sistemleri

Sorun bildirme Kaynağı görüntüleme Nightly · 8.0 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

Öğe 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 ne oluşturulacağını söyler ancak derleme sistemi nasıl oluşturulacağını belirler. Görev tabanlı derleme sistemlerinde olduğu gibi, Bazel gibi yapıya dayalı derleme sistemlerinde de derleme dosyaları bulunur ancak bu derleme dosyalarının içeriği çok farklıdır. Bazel'deki derleme dosyaları, Turing-tam bir komut dosyası dilinde bir çıkışın nasıl üretileceğini açıklayan zorunlu bir komut grubu olmaktan ziyade, derlenecek bir yapı grubunu, bu yapıların bağımlılıkları ile bunların nasıl derleneceğini etkileyen sınırlı sayıda seçeneği açıklayan açıklayıcı bir manifesttir. Mühendisler komut satırında bazel çalıştırdığında, derlenecek bir dizi hedef (ne) belirtir ve Bazel, derleme adımlarını (nasıl) yapılandırma, çalıştırma ve planlamaktan sorumludur. Derleme sistemi artık hangi araçların ne zaman çalışacağı üzerinde tam kontrole sahip olduğundan, doğruluğu garanti ederken çok daha verimli olmasını sağlayan çok daha güçlü garantiler verebilir.

İşlevsel bakış açısı

Yapıya dayalı derleme sistemleri ile işlevsel programlama arasında analoji yapmak kolaydır. Görev tabanlı derleme sistemlerinin programcıların yürütecekleri bir dizi adımı tanımlamasına olanak tanıdığı gibi, geleneksel zorunlu programlama dilleri (Java, C ve Python gibi) de birbiri ardına yürütülecek ifade listelerini belirtir. Buna karşılık işlevsel programlama dilleri (Haskell ve ML gibi), daha çok bir dizi matematiksel denklem gibi yapılandırılmıştır. İşlevsel dillerde programcı, yapılacak bir hesaplamayı açıklar ancak bu hesaplamanın ne zaman ve tam olarak nasıl yürütüleceğine dair ayrıntıları derleyiciye bırakır.

Bu, yapıya dayalı bir derleme sisteminde manifest beyan etme ve sistemin derlemeyi nasıl yürüteceğini belirlemesine izin verme fikriyle eşleşir. Birçok sorun işlevsel programlama kullanılarak kolayca ifade edilemez ancak bu programlama biçiminden büyük fayda sağlayan sorunlar da vardır: Dil, genellikle bu tür programları kolayca paralelleştirebilir ve doğruluklarıyla ilgili, zorunlu bir dilde imkansız olan güçlü garantiler verebilir. İşlevsel programlama kullanarak ifade etmenin en kolay olduğu sorunlar, bir dizi kural veya işlev kullanarak bir veri parçasını başka bir veri parçasına dönüştürmeyi içerenlerdir. Derleme sistemi tam olarak budur: Sistemin tamamı, giriş olarak kaynak dosyaları (ve derleyici gibi araçları) alan ve çıkış olarak ikili dosyalar üreten matematiksel bir işlevdir. Bu nedenle, bir derleme sisteminin işlevsel programlamanın temel ilkelerine dayandırılmasının iyi sonuç vermesi şaşırtıcı değildir.

Öğe tabanlı derleme sistemlerini anlama

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

Bazel'de bir derleme dosyası (normalde BUILD olarak adlandırılır) aşağıdaki gibi 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şturulabilecek bir yapıya karşılık gelir: İkili hedefler doğrudan yürütülebilen ikili programlar, kitaplık hedefleri ise ikili programlar veya diğer kitaplıklar tarafından kullanılabilen kitaplıklar oluşturur. Her hedefin:

  • name: Hedefin komut satırında ve diğer hedefler tarafından nasıl referans verildiği
  • srcs: Hedefin yapısını oluşturmak için derlenecek 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'ün :mylib'a bağımlılığı) veya aynı kaynak hiyerarşisindeki farklı bir pakette (ör. mylib'nin //java/com/example/common'a bağımlılığı) olabilir.

Görev tabanlı derleme sistemlerinde olduğu gibi, derlemeleri Bazel'in komut satırı aracını kullanarak 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:

  1. Araçlar arasındaki bağımlılık grafiğini oluşturmak için çalışma alanındaki her BUILD dosyasını ayrıştırır.
  2. MyBinary öğesinin geçişli bağımlılıkları (yani MyBinary öğesinin bağımlı olduğu her hedef ve bu hedeflerin bağımlı olduğu her hedef) belirlemek için grafiği kullanır.
  3. 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 izler. Bir hedefin tüm bağımlılıkları derlendikten sonra Bazel, söz konusu hedefi derlemeye başlar. Bu işlem, MyBinary'ün geçişli bağımlılıkları oluşturulana kadar devam eder.
  4. 3. adımda oluşturulan tüm bağımlılıkları içeren son bir yürütülebilir ikili oluşturmak için MyBinary'ü oluşturur.

Temel olarak, burada gerçekleşen sürecin görev tabanlı bir derleme sistemi kullanırken gerçekleşen süreçten çok farklı olmadığı düşünülebilir. Sonuçta aynı ikili dosya elde edilir. Bu dosyayı oluşturma süreci, aralarındaki bağımlılıkları bulmak için bir dizi adımın analiz edilmesini ve ardından bu adımların sırayla çalıştırılmasını içerir. Ancak kritik farklılıklar vardır. İlki 3. adımda görünür: Bazel, her hedefin yalnızca bir Java kitaplığı ürettiğini bildiğinden, yapması gereken tek şeyin kullanıcı tanımlı rastgele bir komut dosyası yerine Java derleyicisini çalıştırmak olduğunu bilir. Bu nedenle, bu adımların paralel olarak çalıştırılmasının güvenli olduğunu bilir. Bu, hedefleri çok çekirdekli bir makinede tek tek derlemeye kıyasla performansta büyük bir artış sağlayabilir. Bu durum yalnızca yapıya dayalı yaklaşımın, paralellik hakkında daha güçlü garantiler verebilmek için derleme sisteminin kendi yürütme stratejisinden sorumlu bırakması nedeniyle mümkündür.

Bununla birlikte, paralelleştirmenin avantajları bununla sınırlı değildir. Bu yaklaşımın bize sunduğu bir sonraki avantaj, geliştirici herhangi bir değişiklik yapmadan bazel build :MyBinary ifadesini ikinci kez yazdığında ortaya çıkar: Bazel, hedefin güncel olduğunu belirten bir mesajla bir saniyeden kısa sürede çıkar. Bu, daha önce bahsettiğimiz işlevsel programlama paradigması sayesinde mümkündür. Bazel, her hedefin yalnızca bir Java derleyicisinin çalıştırılmasını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'ü yeniden oluşturmayı ancak mylib'yi yeniden kullanmayı bilir. //java/com/example/common için bir kaynak dosya değişirse Bazel, bu kitaplığı, mylib ve MyBinary'yi yeniden derlemeyi bilir ancak //java/com/example/myproduct/otherlib'ı yeniden kullanmaz. Bazel, her adımda çalıştırdığı araçların özelliklerini bildiğinden, her seferinde yalnızca minimum yapı grubunu yeniden oluşturabilir ve eski derlemeler oluşturmayacağını garanti eder.

Derleme sürecini görevler yerine yapı olarak yeniden çerçevelendirmek ince ama güçlü bir yaklaşımdır. Programcıya sunulan esnekliği azaltarak derleme sisteminin, derlemenin her adımında neler yapıldığı hakkında daha fazla bilgi edinmesini sağlayabilirsiniz. Bu bilgiyi kullanarak derleme süreçlerini paralelleştirip çıktılarını yeniden kullanarak derlemeyi çok daha verimli hale getirebilir. Ancak bu yalnızca ilk adımdır. Paralellik ve yeniden kullanmanın bu yapı taşları, dağıtılmış ve yüksek ölçeklenebilir bir derleme sisteminin temelini oluşturur.

Diğer kullanışlı Bazel püf noktaları

Öğe tabanlı derleme sistemleri, temel olarak görev tabanlı derleme sistemlerine özgü paralellik ve yeniden kullanım sorunlarını çözer. Ancak daha önce ortaya çıkan ve henüz ele almadığımız birkaç sorun var. Bazel'in bu sorunların her birini çözmenin akıllıca yolları vardır. 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ı. Ayrıca, farklı araç sürümleri veya konumları nedeniyle derlemeleri sistemler arasında yeniden oluşturmak zor olabilir. Projenizde, geliştirildikleri veya derlendikleri platforma (ör. Windows ve Linux) göre farklı araçlar gerektiren diller kullanıldığında ve bu platformların her biri aynı işi yapmak için biraz farklı bir araç grubu gerektirdiğinde sorun daha da karmaşık hale gelir.

Bazel, araçları her hedefin bağımlılığı olarak ele alarak bu sorunun ilk bölümünü çözer. Çalışma alanındaki her java_library, varsayılan olarak iyi bilinen bir derleyici olan bir Java derleyiciye bağlıdır. Bazel bir java_library derlediğinde, belirtilen derleyicinin bilinen bir konumda bulunduğundan emin olur. Diğer tüm bağımlılıklarda olduğu gibi, Java derleyicisi değişirse ona bağlı her yapı yeniden oluşturulur.

Bazel, derleme yapılandırmaları ayarlayarak sorunun ikinci bölümünü (platform bağımsızlığı) çö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 derleme araçları
  • Hedef yapılandırma: Son olarak istediğiniz ikili dosyayı derleme

Derleme sistemini genişletme

Bazel, birçok popüler programlama dili için hazır hedefler sunar ancak mühendisler her zaman daha fazlasını yapmak ister. Görev tabanlı sistemlerin avantajlarından biri, her türlü derleme sürecini destekleme esnekliğidir ve yapıya dayalı bir derleme sisteminde bu avantajdan vazgeçmemek daha iyi olur. Neyse ki Bazel, özel kurallar ekleyerek desteklenen hedef türlerinin 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 biçiminde) ve kuralın oluşturduğu sabit çıkış grubunu belirtir. Yazar, bu kural tarafından oluşturulacak işlemleri de tanımlar. Her işlem giriş ve çıkışlarını belirtir, belirli bir yürütülebilir dosyayı çalıştırır veya belirli bir dizeyi bir dosyaya yazar. Ayrıca giriş ve çı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 beyan edilen giriş ve çıkışlarını kullandığı sürece istediğini yapabilir. Bazel, işlemleri planlama ve sonuçlarını uygun şekilde önbelleğe alma işlemlerini üstlenir.

Bir işlem geliştiricisinin işleminin bir parçası olarak rastgele olmayan bir işlem başlatması gibi işlemleri yapmasının önüne geçemeyeceği için sistem hatasız değildir. Ancak bu durum pratikte çok sık yaşanmaz ve kötüye kullanım olasılıklarını işlem düzeyine kadar indirmek, hata olasılıklarını büyük ölçüde azaltır. Birçok yaygın dili ve aracı destekleyen kurallar internette yaygın olarak mevcuttur. Çoğu projenin kendi kurallarını tanımlaması gerekmez. Bu tür kurallar için bile kural tanımlarının yalnızca depoda tek bir merkezi yerde tanımlanması gerekir. Bu da çoğu mühendisin bu kuralları uygulama konusunda endişelenmeksizin kullanabileceği anlamına gelir.

Ortamı izole etme

İşlemler, diğer sistemlerdeki görevlerle aynı sorunlarla karşılaşabilir. Aynı dosyaya yazma işlemi yapan ve birbiriyle çakışan işlemler yazmak mümkün mü? Aslında Bazel, korumalı alan kullanarak bu çakışmaları imkansız hale getirir. Desteklenen sistemlerde her işlem, dosya sistemi korumalı alanı aracılığıyla diğer işlemlerden izole edilir. Her işlem, dosya sisteminin yalnızca beyan ettiği girişleri ve oluşturduğu çıkışları içeren kısıtlanmış bir görünümünü görebilir. Bu, Linux'daki LXC gibi sistemler tarafından zorunlu kılınmaktadır. LXC, Docker'ın temelindeki teknolojiyle aynıdır. Bu, beyan etmedikleri dosyaları okuyamadıkları için işlemlerin birbiriyle çakışmasının mümkün olmadığı anlamına gelir. Beyan etmedikleri ancak yazdıkları dosyalar, işlem sona erdiğinde silinir. Bazel, işlemlerin ağ üzerinden iletişim kurmasını kısıtlamak için korumalı alanlardan da yararlanır.

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

Ancak hâlâ çözülmemiş bir sorun var: Derleme sistemlerinin, bağımlılıkları (araç veya kitaplık fark etmeksizin) doğrudan derlemek yerine genellikle harici kaynaklardan indirmesi gerekir. Bu durum, 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ğımlı olmak risklidir. Bu dosyalar dilediğiniz zaman değişebilir. Bu da derleme sisteminin, dosyaları sürekli olarak güncel olup olmadığını kontrol etmesini gerektirebilir. Uzak bir dosya, Workspace kaynak kodunda karşılık gelen bir değişiklik yapılmadan değiştirilirse yeniden üretilemeyen derlemelere de neden olabilir. Bir derleme, fark edilmeyen bir bağımlılık değişikliği nedeniyle bir gün çalışırken ertesi gün belirgin bir neden olmadan başarısız olabilir. Son olarak, harici bağımlılık üçüncü tarafa ait olduğunda büyük bir güvenlik riski oluşturabilir: Saldırganlar bu üçüncü taraf sunucusuna sızabilirse bağımlılık dosyasını kendi tasarımlarıyla değiştirebilir. Bu da, derleme ortamınız ve çıkışınız üzerinde tam kontrol sahibi olmalarını sağlayabilir.

Temel sorun, derleme sisteminin bu dosyaları kaynak denetimine eklemek zorunda kalmadan bu dosyalardan haberdar olmasını istememizdir. Bir bağımlılığı güncellemek bilinçli bir seçim olmalıdır ancak bu seçim, mühendisler tarafından tek tek veya sistem tarafından otomatik olarak yönetilmek yerine merkezi bir yerde bir kez yapılmalıdır. Bunun nedeni, "Baştaki canlı" modelinde bile derlemelerin deterministik olmasını istememizdir. Bu, geçen haftaki bir taahhütte, bağımlılıklarınızı şu andaki halleri yerine o zamanki halleriyle göreceğiniz anlamına gelir.

Bazel ve diğer bazı derleme sistemleri, Workspace'teki her harici bağımlılık için şifrelenmiş karma listelemesi gereken Workspace genelinde bir manifest dosyası zorunlu kılarak bu sorunu giderir. Karma, dosyanın tamamını kaynak denetiminde kontrol etmeden dosyayı benzersiz bir şekilde temsil etmenin kısa bir yoludur. Bir çalışma alanından yeni bir harici bağımlılık referans verildiğinde, bu bağımlığın karması manuel olarak veya otomatik olarak manifest dosyasına eklenir. Bazel bir derleme çalıştırdığında, önbelleğe alınmış bağımlılığının gerçek karma değerini manifest dosyasında tanımlanan beklenen karma değeriyle karşılaştırır ve yalnızca karma değer farklıysa dosyayı 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 derleme işleminin yeni bağımlılığı kabul etmesi için bu değişikliğin onaylanması ve kaynak denetimine kaydedilmesi gerekir. Bu, bir bağımlığın ne zaman güncellendiğine dair her zaman bir kayıt bulunduğu ve harici bir bağımlığın, çalışma alanı kaynağında ilgili bir değişiklik yapılmadan değişemeyeceği anlamına gelir. Ayrıca, kaynak kodunun eski bir sürümünü kontrol ederken derlemenin, söz konusu sürümün kontrol edildiği noktada kullandığı bağımlılıkları kullanacağı garanti edilir (aksi takdirde, bu bağımlılıklar artık mevcut değilse derleme başarısız olur).

Elbette, uzak bir sunucu kullanılamaz hale gelirse veya bozuk veriler sunmaya başlarsa bu yine de sorun olabilir. Bu durumda, söz konusu bağımlılığın başka bir kopyasına sahip değilseniz tüm derlemeleriniz başarısız olmaya başlayabilir. Bu sorunu önlemek için, önemsiz olmayan tüm projelerde bağımlılıkların tamamını güvendiğiniz ve kontrol ettiğiniz sunuculara veya hizmetlere yansıtmanızı öneririz. Aksi takdirde, kontrol edilen karma oluşturma işlemleri güvenliğini garanti etse bile derleme sisteminizin kullanılabilirliği için her zaman üçüncü tarafların insafına kalmış olursunuz.