Yapı Tabanlı Derleme Sistemleri

Bu sayfada, yapı tabanlı derleme sistemleri ve bu sistemlerin ardındaki felsefe ele alınmaktadır. Bazel, yapı tabanlı bir derleme sistemidir. Göreve dayalı derleme sistemleri, komut dosyalarını derlemeye kıyasla iyi bir adımdır. Ancak mühendislere kendi görevlerini tanımlamalarına izin vererek çok fazla güç sağlarlar.

Yapı tabanlı derleme sistemlerinde, sistem tarafından tanımlanan az sayıda görev bulunur ve bu görevler mühendisler tarafından sınırlı bir şekilde yapılandırılabilir. Mühendisler yine de sisteme nenin derleneceğini söyler ancak nasıl oluşturulacağını derleme sistemi belirler. Görev tabanlı derleme sistemlerinde olduğu gibi Bazel gibi yapı tabanlı derleme sistemlerinin de derleme dosyaları vardır ancak bu derleme dosyalarının içeriği çok farklıdır. Bazel'deki derleme dosyaları, nasıl çıktı üretileceğini açıklayan Turing -complete kodlama dilinde zorunlu bir komut dizisi olmaktan çok, 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 bildirimsel bir manifesttir. Mühendisler komut satırında bazel aracını çalıştırdıklarında oluşturulacak bir dizi hedef belirtir (ne) ve derleme adımlarını yapılandırmak, çalıştırmak ve planlamaktan (nasıl) Bazel sorumludur. Derleme sistemi artık hangi araçların ne zaman çalıştırılacağı üzerinde tam kontrole sahip olduğu için doğruluğu garanti ederken çok daha verimli olmasını sağlayan çok daha güçlü garantiler verebilir.

İşlevsel bir bakış açısı

Yapı tabanlı derleme sistemleriyle işlevsel programlama arasında kolayca bir karşılaştırma yapabilirsiniz. Görev tabanlı derleme sistemlerinin, programcıların yürütülecek bir dizi adım tanımlamasına olanak tanıması gibi, geleneksel zorunluluk programlama dilleri (Java, C ve Python gibi), birbiri ardına yürütülecek ifade listelerini belirtir. İşlevsel programlama dilleri (ör. Haskell ve ML) ise daha çok bir dizi matematiksel denkleme benzer. İşlevsel dillerde, programcı gerçekleştirilecek bir hesaplamayı açıklar ancak bu hesaplamanın ne zaman ve tam olarak derleyiciye nasıl yürütüldüğüyle ilgili ayrıntıları bırakır.

Bu, yapı tabanlı bir derleme sisteminde manifest beyan edilmesi ve sistemin, derlemeyi nasıl yürüteceğini belirlemesine izin verme fikriyle aynıdır. Pek çok problem, işlevsel programlama kullanılarak kolayca ifade edilemez, ancak bundan büyük fayda sağlayanlar: Bu dil, çoğu zaman bu tür programları önemsiz bir şekilde paralel hale getirebiliyor ve zorunlu bir dilde, doğrulukları konusunda güçlü garantiler verebiliyor. İşlevsel programlama kullanarak ifade edilmesi en kolay problemler, bir dizi kural veya işlev kullanarak bir veri parçasını diğerine dönüştürmeyi içeren sorunlardır. Derleme sistemi tam da budur: Tüm sistem, kaynak dosyaları (ve derleyici gibi araçları) girdi olarak alan ve çıktı olarak ikili programlar üreten, etkin bir şekilde matematiksel bir işlevdir. Dolayısıyla, bir derleme sistemini işlevsel programlamanın temelleri üzerine inşa etmek için başarılı olması şaşırtıcı değildir.

Yapı tabanlı derleme sistemlerini anlama

