Yapı Tabanlı Derleme Sistemleri

Sorun bildirme Kaynağı görüntüleme Nightly · 7.4 .

Bu sayfada, yapı tabanlı derleme sistemleri ve bunların oluşturulmasının arkası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ühendislere kendi görevlerini tanımlamalarına izin vererek ç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 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ı 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 çıktını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, derlemek için bir dizi hedef (ne) belirtir ve Bazel, derleme adımlarını (nasıl) yapılandırmak, çalıştırmak 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 bir dizi yürütme adımı tanımlamasına olanak tanıdığı gibi 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. 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 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 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.

Öğe 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ı (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 ü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 (ö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. 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 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 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.

Temel olarak, burada gerçekleşen sürecin görev tabanlı bir derleme sistemi kullanıldığında 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 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 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 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ğ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 şık Bazel numaraları

Öğe tabanlı derleme sistemleri, temel olarak görev tabanlı derleme sistemlerine özgü paralellik ve yeniden kullanım sorunlarını çözer. Ama hâlâ daha önce ortaya çıkan ve ele alamadığı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 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. Projeniz, üzerinde derlendiği veya derlendiği platforma bağlı olarak farklı araçlar gerektiren diller kullandığında (Windows ve Linux gibi) ve bu platformların her biri aynı işi yapmak için biraz farklı araç setleri 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 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 kısmı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'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.

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; 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 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. 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

İş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, bir dosya sistemi korumalı alanı aracılığıyla diğer tüm işlemlerden izole edilir. Her işlem, dosya sisteminin yalnızca beyan ettiği girişleri ve ürettiği çı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. 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 korumalı alanlar da kullanı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 ö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 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 oluşturulamayan 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, 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. 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, ç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, 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ığ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ığı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).

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, 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.