Google'ın derleme sistemi Blaze, ilk yapı tabanlı 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. Burada iki hedef türü şunlardır: java_binary ve java_library. 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, kitaplık hedefleri ise ikili programlar veya diğer kitaplıklar tarafından kullanılabilen kitaplıklar üretir. Her hedef şunları içerir:

  • name: Hedefe komut satırında ve diğer hedeflerde nasıl referans verildiği
  • srcs: hedef için yapı oluşturmak üzere derlenecek kaynak dosyalar
  • deps: Bu hedeften önce oluşturulması ve buna bağlanması gereken diğer hedefler

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

Göreve dayalı 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 çalıştırırsınız. 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. MyBinary öğesinin geçişli bağımlılıklarını (yani, MyBinary hizmetinin bağımlı olduğu her hedef ve bu hedeflerin bağımlı olduğu her hedef) yinelemeli olarak belirlemek için grafiği kullanır.
  3. Bu bağımlılıkların her birini sırayla oluşturur. Bazel, işe başka bağımlılıkları olmayan her bir hedefi oluşturarak 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şturulur oluşturulmaz 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 oluşturulan tüm bağımlılıklara bağlantı veren nihai bir yürütülebilir ikili program oluşturmak için MyBinary oluşturur.

Temelde, burada olan durum göreve dayalı bir derleme sistemi kullanılırken olanlardan çok farklı görünmeyebilir. Gerçekten de, sonuç aynı ikili programdır ve bu ikiliyi oluşturma süreci, aralarındaki bağımlılıkları bulmak için bir dizi adımı analiz etmeyi ve ardından bu adımları sırayla yürütmeyi gerektirmektedir. Ancak aralarında çok önemli farklar var. İlki 3. adımda görünür: Bazel her hedefin yalnızca bir Java kitaplığı ürettiğini bildiğinden, yapması gereken tek şeyin rastgele bir kullanıcı tanımlı komut dosyası yerine Java derleyiciyi çalıştırmak olduğunu bilir ve dolayısıyla 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 büyük bir performans iyileştirmesi sağlayabilir. Bunun tek nedeni, yapay zekalı yaklaşımın derleme sistemini kendi yürütme stratejisinden sorumlu tutması ve böylece paralellikle ilgili daha güçlü garantiler vermesidir.

Bununla birlikte, avantajlar, paralellikten ibaret değildir. Bu yaklaşımın bize kazandırdığı bir sonraki şey, geliştirici herhangi bir değişiklik yapmadan ikinci kez bazel build :MyBinary yazdığında: Bazel bir saniyeden daha kısa bir süre içinde hedefin güncel olduğunu belirten bir mesajla çıkar. Bu, daha önce bahsettiğimiz işlevsel programlama paradigması sayesinde mümkün. Bazel her hedefin sadece bir Java derleyicisi çalıştırmanın sonucu olduğunu ve Java derleyicisinden gelen çıkışın sadece girişlere bağlı olduğunu, yani girişler değişmediği sürece çıkışın yeniden kullanılabileceğini bilmektedir. Bu analiz her düzeyde işe yarar. MyBinary.java değişirse Bazel MyBinary öğesini yeniden derleyip mylib değerini yeniden kullanmayı bilir. //java/com/example/common için bir kaynak dosya değişirse Bazel, mylib ve MyBinary kitaplığını yeniden oluşturabileceğini ancak //java/com/example/myproduct/otherlib'i yeniden kullanacağını 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şturabiliyor ve eski derlemeler üretmeyeceğini garanti edebiliyor.

Derleme sürecini görevler yerine eserler açısından yeniden çerçevelemek zor olsa da etkilidir. Derleme sistemi, programcıya açık olan esnekliği azaltarak derlemenin her adımında neler yapıldığı hakkında daha fazla bilgi edinebilir. Derleme işlemlerini paralel hale getirerek ve çıktılarını yeniden kullanarak derlemeyi çok daha verimli hale getirmek için bu bilgileri kullanabilir. Ancak bu sadece ilk adım. 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 Bazel numaraları

Yapı tabanlı derleme sistemleri, göreve dayalı derleme sistemlerinde mevcut olan benzerlik ve yeniden kullanım sorunlarını temelde çözer. Ancak daha önce bahsettiğimiz, henüz ele almadığımız birkaç sorun var. Bazel'in bunların her birini çözmek için zekice yolları var. Devam etmeden önce bunları tartışmalıyız.

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

Daha önce karşılaştığımız sorunlardan biri, derlemelerin makinemizde yüklü araçlara bağlı olmasıydı. Ayrıca, derlemeleri sistemler genelinde yeniden üretmek, farklı araç sürümleri veya konumları nedeniyle zor olabiliyordu. Projenizde, hangi platform üzerinde geliştirildiğine veya derlendiğine bağlı olarak (Windows veya Linux gibi) farklı araçlar gerektiren diller kullandığında ve bu platformların her birinde aynı işi yapmak için biraz farklı araç setlerinin kullanılması gerektiğinde sorun daha da zorlaşır.

Bazel, araçları her bir hedefin bağımlılıkları olarak ele alarak bu sorunun ilk kısmını çözer. Çalışma alanındaki her java_library dolaylı olarak bir Java derleyiciye bağlıdır. Bu derleyici, varsayılan olarak iyi bilinen bir derleyiciyi oluşturur. Bazel her java_library derlemesinde belirtilen derleyicinin bilinen bir konumda kullanılabilir olup olmadığı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, derleme yapılandırmaları belirleyerek sorunun ikinci bölümü olan platform bağımsızlığını çözüyor. Hedeflemeler, doğrudan araçlara bağlı olarak çalışmak 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, birçok popüler programlama dili için kullanıma hazır hedefler sunar. Ancak mühendisler her zaman daha fazlasını yapmak isteyeceklerdir. Görev tabanlı sistemlerin avantajlarından biri, her tür derleme işlemini destekleyebilme esnekliği sunmasıdır ve yapı tabanlı bir derleme sisteminde bu hedeften vazgeçmemek daha iyi olur. 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 ürettiği sabit çıktı kümesini bildirir. Yazar, bu kural tarafından oluşturulacak işlemleri de tanımlar. Her işlem kendi girdilerini ve çıkışlarını tanımlar, belirli bir yürütülebilir dosyayı çalıştırır veya bir dosyaya belirli bir dize yazar, girişleri ve çıkışları aracılığıyla diğer işlemlere bağlanabilir. Bu, işlemlerin, derleme sistemindeki en düşük seviyeli birleştirilebilir birim olduğu anlamına gelir. Bir işlem, yalnızca bildirilen girişleri ve çıkışları kullandığı sürece istediği her şeyi yapabilir ve Bazel işlemleri planlamayı ve sonuçlarını önbelleğe almayı uygun şekilde halleder.

Bir aksiyon geliştiricisinin, eyleminin parçası olarak belirli bir deterministik olmayan süreç sunmak gibi bir şey yapmasını engellemenin yolu olmadığından sistem kusursuz değildir. Ancak bu durum, pratikte çok sık yaşanmaz. Kötüye kullanım olasılığını işlem düzeyine kadar itmek, hata olasılığını önemli ölçüde azaltır. Pek çok yaygın dili ve aracı destekleyen kurallar internette yaygın olarak bulunmaktadır ve çoğu projenin kendi kurallarını tanımlaması gerekmez. Bunu yapanlar için bile kural tanımlarının kod deposunda yalnızca tek bir merkezi yerde tanımlanması gerekir. Diğer bir deyişle, ç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şmış gibi duruyormuş gibi görünüyor. Hem aynı dosyaya yazan hem de birbiriyle çelişen 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. Etkin olarak her işlem, dosya sisteminin yalnızca bildirdiği girişleri ve ürettiği çıkışları içeren kısıtlı bir görünümünü görebilir. Bu, Docker'ın arkasındaki teknoloji olan Linux'ta LXC gibi sistemler tarafından uygulanır. Bu, eylemlerin bildirmedikleri dosyaları okuyamayacakları için birbiriyle çelişmesi imkansız olduğu ve yazdıkları ancak bildirmedikleri tüm 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 korumalı alanları da kullanır.

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

Hâlâ bir sorun vardır: Derleme sistemlerinin genellikle bağımlılıkları (araçlar veya kitaplıklar) doğrudan oluşturmak yerine dış kaynaklardan indirmesi gerekir. Bu, örnekte Maven'den bir 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 riskli olabilir. Bu dosyalar herhangi bir zamanda değişebileceğinden derleme sisteminin güncel olup olmadıklarını sürekli olarak kontrol etmesi gerekebilir. Çalışma alanı kaynak kodunda karşılık gelen bir değişiklik olmadan uzak bir dosya değişirse, bu durum yeniden oluşturulamayan derlemelere de yol açabilir. Bir derleme, bir gün çalışabilir ancak bir sonraki gün, fark edilmeyen bir bağımlılık değişikliği nedeniyle belirgin bir neden olmaksızı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 riski oluşturabilir: Bir saldırgan, bu üçüncü taraf sunucusuna sızabilirse bağımlılık dosyasını kendi tasarımı olan bir şeyle değiştirebilir ve böylece derleme ortamınız ve çıktısı üzerinde tam kontrol sahibi olabilir.

Temel sorun, derleme sisteminin bu dosyaları kaynak kontrolünde kontrol etmek zorunda kalmadan bilmesini istememizdir. Bağımlılığı güncellemek bilinçli bir seçim olmalıdır ancak bu seçim, tek tek mühendisler tarafından veya sistem tarafından otomatik olarak yönetmek yerine merkezi bir yerden yapılmalıdır. Çünkü "Başta Canlı" modelinde bile derlemelerin belirleyici olmasını isteriz. Bu da geçen haftaya ait bir taahhüdü kontrol ederseniz bağımlılıklarınızı şu anda olduğu gibi değil, olduğu 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 şifreleme karması listeleyen çalışma alanı genelinde bir manifest dosyası gerektirerek bu sorunu çözer. Karma, dosyanın tamamını kaynak kontrolüne geçirmeden dosyayı benzersiz bir şekilde temsil etmenin kısa ve öz bir yoludur. Çalışma alanından yeni bir dış bağımlılıkya referans verildiğinde, söz konusu bağımlılığın karması manuel veya otomatik olarak manifeste eklenir. Bazel bir derleme çalıştırdığında, önbelleğe alınan bağımlılığının gerçek karmasını, manifest'te tanımlanan beklenen karmayla karşılaştırarak kontrol eder ve dosyayı yalnızca karma değer farklı olduğunda yeniden indirir.

İndirdiğimiz yapı, manifest'te açıklanandan farklı bir karmaya sahipse 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 önce bu değişikliğin onaylanması ve kaynak kontrolünde kontrol edilmesi gerekir. Bu, her zaman bir bağımlılığın ne zaman güncellendiğinin bir kaydının olduğu ve çalışma alanı kaynağında karşılık gelen bir değişiklik olmadan harici bağımlılığın değiştirilemeyeceği anlamına gelir. Bu aynı zamanda, kaynak kodun eski bir sürümünü kontrol ederken, derlemenin bu sürümün kontrol edildiği noktada kullanmakta olduğu bağımlılıkları kullanması garanti edildiği (aksi takdirde, bu bağımlılıklar artık mevcut değilse başarısız olacağı) anlamına da gelir.

Uzak bir sunucunun kullanılamaz hale gelmesi veya bozuk veri sunmaya başlaması, söz konusu bağımlılığın başka bir kopyasına sahip değilseniz tüm derlemelerinizin başarısız olmaya başlamasına yol açabilir. Böyle bir sorunla karşılaşmamak için, önemli olmayan projelerde projenin tüm bağımlılıklarını güvendiğiniz ve denetlediğiniz sunuculara veya hizmetlere yansıtmanızı öneririz. Aksi takdirde, check-in yapılan karmalar güvenliği garanti etse bile derleme sisteminizin kullanılabilirliği konusunda her zaman üçüncü tarafın eline geçersiniz.