Bazel kod tabanı

Sorun bildir Kaynağı göster

Bu belgede, kod tabanı ve Bazel'ın nasıl yapılandırıldığı açıklanmaktadır. Son kullanıcılar için değil, Bazel'e katkıda bulunmak isteyen kişiler için tasarlanmıştır.

Giriş

Bazel'in kod tabanı büyüktür (yaklaşık 350KLOC üretim kodu ve yaklaşık 260 KLOC test kodu) ve bu manzaranın tamamına kimse aşina değildir: Herkes kendi vadisini çok iyi bilir, ama her yönden tepelerde neler olduğunu çok az kişi bilir.

Yolculuğun ortasında, kaybolan dolambaçlı ormanlarda karanlıkta kalmayı başaramayan bu belge, kod tabanına genel bir bakış sunarak üzerinde çalışmaya başlamayı kolaylaştırmaya çalışıyor.

Bazel kaynak kodunun herkese açık sürümü, github.com/bazelbuild/bazel adresindeki GitHub'da yer almaktadır. Bu "bilgi kaynağı" değildir. Google dışında yararlı olmayan ek işlevler içeren Google dahili kaynak ağacından türetilmiştir. Uzun vadeli hedef, GitHub'ı doğru veri kaynağı haline getirmektir.

Katkılar, normal GitHub pull isteği mekanizması aracılığıyla kabul edilir, bir Google çalışanı tarafından dahili kaynak ağacına manuel olarak aktarıldıktan sonra tekrar GitHub'a aktarılır.

İstemci/sunucu mimarisi

Bazel'in büyük bir kısmı, derlemeler arasında RAM'de kalan bir sunucu işleminde bulunur. Bu, Bazel'ın yapılar arasında durumu korumasına olanak tanır.

Bu nedenle Bazel komut satırında iki tür seçenek bulunur: başlatma ve komut. Aşağıdaki gibi bir komut satırında:

    bazel --host_jvm_args=-Xmx8G build -c opt //foo:bar

Bazı seçenekler (--host_jvm_args=) çalıştırılacak komutun adından önce, bazıları ise (-c opt) sonrasında yer alır. İlk tür "başlangıç seçeneği" olarak adlandırılır ve sunucu işlemini bir bütün olarak etkiler. İkinci tür "komut seçeneği" ise yalnızca tek bir komutu etkiler.

Her sunucu örneğinin tek bir ilişkilendirilmiş çalışma alanı ("depolar" olarak bilinen kaynak ağaçları koleksiyonu) ve her çalışma alanının genellikle tek bir etkin sunucu örneği vardır. Bu, özel bir çıkış tabanı belirterek engellenebilir (daha fazla bilgi için "Dizin düzeni" bölümüne bakın).

Bazel, aynı zamanda geçerli bir .zip dosyası olan tek bir ELF yürütülebilir dosyası olarak dağıtılır. bazel yazdığınızda, C++'ta uygulanan yukarıdaki ELF yürütülebilir dosyası ("istemci") kontrolü alır. Aşağıdaki adımları uygulayarak uygun bir sunucu işlemi oluşturur:

  1. Önceden kendisini çıkarıp çıkarmadığını kontrol eder. Aksi halde, bu işlevi görür. Bu, sunucunun uygulanma işleminin geldiği noktadır.
  2. Çalışan bir etkin sunucu örneğinin olup olmadığını kontrol eder: Örnek çalışıyor, doğru başlangıç seçeneklerine sahip ve doğru çalışma alanı dizinini kullanıyor. Çalışan sunucuyu, sunucunun dinlediği bağlantı noktasını içeren bir kilit dosyasının bulunduğu $OUTPUT_BASE/server dizinine bakarak bulur.
  3. Gerekirse eski sunucu işlemini sonlandırır
  4. Gerekirse yeni bir sunucu işlemi başlatır

Uygun bir sunucu işlemi hazır olduğunda, çalıştırılması gereken komut gRPC arayüzü üzerinden kendisine iletilir, ardından Bazel çıktısı tekrar terminale aktarılır. Aynı anda yalnızca bir komut çalışabilir. Bu, bölümleri C++ ve Java'daki bölümleriyle ayrıntılı bir kilitleme mekanizması kullanılarak gerçekleştirilir. bazel version komutunun başka bir komutla paralel olarak çalıştırılamaması utanç verici olduğundan birden fazla komutu paralel olarak çalıştırmak için bazı altyapılar mevcuttur. Ana engel, BlazeModule döngülerinin yaşam döngüsüdür ve BlazeRuntime bölgesindeki bazı durumlardır.

Bir komutun sonunda, Bazel sunucusu istemcinin döndürmesi gereken çıkış kodunu iletir. İşin ilginç bir yanı, bazel run işlevinin uygulanmasıdır: Bu komutun işi Bazel'ın yeni oluşturduğu bir şeyi çalıştırmaktır, ancak terminali olmadığı için sunucu işleminden bunu yapamaz. Bunun yerine, istemciye hangi ikili programın ujexec() ve hangi bağımsız değişkenlerle ujexec() işlemi yapması gerektiğini söyler.

Ctrl-C tuşlarına basıldığında istemci, bunu gRPC bağlantısında bir İptal çağrısına çevirir ve bu çağrı, komutu mümkün olan en kısa sürede sonlandırmaya çalışır. Üçüncü Ctrl-C tuşlarına geldikten sonra istemci, sunucuya bir SIGKILL gönderir.

İstemcinin kaynak kodu src/main/cpp altında ve sunucuyla iletişim kurmak için kullanılan protokol src/main/protobuf/command_server.proto içindedir .

Sunucunun ana giriş noktası BlazeRuntime.main()'tir ve istemciden gelen gRPC çağrıları GrpcServerImpl.run() tarafından işlenir.

Dizin düzeni

Bazel, bir derleme sırasında biraz karmaşık dizinler oluşturur. Çıkış dizini düzeninde tam bir açıklama mevcuttur.

"Ana depo", Bazel'ın çalıştırıldığı kaynak ağaçtır. Bu genellikle kaynak kontrolünden kontrol ettiğiniz bir şeye karşılık gelir. Bu dizinin kökü "workspace root" olarak bilinir.

Bazel, tüm verilerini "çıktı kullanıcı kökü" altına yerleştiriyor. Bu genellikle $HOME/.cache/bazel/_bazel_${USER} olur ancak --output_user_root başlatma seçeneği kullanılarak geçersiz kılınabilir.

"Yükleme tabanı", Bazel'in ayıklandığı yerdir. Bu işlem otomatik olarak yapılır ve her Bazel sürümü, yükleme tabanının altındaki sağlama sayısına göre bir alt dizin alır. Varsayılan olarak $OUTPUT_USER_ROOT/install biçimindedir ve --install_base komut satırı seçeneği kullanılarak değiştirilebilir.

"Çıkış tabanı", belirli bir çalışma alanına ekli Bazel örneğinin yazma işlemi yaptığı yerdir. Her çıkış tabanında, aynı anda çalışan en fazla bir Bazel sunucu örneği bulunur. Genelde saat $OUTPUT_USER_ROOT/<checksum of the path to the workspace>. --output_base başlatma seçeneği kullanılarak değiştirilebilir. Bu seçenek, diğer unsurların yanı sıra, herhangi bir çalışma alanında belirli bir zamanda yalnızca bir Bazel örneğinin çalıştırılması sınırını aşmak için kullanışlıdır.

Çıkış dizini, diğer özelliklerinin yanı sıra şunları içerir:

  • $OUTPUT_BASE/external itibarıyla getirilen harici depolar.
  • Geçerli derleme için tüm kaynak kodunun sembolik bağlantılarını içeren bir dizin olan exec kökü. Konumu: $OUTPUT_BASE/execroot. Derleme sırasında çalışma dizini $EXECROOT/<name of main repository> şeklindedir. Çok uyumsuz bir değişiklik olduğu için bu, uzun vadeli bir plan olsa da $EXECROOT olarak değiştirmeyi planlıyoruz.
  • Derleme sırasında oluşturulan dosyalar.

Bir komut çalıştırma süreci

Bazel sunucusu kontrolü ele geçirip yürütmesi gereken bir komut hakkında bilgilendirildiğinde, aşağıdaki olaylar dizisi gerçekleşir:

  1. BlazeCommandDispatcher yeni istek hakkında bilgilendirildi. Komutun, çalışması için bir çalışma alanı gerekip gerekmediğine (sürüm veya yardım gibi kaynak koduyla alakalı olmayan komutlar dışında hemen hemen her komuta) ve başka bir komutun çalışıp çalışmadığına karar verir.

  2. Doğru komut bulundu. Her komut, BlazeCommand arayüzünü uygulamalı ve @Command ek açıklamasına sahip olmalıdır (bu biraz antipattern’dir. Bir komutun ihtiyaç duyduğu tüm meta verilerin BlazeCommand üzerinde yöntemler tarafından açıklanması güzel olur).

  3. Komut satırı seçenekleri ayrıştırılır. Her komut, @Command ek açıklamasında açıklanan farklı komut satırı seçeneklerine sahiptir.

  4. Bir etkinlik veri yolu oluşturulur. Etkinlik veri yolu, derleme sırasında gerçekleşen etkinliklerin akışıdır. Bunlardan bazıları, derlemenin nasıl ilerlediğini dünyaya duyurmak için Derleme Etkinlik Protokolü desteği kapsamında Bazel'in dışına aktarılır.

  5. Kontrolü komut alır. En ilginç komutlar bir derleme çalıştıran komutlardır: derleme, test etme, çalıştırma, kapsam vb.: Bu işlev BuildTool tarafından uygulanır.

  6. Komut satırındaki hedef kalıpları grubu ayrıştırılır ve //pkg:all, //pkg/... gibi joker karakterler çözümlenir. Bu, AnalysisPhaseRunner.evaluateTargetPatterns() uygulamasında uygulanır ve Skyframe'de TargetPatternPhaseValue olarak yeniden ayarlanır.

  7. Yükleme/analiz aşaması, eylem grafiğini (derleme için yürütülmesi gereken komutların yönlendirilmiş eşzamansız grafiği) oluşturmak için çalıştırılır.

  8. Yürütme aşaması çalıştırılır. Bu, istenen üst düzey hedefleri oluşturmak için gereken her işlemin yürütülmesi anlamına gelir.

Komut satırı seçenekleri

Bazel çağrısı için komut satırı seçenekleri OptionsParsingResult nesnesinde açıklanmıştır. Bu nesnede, "seçenek sınıfları"ndan seçeneklerin değerlerine kadar bir harita bulunur. "Seçenek sınıfı", OptionsBase öğesinin bir alt sınıfıdır ve birbirleriyle ilişkili komut satırı seçeneklerini gruplandırır. Örneğin:

  1. Bir programlama diliyle (CppOptions veya JavaOptions) ilgili seçeneklerdir. Bunlar FragmentOptions alt sınıfı olmalıdır ve sonunda bir BuildOptions nesnesine sarmalanır.
  2. Bazel'ın işlemleri yürütme şekliyle ilgili seçenekler (ExecutionOptions)

Bu seçenekler analiz aşamasında ve (Java'da RuleContext.getFragment() veya Starlark'ta ctx.fragments aracılığıyla) kullanılmak üzere tasarlanmıştır. Bunlardan bazıları (örneğin, C++'nın taramanın dahil edilip edilmeyeceğinin dikkate alınıp alınmayacağı) yürütme aşamasında okunur, ancak BuildConfiguration o anda kullanılamadığı için açıkça tesisat kurulumu gerekir. Daha fazla bilgi için "Yapılandırmalar" bölümüne bakın.

UYARI: OptionsBase örneklerinin sabit olduğunu varsaymak ve bunları bu şekilde kullanmak istiyoruz (SkyKeys'in bir parçası gibi). Bu durum geçerli değildir ve bunların değiştirilmesi, Bazel'ı hata ayıklaması zor, fark edilmeyecek şekilde bozmanın gerçekten iyi bir yoludur. Maalesef bu araçların gerçekten sabit olmasını sağlamak büyük bir girişim. (İnşaattan hemen sonra ve herkes buna referansta bulunma şansı elde etmeden ve equals() veya hashCode() çağrılmadan önce bir FragmentOptions üzerinde değişiklik yapılması herhangi bir sorun teşkil etmez.)

Bazel, seçenek sınıflarını aşağıdaki şekillerde öğrenir:

  1. Bazıları Bazel'e (CommonCommandOptions) bağlı olarak çalışır
  2. Her Bazel komutundaki @Command ek açıklamadan
  3. ConfiguredRuleClassProvider dilinden (bunlar, her bir programlama diliyle ilgili komut satırı seçenekleridir)
  4. Starlark kuralları kendi seçeneklerini de tanımlayabilir (buraya göz atın)

Her seçenek (Starlark tarafından tanımlanan seçenekler hariç), bir yardım metniyle birlikte komut satırı seçeneğinin adını ve türünü belirten @Option ek açıklamasına sahip FragmentOptions alt sınıfının üye değişkenidir.

Komut satırı seçeneği değerinin Java türü genellikle basit bir değerdir (dize, tam sayı, Boole, etiket vb.). Bununla birlikte, daha karmaşık tür seçeneklerini de destekliyoruz. Bu örnekte, komut satırı dizesinden veri türüne dönüştürme işi com.google.devtools.common.options.Converter uygulamasına denk gelir.

Bazel'in gördüğü kaynak ağaç

Bazel, kaynak kodu okuyup yorumlayarak gerçekleşen yazılım geliştirme işindedir. Bazel'in üzerinde çalıştığı kaynak kodunun toplamı "çalışma alanı" olarak adlandırılır ve depolar, paketler ve kurallar şeklinde yapılandırılır.

Depolar

"Depo", geliştiricinin üzerinde çalıştığı bir kaynak ağacıdır. Genellikle tek bir projeyi temsil eder. Bazel'ın atası Blaze, bir monorepo üzerinde, yani derlemeyi çalıştırmak için kullanılan tüm kaynak kodlarını içeren tek bir kaynak ağacı üzerinde çalışıyordu. Öte yandan Bazel, kaynak kodu birden fazla depoyu kapsayan projeleri destekler. Bazel'in çağrıldığı depoya "ana depo", diğerlerine ise "harici depolar" denir.

Bir depo, kök dizininde kod deposu sınır dosyasıyla (MODULE.bazel, REPO.bazel veya eski bağlamlarda WORKSPACE ya da WORKSPACE.bazel) işaretlenir. Ana depo, Bazel'ı çağırdığınız kaynak ağaçtır. Harici depolar farklı şekillerde tanımlanır. Daha fazla bilgi için harici bağımlılıklara genel bakış sayfasını inceleyin.

Harici depoların kodu, $OUTPUT_BASE/external altında simüle edilmiş veya indirilmiş.

Derlemeyi çalıştırırken kaynak ağacının tamamının birleştirilmesi gerekir. Bu işlem SymlinkForest tarafından gerçekleştirilir. Bu işlem, ana depodaki her paketi $EXECROOT'a, her harici kod deposunu da $EXECROOT/external veya $EXECROOT/..'e sembolik olarak bağlar.

Paketler

Her depo paketlerden, ilgili dosyaların bir koleksiyonundan ve bağımlılıkların spesifikasyonundan oluşur. Bunlar, BUILD veya BUILD.bazel adlı bir dosyayla belirtilir. Her ikisi de varsa Bazel BUILD.bazel seçeneğini tercih eder. BUILD dosyalarının hâlâ kabul edilmesinin nedeni, Bazel'in üst öğesi Blaze'in bu dosya adını kullanmasıdır. Ancak, özellikle dosya adlarının büyük/küçük harfe duyarlı olmadığı Windows'da yaygın olarak kullanılan bir yol segmenti olduğu ortaya çıktı.

Paketler birbirinden bağımsızdır: Paketin BUILD dosyasında yapılan değişiklikler diğer paketlerin değişmesine neden olamaz. Yinelemeli glob'lar paket sınırlarında durduğundan ve dolayısıyla, BUILD dosyasının olması yinelenmeyi durduracağı için BUILD dosyalarının eklenmesi veya kaldırılması diğer paketleri _değiştirebilir.

BUILD dosyasının değerlendirilmesine "paket yükleniyor" denir. PackageFactory sınıfında uygulanır, Starlark çevirmenini çağırarak çalışır ve mevcut kural sınıfları kümesi hakkında bilgi gerektirir. Paket yükleme işleminin sonucunda Package nesnesi oluşur. Çoğunlukla bir dizeden (hedefin adı) hedefin kendisine giden bir haritadır.

Paket yükleme sırasında büyük bir karmaşıklık yığını, yer değiştirmedir: Bazel, her kaynak dosyanın açıkça listelenmesini gerektirmez. Bunun yerine, glob'ları (glob(["**/*.java"]) gibi) çalıştırabilir. Kabuktan farklı olarak, alt dizinlere inen (ancak alt paketlere değil) yinelemeli glob'ları destekler. Bu işlem, dosya sistemine erişim gerektirir ve yavaş olabildiği için dosyanın mümkün olduğunca paralel ve verimli bir şekilde çalışması için her türlü hileyi uygularız.

Globbing aşağıdaki sınıflarda uygulanır:

  • LegacyGlobber, Skyframe'in farkında olmayan hızlı ve keyifli bir küre
  • SkyframeHybridGlobber. Skyframe'in kullanıldığı ve "Skyframe yeniden başlatılmasından" kaçınmak için eski yerküreye geri dönen bir sürüm (aşağıda açıklanmıştır)

Package sınıfının kendisi, özel olarak "harici" paketi (harici bağımlılıklarla ilgili) ayrıştırmak için kullanılan ve gerçek paketler için bir anlam ifade etmeyen bazı üyeler içerir. Bu bir tasarım hatasıdır çünkü normal paketleri açıklayan nesnelerin başka bir şeyi tanımlayan alanlar içermemesi gerekir. Bunlardan bazıları:

  • Depo eşlemeleri
  • Tescilli araç zincirleri
  • Kayıtlı yürütme platformları

İdeal olarak, "harici" paketi normal paketlerin ayrıştırılmasından ayrıştırmak arasında daha fazla ayrım olması gerekir. Böylece, Package her ikisinin de ihtiyaçlarını karşılamak zorunda kalmaz. Bu ikisi iç içe geçmiş olduğundan maalesef bunu yapmak zor.

Etiketler, Hedefler ve Kurallar

Paketler aşağıdaki türlere sahip hedeflerden oluşur:

  1. Dosyalar: Derlemenin girdisi veya çıkışı olan şeyler. Bazel dilinde bunlara eserler diyoruz (başka yerde de anlatılmıştır). Derleme sırasında oluşturulan tüm dosyalar hedef değildir. Bir Bazel çıktısının ilişkili bir etiketinin olmaması yaygın bir durumdur.
  2. Kurallar: Bu kurallar, girişlerden çıkış elde etme adımlarını açıklar. Genellikle bir programlama diliyle (cc_library, java_library veya py_library gibi) ilişkilendirilir ancak dilden bağımsız bazı diller (genrule veya filegroup gibi) de vardır.
  3. Paket grupları: Görünürlük bölümünde ele alınmıştır.

Hedefin adına Etiket adı verilir. Etiketlerin söz dizimi @repo//pac/kage:name şeklindedir. Burada repo, Etiketin bulunduğu deponun adı, pac/kage, BUILD dosyasının bulunduğu dizindir ve name, paketin dizinine göreli dosyanın yoludur (etiket bir kaynak dosyaya işaret ediyorsa). Komut satırında bir hedefe atıfta bulunurken etiketin bazı bölümleri atlanabilir:

  1. Depo atlanırsa etiket ana depoya eklenmiş olarak kabul edilir.
  2. Paket bölümü atlanırsa (ör. name veya :name) etiketin geçerli çalışma dizininin paketinde yer aldığı kabul edilir (üst düzey referanslar (..) içeren göreli yollara izin verilmez)

Bir kural türüne (ör. "C++ kitaplığı") "kural sınıfı" adı verilir. Kural sınıfları, Starlark'ta (rule() işlevi) veya Java'da (diğer adıyla "yerel kurallar", RuleClass türünde) uygulanabilir. Uzun vadede, dile özgü her kural Starlark'ta uygulanacaktır, ancak bazı eski kural aileleri (Java veya C++ gibi) şu an için hâlâ Java'da olacaktır.

Starlark kural sınıflarının, BUILD dosyalarının başında load() ifadesi kullanılarak içe aktarılması gerekirken, Java kural sınıfları, ConfiguredRuleClassProvider ile kaydettirildiği için Bazel tarafından "doğuştan" bilinir.

Kural sınıfları aşağıdakilere benzer bilgiler içerir:

  1. Özellikleri (ör. srcs, deps): türleri, varsayılan değerleri, kısıtlamalar vb.
  2. Varsa her özelliğe eklenen yapılandırma geçişleri ve özellikler
  3. Kuralın uygulanması
  4. "Genellikle" kuralının oluşturduğu geçişli bilgi sağlayıcıları,

Terminoloji notu: Kod tabanında, bir kural sınıfı tarafından oluşturulan hedefi belirtmek için genellikle "Kural" ifadesini kullanırız. Ancak, Starlark'ta ve kullanıcıya yönelik dokümanlarda "Kural", yalnızca kural sınıfının kendisine atıfta bulunmak için kullanılmalıdır. Hedef, yalnızca bir "hedef"tir. Ayrıca RuleClass adında "class" bulunmasına rağmen, bir kural sınıfı ile bu türdeki hedefler arasında Java devralma ilişkisi olmadığını unutmayın.

Gökyüzü Çerçevesi

Bazel'in altında yatan değerlendirme çerçevesine Skyframe adı verilir. Bu yaklaşımın modeli, bir derleme sırasında oluşturulması gereken her şeyin, kenarları herhangi bir veri parçasından bağımlılıklarına, yani onu oluşturmak için bilinmesi gereken diğer veri parçalarına işaret eden, yönlendirilmiş eğimli bir grafik halinde düzenlenmesidir.

Grafikteki düğümler SkyValue, adları ise SkyKey olarak adlandırılır. Her ikisi de son derece sabittir, bunlardan yalnızca sabit nesnelere ulaşılabilir. Bu değişmeyen neredeyse her zaman geçerlidir. Gerçekleşmemesi durumunda (BuildConfigurationValue ve SkyKey üyesi olan BuildOptions bağımsız seçenek sınıfları için) bunları değiştirmemek veya yalnızca dışarıdan gözlemlenemeyen şekillerde değiştirmek için elimizden geleni yapıyoruz. Buradan yola çıkarak, Skyframe'de hesaplanan her şeyin (yapılandırılmış hedefler gibi) sabit olması gerekir.

Skyframe grafiğini gözlemlemenin en uygun yolu, grafikte her satırda bir SkyValue olacak şekilde döküm bırakan bazel dump --skyframe=deps çalıştırmaktır. Bu işlemi, oldukça büyük olabildiğinden küçük derlemeler için yapmak en iyisidir.

Skyframe, com.google.devtools.build.skyframe paketinde bulunur. Benzer şekilde adlandırılmış com.google.devtools.build.lib.skyframe paket, Skyframe'in üzerine Bazel uygulamasını içerir. Skyframe hakkında daha fazla bilgiyi burada bulabilirsiniz.

Belirli bir SkyKey öğesini bir SkyValue olarak değerlendirmek için Skyframe, anahtar türüne karşılık gelen SkyFunction kodunu çağırır. İşlevin değerlendirmesi sırasında, çeşitli SkyFunction.Environment.getValue() aşırı yüklemelerini çağırarak Skyframe'den başka bağımlılıklar isteyebilir. Bu yöntemin, bu bağımlılıkları Skyframe'in dahili grafiğine kaydetme gibi bir yan etkisi vardır. Böylece Skyframe, herhangi bir bağımlılık değiştiğinde işlevi yeniden değerlendireceğini bilir. Başka bir deyişle, Skyframe'in önbelleğe alma ve artımlı hesaplaması SkyFunction ve SkyValue ayrıntı düzeyinde çalışır.

Bir SkyFunction, kullanılamayan bir bağımlılığı istediğinde getValue() boş sonucunu döndürür. Daha sonra işlev, boş değer döndürerek Skyframe'e geri kontrol sağlamalıdır. Daha sonraki bir noktada Skyframe, kullanılamayan bağımlılığı değerlendirir ve ardından işlevi baştan yeniden başlatır. Yalnızca bu sefer getValue() çağrısı null olmayan bir sonuçla başarılı olur.

Bunun sonucunda, yeniden başlatmadan önce SkyFunction içinde gerçekleştirilen hesaplamaların tekrarlanması gerekir. Ancak bu, önbelleğe alınan SkyValues bağımlılığını değerlendirmek için yapılan çalışmaları içermez. Bu nedenle, bu sorunu genellikle çözmek için:

  1. Yeniden başlatma sayısını sınırlamak için bağımlılıkları gruplar halinde bildirme (getValuesAndExceptions() kullanarak).
  2. Bir SkyValue öğesini farklı SkyFunction'lar tarafından hesaplanan ayrı parçalara bölerek bağımsız olarak hesaplanabilir ve önbelleğe alınabilir. Bellek kullanımını artırma potansiyeline sahip olduğundan bu işlem stratejik olarak yapılmalıdır.
  3. Yeniden başlatmalar arasında SkyFunction.Environment.getState() kullanarak veya geçici statik önbelleği "Skyframe'in arkasında" tutarak yeniden başlatmalar arasında durum depolama. Karmaşık SkyFunctions nedeniyle yeniden başlatmalar arasında durum yönetimi zor olabilir. Bu nedenle, SkyFunction içerisinde hiyerarşik hesaplamaları askıya almak ve devam ettirmek için kancalar da dahil olmak üzere StateMachine'ler mantıksal eşzamanlılığa yönelik yapılandırılmış bir yaklaşım geliştirildi. Örnek: DependencyResolver#computeDependencies, yapılandırılmış hedefin potansiyel olarak büyük olan doğrudan bağımlılık grubunu hesaplamak için getState() içeren bir StateMachine kullanır. Aksi takdirde bu durum, pahalı yeniden başlatmalara neden olabilir.

Bazel'in temel olarak bu tür geçici çözümlere ihtiyacı vardır. Bunun nedeni, uçuş halindeki yüzlerce Skyframe düğümü yaygındır ve Java'nın hafif iş parçacıklarını desteklemesi, 2023 itibarıyla StateMachine uygulamasını daha iyi performans göstermemesidir.

Yıldız tarlası

Starlark, kullanıcıların Bazel'ı yapılandırmak ve genişletmek için kullandığı alana özgü dildir. Python'un çok daha az türe, kontrol akışı için daha fazla kısıtlamaya ve en önemlisi, eşzamanlı okumaları etkinleştirmek için güçlü sabitlik garantisine sahip kısıtlanmış bir alt kümesi olarak düşünülür. Bu, bazı kullanıcıları (ancak hepsini değil) dil içinde genel programlama görevlerini tamamlamaya çalışmaktan vazgeçiren bir Turing-complete değildir.

Starlark, net.starlark.java paketine uygulandı. Ayrıca, burada bağımsız bir Go uygulaması vardır. Bazel'de kullanılan Java uygulaması şu anda bir çevirmendir.

Starlark, aşağıdakileri de içeren çeşitli bağlamlarda kullanılır:

  1. BUILD dosya. Yeni derleme hedefleri burada tanımlanır. Bu bağlamda çalışan Starlark kodu, yalnızca BUILD dosyasının ve bu dosyanın yüklediği .bzl dosyanın içeriğine erişebilir.
  2. MODULE.bazel dosyası. Dış bağımlılıklar burada tanımlanır. Bu bağlamda çalışan Starlark kodunun yalnızca önceden tanımlanmış birkaç yönergeye erişimi son derece sınırlıdır.
  3. .bzl dosya. Yeni derleme kuralları, depo kuralları ve modül uzantıları burada tanımlanır. Buradaki Starlark kodu yeni işlevler tanımlayabilir ve diğer .bzl dosyalarından yüklenebilir.

BUILD ve .bzl dosyaları için kullanılabilen lehçeler, farklı şeyleri ifade ettiklerinden biraz farklıdır. Farklılıkların listesini burada bulabilirsiniz.

Buradan Starlark hakkında daha fazla bilgi edinebilirsiniz.

Yükleme/analiz aşaması

Yükleme/analiz aşamasında Bazel, belirli bir kuralı oluşturmak için hangi işlemlerin gerekli olduğunu belirler. Temel birimi, "yapılandırılmış hedef", yani kısaca bir (hedef, yapılandırma) çiftidir.

Bu aşama, daha önce serileştirilmiş olan ama artık zaman içinde üst üste gelebilen iki farklı parçaya bölünebildiği için "yükleme/analiz aşaması" olarak adlandırılır:

  1. Paketler yükleniyor, yani BUILD dosyaları bunları temsil eden Package nesnelerine dönüştürülüyor
  2. Yapılandırılmış hedefleri analiz etme, yani eylem grafiğini oluşturmak için kuralların uygulanmasını uygulama

Komut satırında istenen yapılandırılmış hedeflerin geçişli olarak kapatılmasındaki her yapılandırılmış hedef, aşağıdan yukarıya analiz edilmelidir. Yani önce yaprak düğümleri, sonra komut satırındakiler analiz edilmelidir. Tek bir yapılandırılmış hedefin analizinde kullanılan girişler şunlardır:

  1. Yapılandırma. ("Kural nasıl oluşturulur?"; örneğin, hedef platform ve kullanıcının C++ derleyicisine iletilmesini istediği komut satırı seçenekleri gibi)
  2. Doğrudan bağımlılıklar. Analiz edilen kural, geçişli bilgi sağlayıcılarını kullanabilir. Bu şekilde adlandırılır çünkü sınıf yolundaki tüm .jar dosyaları veya bir C++ ikili programına bağlanması gereken tüm .o dosyaları gibi, yapılandırılan hedefin geçişli olarak kapatılmasındaki bilgilerin bir "toplayıcı analizini" sağlarlar.
  3. Hedefin kendisi. Bu durum, hedefin içinde bulunduğu paketin yüklenmesinden kaynaklanır. Kurallar açısından, buna ilişkin özellikler de dahildir. Bunlar da genellikle önemlidir.
  4. Yapılandırılmış hedefin uygulanması. Kurallar için bu, Starlark'ta veya Java'da olabilir. Kural olmayan yapılandırılmış tüm hedefler Java'da uygulanır.

Yapılandırılmış bir hedefin analiz çıkışı şu şekilde olur:

  1. Kendine bağlı hedefleri yapılandıran geçişli bilgi sağlayıcılar,
  2. Oluşturabileceği eserler ve bunları üreten eylemler.

Java kurallarına sunulan API RuleContext'dir. Bu, Starlark kurallarının ctx bağımsız değişkenine eşdeğerdir. API daha güçlüdür ancak aynı zamanda Bad ThingsTM'i yapmak daha kolaydır. Örneğin, zaman veya alan karmaşıklığı ikinci dereceden (veya daha kötü) olan kod yazmak, Java istisnasıyla Bazel sunucusunun kilitlenmesine neden olmak veya değişmez değerleri ihlal etmek (örneğin, bir Options örneğini yanlışlıkla değiştirerek ya da yapılandırılmış bir hedefi değişebilir hale getirerek) Bad ThingsTM'i yapmak daha kolaydır.

Yapılandırılmış bir hedefin doğrudan bağımlılıklarını belirleyen algoritma, DependencyResolver.dependentNodeMap() bölgesinde çalışır.

Yapılandırmalar

Yapılandırmalar, hedef oluşturmanın "nasıl" yapıldığını, yani "hangi platform için, hangi komut satırı seçenekleriyle" vb. ile ilgilidir.

Aynı hedef, aynı yapıdaki birden fazla yapılandırma için oluşturulabilir. Örneğin, derleme sırasında çalıştırılan bir araç ve hedef kod için aynı kod kullanıldığında, çapraz derleme yaptığımızda veya yağ Android uygulaması (birden fazla CPU mimarisi için yerel kod içeren bir uygulama) oluşturduğumuzda bu yöntem kullanışlıdır.

Kavramsal olarak yapılandırma bir BuildOptions örneğidir. Ancak pratikte BuildOptions, ek işlev sağlayan BuildConfiguration tarafından sarmalanır. Bağımlılık grafiğinin en üstünden en alta doğru yayılır. Değişirse derlemenin yeniden analiz edilmesi gerekir.

Bu durum, örneğin yalnızca test hedeflerini etkilese de istenen test çalıştırmalarının sayısı değişirse (böyle olmamasına rağmen henüz hazır olmadığı için yapılandırmaları "kırpmayı" planlıyoruz) anormalliklere neden olur. Örneğin, istenen test çalıştırmalarının sayısı değişirse derlemenin tamamını yeniden analiz etmek zorunda kalmazsınız.

Bir kural uygulaması, yapılandırmanın bir kısmına ihtiyaç duyduğunda bunu RuleClass.Builder.requiresConfigurationFragments() kullanarak tanımında açıklaması gerekir. Bunun amacı, hataları önlemek (Java parçasını kullanan Python kuralları gibi) ve Python seçenekleri değişirse C++ hedeflerinin yeniden analiz edilmesine gerek kalmayacak şekilde yapılandırma düzeltmesini kolaylaştırmaktır.

Bir kuralın yapılandırması, "üst" kuralının yapılandırmasıyla aynı değildir. Bağımlılık ucundaki yapılandırmayı değiştirme işlemine "yapılandırma geçişi" adı verilir. Bu durum iki yerde gerçekleşebilir:

  1. Bağımlılık ucunda. Bu geçişler Attribute.Builder.cfg() bölümünde belirtilmiştir ve Rule (geçişin gerçekleştiği yerde) ve BuildOptions'den (orijinal yapılandırma) bir veya daha fazla BuildOptions'ye (çıkış yapılandırması) geçişlerdir.
  2. Yapılandırılmış bir hedefe gelen herhangi bir uçta. Bunlar RuleClass.Builder.cfg() içinde belirtilmiştir.

İlgili sınıflar: TransitionFactory ve ConfigurationTransition.

Yapılandırma geçişleri kullanılır. Örneğin:

  1. Derleme sırasında belirli bir bağımlılığın kullanıldığını ve yürütme mimarisinde derlenmesi gerektiğini beyan etmek için
  2. Birden fazla mimari için belirli bir bağımlılığın oluşturulması gerektiğini beyan etmek (örneğin, yağ Android APK'larındaki yerel kod için)

Bir yapılandırma geçişi birden fazla yapılandırmayla sonuçlanırsa bu geçişe bölünmüş geçiş adı verilir.

Yapılandırma geçişleri Starlark'ta da uygulanabilir (belgeleri burada bulabilirsiniz)

Geçişli bilgi sağlayıcılar

Geçişli bilgi sağlayıcıları, yapılandırılmış hedeflerin buna bağlı olan diğer yapılandırılmış hedefler hakkında bir şeyler bildirmesinin bir yoludur (ve _only _way). Adlarında "geçişli" ifadesinin nedeni, bunun genellikle yapılandırılmış hedefin geçişli olarak kapanmasının bir tür kapsamlı hali olmasıdır.

Java geçişli bilgi sağlayıcıları ile Starlark sağlayıcıları arasında genellikle bire bir iletişim vardır (FileProvider, FilesToRunProvider ve RunfilesProvider ifadelerinin birleşiminden oluşmuş DefaultInfo istisnadır çünkü bu API, Java'nın doğrudan harf çevirisinden daha Starlark benzeridir). Müşterinin anahtarı aşağıdakilerden biridir:

  1. Java Sınıfı nesnesi. Bu özellik yalnızca Starlark'tan erişilemeyen sağlayıcılar tarafından kullanılabilir. Bu sağlayıcılar TransitiveInfoProvider sınıfının bir alt sınıfıdır.
  2. Bir dize. Bu eski bir yöntem olup isim çakışmalarına açık olduğundan büyük ölçüde önerilmez. Bu tür geçişli bilgi sağlayıcıları, build.lib.packages.Info doğrudan alt sınıflarıdır .
  3. Sağlayıcı simgesi. Bu, provider() işlevi kullanılarak Starlark'tan oluşturulabilir ve yeni sağlayıcılar oluşturmak için önerilen yoldur. Sembol, Java'da Provider.Key örneğiyle temsil edilir.

Java'da uygulanan yeni sağlayıcılar BuiltinProvider kullanılarak uygulanmalıdır. NativeProvider desteği sonlandırıldı (henüz kaldırma vaktimiz olmadı) ve TransitiveInfoProvider alt sınıfa Starlark'tan erişilemiyor.

Yapılandırılmış hedefler

Yapılandırılmış hedefler RuleConfiguredTargetFactory olarak uygulanır. Java'da uygulanan her kural sınıfı için bir alt sınıf vardır. Starlark'ın yapılandırılmış hedefleri StarlarkRuleConfiguredTargetUtil.buildRule() aracılığıyla oluşturulur .

Yapılandırılmış hedef fabrikalar, dönüş değerlerini oluşturmak için RuleConfiguredTargetBuilder kullanmalıdır. Şunları içerir:

  1. filesToBuild, belirsiz bir kavram olan "bu kuralın temsil ettiği dosya kümesi"dir. Bunlar, yapılandırılan hedef komut satırında veya bir genrule türünün src'lerinde olduğunda oluşturulan dosyalardır.
  2. Çalışma dosyaları, normal veriler ve veriler.
  3. Kullanıcının çıkış grupları. Bunlar, kuralın oluşturabileceği çeşitli "diğer dosya gruplarıdır". Bunlara BUILD'daki dosya grubu kuralının çıkış_grubu özelliği ve Java'da OutputGroupInfo sağlayıcısı kullanılarak erişilebilir.

Çalıştırma dosyaları

Bazı ikili programların çalışması için veri dosyalarına ihtiyaç vardır. Belirgin bir örnek, giriş dosyalarına ihtiyaç duyan testlerdir. Bu, Bazel'de "runfiles" kavramıyla temsil edilir. "Çalıştırma dosyaları ağacı", belirli bir ikili program için veri dosyalarının dizin ağacıdır. Dosya sisteminde, çıkış ağaçlarının kaynağındaki dosyalara işaret eden bağımsız sembolik bağlantıların bulunduğu bir sembolik bağlantı ağacı olarak oluşturulur.

Runfiles grubu, Runfiles örneği olarak temsil edilir. Kavram olarak, runfiles ağacındaki bir dosyanın yolundan dosyayı temsil eden Artifact örneğine kadar olan bir haritadır. Bu, iki nedenden dolayı tek bir Map'dan biraz daha karmaşıktır:

  • Çoğu zaman bir dosyanın çalıştırma dosyaları yolu, execpath ile aynıdır. Bu seçenekle RAM'den tasarruf ederiz.
  • Runfiles ağaçlarında, bunların da temsil edilmesi gereken çeşitli eski giriş türleri vardır.

Çalıştırma dosyaları RunfilesProvider kullanılarak toplanır: Bu sınıfın bir örneği, runfiles'i yapılandırılmış bir hedefi (kitaplık gibi) ve geçişli kapatma ihtiyaçlarını temsil eder ve iç içe yerleştirilmiş bir grup gibi toplanır (aslında, kapak altında iç içe yerleştirilmiş kümeler kullanılarak uygulanır): Her hedef birleşimleri, bağımlılıklarına ait çalıştırma dosyalarının bir kısmını ekler, kendilerinden bazılarını ekler ve ardından elde edilen kurulumu bağımlılık grafiğine gönderir. Bir RunfilesProvider örneği, biri kuralın "data" özelliği üzerinden bağımlı olduğu durumlarda ve diğeri gelen tüm diğer bağımlılık türleri için olmak üzere iki Runfiles örneği içerir. Bunun nedeni, bir hedefin bazen bir veri özelliği aracılığıyla bağlı olduğunda farklı çalıştırma dosyaları sunmasıdır. Bu istenmeyen eski davranıştır ve henüz gidermediğimiz durumlarla karşılaşılmıştır.

İkili programların çalıştırma dosyaları, RunfilesSupport örneği olarak temsil edilir. Bu, Runfiles ürününden farklıdır çünkü RunfilesSupport, gerçekte oluşturulma yeteneğine sahiptir (yalnızca bir haritalama olan Runfiles'den farklı olarak). Bu, aşağıdaki ek bileşenleri gerektirir:

  • Runfiles girişi. Bu, çalıştırma dosyaları ağacının serileştirilmiş bir açıklamasıdır. Çalıştırma dosyaları ağacının içerikleri için proxy olarak kullanılır ve Bazel, çalıştırma dosyaları ağacının yalnızca manifest içeriği değişirse değişeceğini varsayar.
  • Çıkış runfiles manifesti. Bu, bazen sembolik bağlantıları desteklemeyen Windows'da, runfiles ağaçlarını işleyen çalışma zamanı kitaplıkları tarafından kullanılır.
  • Runfiles aracı. Bir Runfiles ağacının var olması için sembolik bağlantı ağacı ile sembollerin işaret ettiği yapı derlenmelidir. Bağımlılık kenarlarının sayısını azaltmak amacıyla bunların tümünü temsil etmek için runfiles aracı kullanılabilir.
  • RunfilesSupport nesnesinin çalıştırma dosyalarının temsil ettiği ikili programı çalıştırmak için komut satırı bağımsız değişkenleri.

Bakış Açıları

Özellikler, "işlemi bağımlılık grafiğine göre yaymanın" bir yoludur. Bunlar Bazel kullanıcıları için burada açıklanmıştır. Protokol tamponları iyi bir örnektir: Bir proto_library kuralı belirli bir dili bilmemelidir, ancak herhangi bir programlama dilinde protokol arabelleği mesajının ("protokol arabelleklerinin "temel birimi") uygulanması, proto_library kuralına bağlanmalıdır. Böylece, aynı dilde iki hedef yalnızca bir kez oluşturulan protokol arabelleğine bağımlıdır.

Yapılandırılmış hedefler gibi Skyframe'de de SkyValue olarak temsil edilirler ve oluşturulma şekilleri, yapılandırılmış hedeflerin oluşturulma biçimine çok benzer: RuleContext'ye erişimi olan ConfiguredAspectFactory adlı bir fabrika sınıfı vardır ancak yapılandırılmış hedef fabrikaların aksine, bağlı olduğu yapılandırılmış hedef ve sağlayıcılarını da bilir.

Bağımlılık grafiğinde aşağı doğru dağıtılan yönler, Attribute.Builder.aspects() işlevi kullanılarak her özellik için belirtilir. Bu sürece katılan birkaç sınıf vardır:

  1. AspectClass, özelliğin uygulanmasıdır. Java'da (bu durumda bir alt sınıftır) veya Starlark'ta (bu durumda StarlarkAspectClass örneğidir) olabilir. RuleConfiguredTargetFactory ile benzerdir.
  2. AspectDefinition, ilgili öğenin tanımıdır; gerektirdiği sağlayıcıları ve sağladığı sağlayıcıları içerir ve uygun AspectClass örneği gibi uygulamaya ilişkin bir referans içerir. RuleClass için benzerdir.
  3. AspectParameters, bağımlılık grafiğinde aşağı yayılan bir yönü parametrize etme yöntemidir. Şu anda dize haritasına ilişkin bir dizedir. Protokol arabelleklerinin faydalı olmasına örnek olarak protokol arabellekleri verilebilir. Bir dilde birden fazla API varsa protokol arabelleklerinin hangi API için oluşturulması gerektiğiyle ilgili bilgiler bağımlılık grafiğinde aşağıya yayılmalıdır.
  4. Aspect, bağımlılık grafiğinde yayılım gösteren bir yönü hesaplamak için gereken tüm verileri temsil eder. En boy sınıfı, tanımı ve parametrelerinden oluşur.
  5. RuleAspect, belirli bir kuralın hangi yönlerine yayılması gerektiğini belirleyen işlevdir. Bu, Rule -> Aspect işlevidir.

Beklenmeyen bir komplikasyon, yönlerin diğer yönlere eklenebilmesidir. Örneğin, bir Java IDE için sınıf yolunu toplayan bir özellik muhtemelen sınıf yolundaki tüm .jar dosyaları hakkında bilgi edinmek isteyecektir. Ancak bunların bazıları protokol arabelleğidir. Bu durumda, IDE unsuru (proto_library kuralı + Java proto en boy oranı) çiftine eklemek ister.

Özelliklerle ilgili unsurların karmaşıklığı, AspectCollection dersinde ele alınmıştır.

Platformlar ve araç zincirleri

Bazel çok platformlu derlemeleri, yani derleme işlemlerinin çalıştırıldığı birden fazla mimarinin ve kodun derlendiği birden fazla mimarinin olabileceği derlemeleri destekler. Bu mimariler, Bazel dilinde platformlar olarak adlandırılır (Tüm belgeleri burada bulabilirsiniz)

Platform, kısıtlı ayarlardan ("CPU mimarisi" kavramı gibi) kısıtlı değerlere (x86_64 gibi belirli bir CPU gibi) giden anahtar/değer çiftiyle tanımlanır. @platforms deposunda en sık kullanılan sınırlama ayarlarının ve değerlerinin yer aldığı bir "sözlüğümüz" mevcuttur.

Araç zinciri kavramı, derlemenin çalıştırıldığı platformlara ve hedeflenen platformlara bağlı olarak farklı derleyicilerin kullanılması gerekebileceği gerçeğinden gelir. Örneğin, belirli bir C++ araç zinciri belirli bir işletim sisteminde çalışabilir ve diğer bazı işletim sistemlerini hedefleyebilir. Bazel, ayarlanan yürütme ve hedef platforma göre kullanılan C++ derleyiciyi belirlemelidir (araç zincirleri ile ilgili belgeleri burada bulabilirsiniz).

Bunu yapmak için araç zincirlerine, destekledikleri yürütme grubu ve hedef platform kısıtlamalarını belirten ek açıklamalar eklenir. Bunu yapmak için araç zincirinin tanımı iki bölüme ayrılır:

  1. Yürütme grubunu ve hedef kısıtlamalarını belirten toolchain() kuralı, araç zincirinin desteklediğini ve ne tür bir araç zinciri olduğunu (ör. C++ veya Java) söyler (ikinci seçenek, toolchain_type() kuralıyla temsil edilir).
  2. Gerçek araç zincirini açıklayan dile özgü bir kural (ör. cc_toolchain())

Bu şekilde yapılır çünkü araç zinciri çözümü yapmak için her araç zincirinin kısıtlamalarını bilmemiz gerekir ve dile özgü *_toolchain() kuralları bundan çok daha fazla bilgi içerir. Bu nedenle, yüklenmeleri daha uzun sürer.

Yürütme platformları, aşağıdaki yollardan biriyle belirtilir:

  1. MODULE.bazel dosyasında register_execution_platforms() işlevini kullanarak
  2. --extra_execution_platforms komut satırı seçeneğini kullanarak komut satırında

Kullanılabilir yürütme platformları grubu şurada hesaplanır: RegisteredExecutionPlatformsFunction

Yapılandırılmış bir hedefin hedef platformu PlatformOptions.computeTargetPlatform() tarafından belirlenir . Bu bir platform listesidir çünkü nihayetinde birden çok hedef platformu desteklemek isteyeceğiz, ancak henüz uygulanmadı.

Yapılandırılmış bir hedef için kullanılacak araç zinciri, ToolchainResolutionFunction tarafından belirlenir. Şunun bir fonksiyonudur:

  • Kayıtlı araç zincirleri grubu (MODULE.bazel dosyasında ve yapılandırmada)
  • İstenen yürütme ve hedef platformlar (yapılandırmada)
  • Yapılandırılmış hedefin (UnloadedToolchainContextKey) içinde) gerektirdiği araç zinciri türleri grubu
  • UnloadedToolchainContextKey bölgesindeki yapılandırılmış hedefin (exec_compatible_with özelliği) ve yapılandırmanın (--experimental_add_exec_constraints_to_targets) yürütme platformu kısıtlamaları grubu

Bunun sonucunda UnloadedToolchainContext elde edilir. Bu, temel olarak araç zinciri türünden (ToolchainTypeInfo örneği olarak temsil edilir) seçili araç zincirinin etiketine kadar olan bir haritadır. "Boşaltılmış" olarak adlandırılmıştır çünkü araç zincirlerini içermez, yalnızca etiketlerini içerir.

Daha sonra araç zincirleri, ResolvedToolchainContext.load() kullanılarak yüklenir ve bunları isteyen yapılandırılmış hedefin uygulanması tarafından kullanılır.

Ayrıca, --cpu gibi çeşitli yapılandırma bayraklarıyla temsil edilen tek bir "ana makine" yapılandırması ve hedef yapılandırmalarına dayanan eski bir sistemimiz vardır . Kademeli olarak yukarıdaki sisteme geçiyoruz. Kullanıcıların eski yapılandırma değerlerine bel bağlı olduğu durumları çözmek için eski işaretler ile yeni stil platform kısıtlamaları arasında çeviri yapmak üzere platform eşlemelerini uyguladık. Kodu PlatformMappingFunction dilindedir ve Starlark dışında bir "küçük dil" kullanır.

Sınırlamalar

Bazen bir hedef yalnızca birkaç platformla uyumlu olacak şekilde belirlenir. Bazel'in bu amaca ulaşmak için kullanabileceği çok sayıda mekanizma var ne yazık ki:

  • Kurala özel kısıtlamalar
  • environment_group() / environment()
  • Platform kısıtlamaları

Kurala özgü kısıtlamalar, çoğunlukla Google'da Java kuralları için kullanılır; bu kurallar kullanımdan kaldırılma aşamasındadır ve Bazel'de kullanılamaz, ancak kaynak kodu buna referanslar içerebilir. Bunu yöneten özelliğe constraints= denir .

ortam_grubu() ve ortam()

Bu kurallar eski bir mekanizmadır ve yaygın olarak kullanılmaz.

Tüm derleme kuralları, hangi "ortamlar" için oluşturulabileceğini tanımlayabilir. Burada "ortam", environment() kuralının bir örneğidir.

Desteklenen ortamların bir kural için belirtilebileceği çeşitli yollar vardır:

  1. restricted_to= özelliği aracılığıyla. Bu, en doğrudan spesifikasyon formudur; kuralın bu grup için desteklediği tam ortam grubunu belirtir.
  2. compatible_with= özelliği aracılığıyla. Bu, bir kuralın varsayılan olarak desteklenen "standart" ortamlara ek olarak desteklediği ortamları tanımlar.
  3. Paket düzeyindeki default_restricted_to= ve default_compatible_with= özellikleri aracılığıyla.
  4. environment_group() kurallarındaki varsayılan özellikler aracılığıyla. Her ortam, "CPU mimarileri", "JDK sürümleri" veya "mobil işletim sistemleri" gibi tematik olarak ilişkili benzerler grubuna aittir. Bir ortam grubunun tanımı, bu ortamlardan hangilerinin restricted_to= / environment() özellikleriyle aksi belirtilmedikçe "varsayılan" tarafından desteklenmesi gerektiğini içerir. Bu tür özelliklere sahip olmayan bir kural, tüm varsayılanları devralır.
  5. Bir kural sınıfı varsayılanı aracılığıyla. Bu, belirli bir kural sınıfının tüm örnekleri için genel varsayılanları geçersiz kılar. Bu, örneğin, her bir örneğin bu özelliği açıkça beyan etmesine gerek kalmadan tüm *_test kurallarını test edilebilir hale getirmek için kullanılabilir.

environment() normal bir kural olarak uygulanır. environment_group() ise hem Target alt sınıfıdır ancak Rule (EnvironmentGroup) alt sınıfı değildir hem de varsayılan olarak Starlark (StarlarkLibrary.environmentGroup()) tarafından sunulan bir işlevdir ve nihayetinde isimsiz bir hedef oluşturur. Bunun amacı, her ortamın ait olduğu ortam grubunu ve her ortam grubunun da varsayılan ortamlarını tanımlaması gerektiği için ortaya çıkacak döngüsel bir bağımlılığı önlemektir.

--target_environment komut satırı seçeneği kullanılarak derleme belirli bir ortamla kısıtlanabilir.

Kısıtlama kontrolünün uygulanması RuleContextConstraintSemantics ve TopLevelConstraintSemantics ürünlerindedir.

Platform kısıtlamaları

Bir hedefin hangi platformlarla uyumlu olduğunu açıklamanın şu anki "resmi" yolu, araç zincirlerini ve platformları tanımlamak için kullanılan kısıtlamaların aynısını kullanmaktır. #10945 pull isteğinde inceleniyor.

Görünürlük

Google'da olduğu gibi çok sayıda geliştiriciyle büyük bir kod tabanı üzerinde çalışıyorsanız başkalarının kodunuza rastgele bağlı olmasını engellemeye dikkat etmeniz gerekir. Aksi takdirde, Hyrum yasası uyarınca kullanıcılar, uygulama ayrıntıları olarak kabul ettiğiniz davranışlardan alınacaktır.

Bazel, bunu görünürlük adı verilen mekanizma ile destekler: Belirli bir hedefin yalnızca görünürlük özelliğini kullanarak bağlı olabileceğini beyan edebilirsiniz. Bu özellik biraz özeldir çünkü bir etiket listesi içermesine rağmen bu etiketler belirli bir hedefe işaret eden bir işaretçi yerine paket adları üzerinde bir kalıp kodlayabilir. (Evet, bu bir tasarım hatasıdır.)

Bu uygulama aşağıdaki yerlerde uygulanır:

  • RuleVisibility arayüzü, görünürlük bildirimini temsil eder. Sabit bir değer (tamamen herkese açık veya tamamen gizli) veya bir etiket listesi olabilir.
  • Etiketler paket gruplarına (önceden tanımlanmış paket listesi), doğrudan paketlere (//pkg:__pkg__) veya paketlerin alt ağaçlarına (//pkg:__subpackages__) başvurabilir. Bu, //pkg:* veya //pkg/... kullanan komut satırı söz diziminden farklıdır.
  • Paket grupları, kendi hedefleri (PackageGroup) ve yapılandırılmış hedef (PackageGroupConfiguredTarget) olarak uygulanır. İstesek muhtemelen bunları basit kurallarla değiştirebiliriz. Bunların mantığı, //pkg/... gibi tek bir kalıba karşılık gelen PackageSpecification; tek bir package_group packages özelliğine karşılık gelen PackageGroupContents ve package_group ile geçişli includes öğesi üzerinde toplanan PackageSpecificationProvider kullanılarak uygulanır.
  • Görünürlük etiketi listelerinden bağımlılıklara dönüştürme işlemi DependencyResolver.visitTargetVisibility ve başka birkaç yerde daha yapılır.
  • Asıl kontrol CommonPrerequisiteValidator.validateDirectPrerequisiteVisibility() içinde yapılır

İç içe yerleştirilmiş kümeler

Genellikle yapılandırılmış bir hedef, bir dizi dosyayı bağımlılıklarından toplar, kendine ekler ve toplama kümesini geçişli bir bilgi sağlayıcısına sarmalar. Böylece, bu hedefe bağımlı olan yapılandırılmış hedefler de aynısını yapabilir. Örnekler:

  • Derleme için kullanılan C++ üstbilgi dosyaları
  • cc_library işleminin geçişli kapanışını temsil eden nesne dosyaları
  • Bir Java kuralının derlenmesi veya çalıştırılması için sınıf yolunda olması gereken .jar dosyaları kümesi
  • Bir Python kuralının geçişli kapanışındaki Python dosyaları grubu

Bu işlemi List veya Set gibi basit bir şekilde yaparsak ikinci dereceden bellek kullanımına sahip olurduk: N kural zinciri varsa ve her kural bir dosya eklerse 1+2+...+N koleksiyon üyemiz olur.

Bu sorunu çözmek için NestedSet kavramını ortaya çıkardık. Bu, diğer NestedSet örneklerinden ve kendilerine ait bazı üyelerden oluşan bir veri yapısıdır. Böylece, kümelerin yönlendirilmiş bir döngüsel grafiğini oluşturur. Bunlar sabittir ve üyeleri tekrar tekrar oluşturulabilir. Çoklu yineleme sırası (NestedSet.Order): preorder (ön sipariş), postorder (son sipariş), topolojik (bir düğüm her zaman üst öğelerinden sonra gelir) ve "umursamaz, ancak her seferinde aynı olmalıdır".

Aynı veri yapısı Starlark'ta depset olarak adlandırılır.

Yapılar ve İşlemler

Asıl derleme, kullanıcının istediği çıkışı üretmek için çalıştırılması gereken bir dizi komuttan oluşur. Komutlar Action sınıfının örnekleri, dosyalar ise Artifact sınıfının örnekleri olarak gösterilir. Bunlar, "eylem grafiği" adı verilen, iki taraflı, yönlendirilmiş, döngüsel bir grafik halinde düzenlenmiştir.

Yapılar iki türde sunulur: kaynak yapılar (Bazel yürütmeye başlamadan önce kullanılabilen yapılar) ve türetilmiş yapılar (oluşturulması gereken yapılar). Türetilen yapıların kendileri birden fazla türde olabilir:

  1. **Normal eserler. **Bunlar, sağlama toplamları hesaplanarak güncel olup olmadığı kontrol edilir. Kısayol olarak mtime kullanıldığında da; saati değişmemişse dosyanın toplamını almayız.
  2. Çözülmemiş sembolik bağlantı yapıları. Bunlar, readlink() çağrısıyla güncellik açısından kontrol edilir. Normal yapıların aksine, bunlar sallanan sembolik bağlantılar olabilir. Genellikle, daha sonra bazı dosyaların bir tür arşivde toplandığı durumlarda kullanılır.
  3. Ağaç yapıları. Bunlar tek dosyalar değil, dizin ağaçlarıdır. Bu dosyalardaki dosya grubu ve içerikleri kontrol edilerek güncel olup olmadıkları kontrol edilir. Bunlar TreeArtifact olarak gösterilir.
  4. Sabit meta veri yapıları. Bu yapılarda yapılan değişiklikler yeniden derlemeyi tetiklemez. Bu, yalnızca derleme damgası bilgileri için kullanılır: Sırf geçerli saat değiştiği için yeniden derleme yapmak istemeyiz.

Kaynak yapıların ağaç yapıları veya çözülmemiş sembolik yapılar olmamasının temel bir nedeni yoktur; sadece bunu henüz uygulamamışızdır (ancak BUILD dosyasındaki bir kaynak dizine referans vermek, Bazel'de uzun süredir bilinen birkaç hatalılık sorunundan biridir. BAZEL_TRACK_SOURCE_DIRECTORIES=1 JVM özelliği tarafından etkinleştirilen bir tür çalışma uygulamamız mevcuttur).

Önemli bir Artifact türü, aracılardır. Bunlar, MiddlemanAction çıktıları olan Artifact örnekleriyle gösterilir. Bazı şeyleri özel hale getirmek için kullanılırlar:

  • Birleştirme aracıları, yapıları birlikte gruplandırmak için kullanılır. Bunun nedeni, çok sayıda işlem aynı büyük giriş grubunu kullanıyorsa N*M bağımlılık kenarlarımızı değil, yalnızca N+M (iç içe yerleştirilmiş kümeler ile değiştirilecektir)
  • Bağımlılık aracılarının zamanlanması, bir işlemin diğerinden önce çalıştırılmasını sağlar. Çoğunlukla hata analizi yapmak için kullanılır ancak aynı zamanda C++ derlemesi için de kullanılır (açıklama için CcCompilationContext.createMiddleman() sayfasına bakın)
  • Runfiles aracıları, bir runfiles ağacının varlığını sağlamak için kullanılır. Böylece, bir çalıştırmanın çıkış manifestine ve çalıştırma dosyaları ağacı tarafından başvurulan her bir yapıya ayrı olarak bağımlı olması gerekmez.

Eylemler; çalıştırılması gereken komutlar, komutun ihtiyaç duyduğu ortam ve ürettiği çıkış kümesi olarak anlaşılır. Bir işlem açıklamasının ana bileşenleri şunlardır:

  • Çalıştırılması gereken komut satırı
  • İhtiyaç duyduğu giriş yapıları
  • Ayarlanması gereken ortam değişkenleri
  • Çalışması gereken ortamı (ör. platform) açıklayan ek açıklamalar \

İçeriği Bazel tarafından bilinen bir dosya yazmak gibi başka özel durumlar da vardır. Bunlar AbstractAction sınıfının bir alt sınıfıdır. Java ve C++ kendi işlem türlerine (JavaCompileAction, CppCompileAction ve CppLinkAction) sahip olsa da, işlemlerin çoğu SpawnAction veya StarlarkAction (aynı şekilde, muhtemelen ayrı sınıflar olmamalıdır).

Sonunda her şeyi SpawnAction konumuna taşımak istiyoruz. JavaCompileAction oldukça yakın bir çözümdür, ancak .d dosyası ayrıştırma ve taramayı içermesi nedeniyle C++ biraz özel bir durumdur.

İşlem grafiği Skyframe grafiğine çoğunlukla "yerleştirilmiştir": Kavramsal olarak bir işlemin gerçekleştirilmesi, ActionExecutionFunction çağrısı olarak temsil edilir. Bir işlem grafiği bağımlılık ucundan Skyframe bağımlılık ucuna eşleme, ActionExecutionFunction.getInputDeps() ve Artifact.key() konularında açıklanmıştır ve Skyframe kenarlarının sayısını düşük tutmak için birkaç optimizasyon yapılmaktadır:

  • Türetilen yapıların kendi SkyValue'leri yoktur. Bunun yerine, onu oluşturan işlemin anahtarını bulmak için Artifact.getGeneratingActionKey() kullanılır
  • İç içe yerleştirilmiş setlerin kendi Skyframe anahtarı vardır.

Paylaşılan işlemler

Bazı işlemler birden çok yapılandırılmış hedef tarafından oluşturulur; Starlark kuralları, türetilen eylemleri yalnızca yapılandırmalarına ve paketlerine göre belirlenen bir dizine eklemelerine izin verildiği için (ancak aynı paketteki kurallar çakışabilir) ancak Java'da uygulanan kurallar türetilen yapıları herhangi bir yere koyabilir.

Bunun yanlış bir özellik olduğu kabul edilir, ancak bundan kurtulmak çok zor bir işlemdir, çünkü örneğin, bir kaynak dosyanın bir şekilde işlenmesi gerektiğinde ve söz konusu dosyaya birden çok kuralla (el dalgası-el dalgası) referans verildiğinde yürütme süresinde önemli ölçüde tasarruf sağlar. Bunun için bir miktar RAM kullanılır: Paylaşılan işlemin her örneğinin bellekte ayrı ayrı depolanması gerekir.

İki işlem aynı çıkış dosyasını oluşturuyorsa iki işlem de tamamen aynı olmalıdır: Aynı girişlere ve aynı çıkışlara sahip olmalı ve aynı komut satırını çalıştırmalıdır. Bu denklik ilişkisi Actions.canBeShared() ürününde uygulanır ve her işleme bakılarak analiz ve yürütme aşamaları arasında doğrulanır. Bu, SkyframeActionExecutor.findAndStoreArtifactConflicts() uygulamasında uygulanır ve Bazel'de yapının "global" bir görünümü gereken birkaç yerden biridir.

Yürütme aşaması

Bu noktada Bazel, çıkış oluşturan komutlar gibi derleme işlemlerini çalıştırmaya başlar.

Analiz aşamasından sonra Bazel'ın yaptığı ilk şey hangi Yapıların oluşturulması gerektiğini belirlemektir. Bunun mantığı TopLevelArtifactHelper içinde kodlanmıştır. Özetle, "bu hedef komut satırındaysa bu yapıları oluştur" ifadesinin açıkça ifade edilmesi amacıyla komut satırındaki yapılandırılmış hedeflerin filesToBuild ve özel bir çıkış grubunun içeriğidir.

Sonraki adım, yürütme kökü oluşturmaktır. Bazel, dosya sisteminde (--package_path) farklı konumlardaki kaynak paketleri okuma seçeneğine sahip olduğundan, yerel olarak gerçekleştirilen işlemleri tam bir kaynak ağacıyla sağlaması gerekir. Bu işlem SymlinkForest sınıfı tarafından işlenir ve analiz aşamasında kullanılan her hedef not alınıp her paketi asıl konumundan farklı bir hedefle sembollere bağlayan tek bir dizin ağacı oluşturulur. Bir alternatif, komutlara doğru yolları aktarmak (--package_path dikkate alınarak) olabilir. Bu istenmeyen bir durumdur çünkü:

  • Bir paket, paket yolu girişinden diğerine taşındığında işlem komut satırlarını değiştirir (önceden yaygın bir durumdur)
  • Bir işlem yerel olarak çalıştırıldığından farklıysa uzaktan çalıştırıldığında farklı komut satırlarıyla sonuçlanır
  • Kullanımdaki araca özel bir komut satırı dönüşümü gerektirir (Java sınıf yolları ve C++ yolları arasındaki farkı göz önünde bulundurun)
  • Bir işlemin komut satırı değiştirildiğinde işlem önbellek girişi geçersiz hale geliyor
  • --package_path yavaş ve sürekli olarak kullanımdan kaldırılıyor

Daha sonra, Bazel aksiyon grafiğinde (eylemler ile bunların giriş ve çıkış yapılarından oluşan iki taraflı, yönlendirilmiş grafik) ve çalıştırmaya başlıyor. Her işlemin yürütülmesi SkyValue ActionExecutionValue sınıfının bir örneğiyle temsil edilir.

Bir işlem çalıştırmak pahalı olduğundan, Skyframe'in arkasına yerleştirilebilecek birkaç önbelleğe alma katmanımız var:

  • ActionExecutionFunction.stateMap, Skyframe'in yeniden başlatılmasını sağlayacak verileri içeriyor: ActionExecutionFunction
  • Yerel işlem önbelleği, dosya sisteminin durumuyla ilgili verileri içerir
  • Uzaktan yürütme sistemleri genellikle kendi önbelleklerini de içerir

Yerel işlem önbelleği

Bu önbellek, Skyframe'in arkasında yer alan başka bir katmandır. Bir işlem Skyframe'de yeniden yürütülse bile yerel işlem önbelleğinde isabet olabilir. Yerel dosya sisteminin durumunu temsil eder ve diske serileştirilir. Diğer bir deyişle, yeni bir Bazel sunucusu başlatıldığında Skyframe grafiği boş olsa bile yerel işlem önbellek isabetleri alınabilir.

Bu önbellek, isabetler için şu yöntem kullanılarak kontrol edilir: ActionCacheChecker.getTokenIfNeedToExecute() .

Adının aksine, türetilen bir öğenin izlediği yoldan onu yayınlayan eyleme kadar geçen bir haritadır. İşlem şu şekilde açıklanır:

  1. Giriş ve çıkış dosyaları kümesi ve bunların sağlama toplamı
  2. Genellikle yürütülen komut satırı olan "işlem anahtarı"dır ancak genel olarak giriş dosyalarının sağlaması tarafından yakalanmayan her şeyi temsil eder (FileWriteAction için yazılan verilerin sağlama toplamıdır).

Ayrıca, hâlâ geliştirilme aşamasında olan ve çok fazla defa önbelleğe gitmekten kaçınmak için geçişli karmalar kullanan son derece deneysel bir "yukarıdan aşağıya işlem önbelleği" vardır.

Giriş bulma ve giriş ayıklama

Bazı işlemler, yalnızca bir dizi girdiye sahip olmaktan daha karmaşıktır. Bir işlemin giriş grubundaki değişiklikler iki biçimde gerçekleşir:

  • Bir eylem, yürütmeden önce yeni girişler keşfedebilir veya bazı girişlerinin aslında gerekli olmadığına karar verebilir. Standart örnek C++'dır. "Bu, Google'ın geçişli kapanışında hangi başlık dosyalarını kullandığı hakkında bilgiye dayalı bir tahminde bulunmak için daha iyidir. "Bu, her dosyayı uzak yürütücülere göndermek zorunda kalmadığımız için" her üst bilgi dosyasını "giriş" olarak kaydetmeme seçeneğimizdir. Şu anda bu tür başlıklarda aşırı kablolu olarak kullanılan kaynak dosyaları tarar ve bu verileri yalnızca aşırı kablolanmış başlıklar için tarama seçeneği sunulur. Bu şekilde,#include
  • Bir işlem, yürütme sırasında bazı dosyaların kullanılmadığını fark edebilir. C++'ta buna ".d dosyaları" adı verilir. Derleyici, olaydan sonra hangi başlık dosyalarının kullanıldığını bildirir ve Bazel, Make'den daha kötü bir artımlılığa sahip olmanın mahcup olmasını önlemek için bu olguyu kullanır. Derleyiciye bağlı olduğu için bu yöntem, dahil etme tarayıcısından daha iyi bir tahmin sunar.

Bunlar, Action'daki yöntemler kullanılarak uygulanır:

  1. Action.discoverInputs() çağrıldı. Gerekli olduğu belirlenen iç içe yerleştirilmiş bir Yapı grubu döndürmelidir. Bunların kaynak yapılar olması gerekir. Böylece işlem grafiğinde, yapılandırılmış hedef grafikte eşdeğeri olmayan bağımlılık kenarları bulunmaz.
  2. İşlem, Action.execute() çağrısı yapılarak yürütülür.
  3. Action.execute() sona erdiğinde, işlem Action.updateInputs() çağrısı yaparak Bazel'a tüm girişlerinin gerekli olmadığını söyleyebilir. Kullanılan bir girişin kullanılmadığı bildirilirse bu durum hatalı artımlı derlemelere yol açabilir.

Bir işlem önbelleği yeni bir Action örneğinde (ör. sunucu yeniden başlatıldıktan sonra oluşturulan) bir isabet döndürdüğünde Bazel, giriş grubunun daha önce yapılan giriş keşfi ve ayıklama sonucunu yansıtması için updateInputs()'nin kendisini çağırır.

Starlark işlemleri, ctx.actions.run() öğesinin unused_inputs_list= bağımsız değişkenini kullanarak bazı girişleri kullanılmamış olarak bildirmek için bu özellikten yararlanabilir.

İşlem yürütmenin çeşitli yolları: Stratejiler/İşlem Bağlamları

Bazı işlemler farklı şekillerde çalıştırılabilir. Örneğin, bir komut satırı yerel olarak, yerel olarak ancak çeşitli korumalı alanlarda veya uzaktan yürütülebilir. Bunu içeren kavrama ActionContext (veya yeniden adlandırmayı sadece yarı yarıya yolladığımız için Strategy) denir.

İşlem bağlamının yaşam döngüsü aşağıdaki gibidir:

  1. Yürütme aşaması başladığında, BlazeModule örneklerine hangi işlem bağlamlarına sahip oldukları sorulur. Bu, ExecutionTool oluşturucuda gerçekleşir. İşlem bağlam türleri, ActionContext alt arayüzünü ifade eden ve işlem bağlamının uygulanması gereken bir Java Class örneği tarafından tanımlanır.
  2. Mevcut olanlardan uygun işlem bağlamı seçilir ve ActionExecutionContext ve BlazeExecutor'a yönlendirilir .
  3. İşlemler, ActionExecutionContext.getContext() ve BlazeExecutor.getStrategy() kullanan bağlamları ister (bunu yapmanın gerçekten tek bir yolu olmalıdır...)

Stratejiler, işlerini yapmaları için diğer stratejileri çağırmak ücretsizdir. Bu, örneğin işlemleri hem yerel olarak hem de uzaktan başlatan ve daha sonra hangisi önce biterse onu kullanan dinamik stratejide kullanılır.

Kayda değer stratejilerden biri kalıcı çalışan süreçleri (WorkerSpawnStrategy) uygulayan yöntemdir. Bu fikir, bazı araçların uzun bir başlatma süresinin olduğu ve bu nedenle, her işlem için yeni bir işlem başlatmak yerine eylemler arasında yeniden kullanılması gerektiğidir (Bazel, çalışan sürecinin bireysel istekler arasında gözlemlenebilir durum taşımayacağı vaadine bağlı olduğu için bu, potansiyel bir doğruluk sorunu teşkil eder.)

Araç değişirse çalışan işlemin yeniden başlatılması gerekir. Bir çalışanın yeniden kullanılıp kullanılamayacağı, WorkerFilesHash kullanılarak kullanılan araç için bir sağlama toplamı hesaplanarak belirlenir. Hangi işlem girişlerinin aracın parçasını, hangilerinin ise girişleri temsil ettiğinin bilinmesine bağlıdır. Bu, işlemi oluşturan kişi tarafından belirlenir: Spawn.getToolFiles() ve Spawn çalıştırma dosyaları aracın bölümleri olarak sayılır.

Stratejiler (veya eylem bağlamları) hakkında daha fazla bilgi edinin:

  • İşlem yürütmeye yönelik çeşitli stratejilerle ilgili bilgileri burada bulabilirsiniz.
  • Dinamik strateji hakkında, hangi işlemin önce tamamlandığını görmek için hem yerel hem de uzaktan bir işlem yaptığımız burada bulunan bilgiler.
  • Yerel olarak işlem yürütmenin incelikleri hakkında bilgiyi burada bulabilirsiniz.

Yerel kaynak yöneticisi

Bazel, birçok işlemi paralel olarak çalıştırabilir. Paralel olarak çalıştırılması gereken yerel işlemlerin sayısı, işlemden işleme farklılık gösterir: Bir işlem ne kadar çok kaynak gerektirirse yerel makinenin aşırı yüklenmesini önlemek için aynı anda daha az örnek çalıştırılmalıdır.

Bu, ResourceManager sınıfında uygulanır: Her işlem, gerektirdiği yerel kaynakların bir tahminiyle ResourceSet örneği (CPU ve RAM) şeklinde açıklanmalıdır. Daha sonra işlem bağlamları yerel kaynaklar gerektiren bir işlem yaptığında, ResourceManager.acquireResources() yöntemini çağırır ve gerekli kaynaklar kullanılabilir olana kadar engellenir.

Yerel kaynak yönetimiyle ilgili daha ayrıntılı bir açıklamayı burada bulabilirsiniz.

Çıkış dizininin yapısı

Her işlem, çıkış dizininde çıktılarının yerleştirildiği ayrı bir yer gerektirir. Türetilen yapıların konumu genellikle şu şekildedir:

$EXECROOT/bazel-out/<configuration>/bin/<package>/<artifact name>

Belirli bir yapılandırmayla ilişkili dizinin adı nasıl belirlenir? İhtiyaç duyulan birbiriyle çelişen iki özellik vardır:

  1. Aynı derlemede iki yapılandırma kullanılabiliyorsa bunların her ikisinin de aynı işlem için kendi sürümü olması için farklı dizinler olmalıdır. Aksi takdirde, aynı çıkış dosyasını üreten bir işlemin komut satırı gibi iki yapılandırma aynı fikirde değilse Bazel hangi işlemi seçeceğini ("işlem çakışması") bilemez.
  2. İki yapılandırma "kabaca" aynı şeyi temsil ediyorsa komut satırları eşleşirse birinde yürütülen işlemlerin diğeri için yeniden kullanılabilmesi için aynı ada sahip olması gerekir: Örneğin, Java derleyicisinde komut satırı seçeneklerinde yapılan değişiklikler, C++ derleme işlemlerinin yeniden çalıştırılmasına neden olmamalıdır.

Şu ana kadar bu sorunu çözmek için, yapılandırma kırpma işlemiyle benzerlikler taşıyan ilkeli bir yöntem bulamadık. Seçenekler hakkında daha uzun bir açıklamayı burada bulabilirsiniz. Başlıca sorunlu alanlar, Starlark kuralları (yazarları genellikle Bazel'e pek aşina değildir) ve "aynı" çıktı dosyasını üretebilecek şeyler alanına başka bir boyut ekleyen yönlerdir.

Mevcut yaklaşımda, yapılandırmanın yol segmenti, çeşitli son eklerle birlikte <CPU>-<compilation mode> şeklindedir. Böylece, Java'da uygulanan yapılandırma geçişleri, işlem çakışmalarına neden olmaz. Ayrıca, kullanıcıların işlem çakışmalarına neden olmaması için Starlark yapılandırma geçişleri kümesinin bir sağlaması eklenir. Bu, mükemmel olmaktan çok uzak. Bu, OutputDirectories.buildMnemonic() içinde uygulanır ve her yapılandırma parçasının çıkış dizini adına kendi parçasını eklemesine dayanır.

Testler

Bazel, testlerin çalıştırılması için zengin destek sunar. Şunları destekler:

  • Testleri uzaktan çalıştırma (uzaktan yürütme arka ucu varsa)
  • Testleri paralel olarak birden çok kez çalıştırma (zamanlama verilerini kaldırmak veya toplamak için)
  • Parçalama testleri (hız için aynı testte test senaryolarını birden fazla işleme bölme)
  • Kesintili testleri yeniden çalıştırma
  • Testleri test paketlerinde gruplandırma

Testler, testin nasıl çalışması gerektiğini açıklayan bir TestProvider'a sahip normal yapılandırılmış hedeflerdir:

  • Yapısı, testin çalıştırılmasıyla sonuçlanan yapılar. Bu, serileştirilmiş TestResultData mesajı içeren bir "önbellek durumu" dosyasıdır
  • Testin kaç kez çalıştırılması gerektiği
  • Testin bölünmesi gereken parça sayısı
  • Testin nasıl çalıştırılması gerektiğiyle ilgili bazı parametreler (test zaman aşımı gibi)

Hangi testlerin çalıştırılacağını belirleme

Hangi testlerin çalıştırılacağını belirlemek ayrıntılı bir süreçtir.

İlk olarak, hedef kalıp ayrıştırma sırasında test paketleri tekrarlı olarak genişletilir. Genişletme, TestsForTargetPatternFunction işlevinde uygulanır. Biraz şaşırtıcı olsa da bir test paketinde hiç test belirtilmemişse bunun paketindeki tüm testlere karşılık gelmesidir. Bu işlem, paket kurallarını test etmek için $implicit_tests adlı dolaylı bir özellik eklenerek Package.beforeBuild() içinde uygulanır.

Daha sonra testler, komut satırı seçeneklerine göre boyut, etiketler, zaman aşımı ve dile göre filtrelenir. Bu, TestFilter içinde uygulanır ve hedef ayrıştırma sırasında TargetPatternPhaseFunction.determineTests() öğesinden çağrılır ve sonuç TargetPatternPhaseValue.getTestsToRunLabels() içine yerleştirilir. Filtrelenebilen kural özelliklerinin yapılandırılmamasının nedeni, bu durumun analiz aşamasından önce gerçekleşmesi ve bu nedenle yapılandırmanın mevcut olmamasıdır.

Bu işlem daha sonra BuildView.createResult() ürününde daha ayrıntılı bir şekilde işlenir: Analizi başarısız olan hedefler filtrelenir ve testler, özel ve münhasır olmayan testlere bölünür. Daha sonra, AnalysisResult içine yerleştirilir. Böylece, ExecutionTool hangi testlerin çalıştırılacağını bilir.

Bu ayrıntılı sürece biraz şeffaflık kazandırmak amacıyla, komut satırında belirli bir hedef belirtildiğinde hangi testlerin çalıştırıldığını bildirmek için tests() sorgu operatörü (TestsFunction ürününde uygulanır) kullanılabilir. Ne yazık ki bu bir yeniden düzenleme işlemi. Bu nedenle, muhtemelen yukarıdakinden farklı şekillerde farklılık gösteriyor.

Test çalıştırma

Testlerin çalıştırılma şekli, önbellek durumu yapıları istemektir. Bunun ardından bir TestRunnerAction yürütülür ve bu yürütme, testi istenen şekilde çalıştıran --test_strategy komut satırı seçeneği tarafından seçilen TestActionContext öğesini çağırır.

Testler, testlere kendilerinden ne beklendiğini bildirmek için ortam değişkenlerini kullanan ayrıntılı bir protokole göre çalıştırılır. Bazel'ın testlerden ne beklediği ve Bazel'den hangi testler beklenebileceği hakkında ayrıntılı açıklamayı burada bulabilirsiniz. En basit ifadeyle, 0 çıkış kodu başarılı, diğer tüm değerler başarısız anlamına gelir.

Önbellek durumu dosyasına ek olarak, her test işlemi bir dizi başka dosya yayınlar. Bunlar, hedef yapılandırmanın çıkış dizininin testlogs adlı alt dizini olan "test günlüğü dizinine" yerleştirilir:

  • test.xml, test parçasındaki bağımsız test durumlarını ayrıntılı bir şekilde açıklayan JUnit tarzı bir XML dosyasıdır
  • test.log testin konsol çıkışı. stdout ve stderr ayrılmaz.
  • test.outputs, "bildirilmemiş çıkışlar dizini"dir. Terminale yazdırdıklarının yanı sıra dosya çıktısı almak isteyen testler tarafından kullanılır.

Test yürütme sırasında normal hedefler oluşturulurken mümkün olmayan iki şey olabilir: özel test yürütme ve çıkış akışı.

Bazı testlerin özel modda yürütülmesi gerekir (ör. diğer testlere paralel olarak değil). Bu, test kuralına tags=["exclusive"] eklenerek veya testi --test_strategy=exclusive ile çalıştırılarak sağlanabilir . Her özel test, "ana" derlemeden sonra testin yürütülmesini isteyen ayrı bir Skyframe çağrısıyla çalıştırılır. Bu, SkyframeExecutor.runExclusiveTest() içinde uygulanır.

İşlem bittiğinde terminal çıktısı düşen normal işlemlerin aksine kullanıcı, uzun süreli bir testin ilerlemesi hakkında bilgi almak için test çıkışının akış şeklinde bulunmasını isteyebilir. Bu, --test_output=streamed komut satırı seçeneğiyle belirtilir ve farklı testlerin çıkışlarının parçalara ayrılmaması için özel test yürütme anlamına gelir.

Bu işlem, uygun şekilde adlandırılmış StreamedTestOutput sınıfında uygulanır ve söz konusu testin test.log dosyasındaki değişiklikler sorgulanarak ve yeni baytlar, Bazel'in kurallarının bulunduğu terminale dökülerek yapılır.

Yürütülen testlerin sonuçları, çeşitli etkinliklerin (TestAttempt, TestResult veya TestingCompleteEvent gibi) gözlemlenmesiyle etkinlik veri yolunda kullanılabilir. Bu testlerin sonuçları, Derleme Etkinliği Protokolü'ne aktarılır ve AggregatingTestListener tarafından konsola yayınlanır.

Kapsam koleksiyonu

Kapsam, testler tarafından dosyalardaki LCOV biçiminde raporlanır. bazel-testlogs/$PACKAGE/$TARGET/coverage.dat

Kapsamı toplamak için her test yürütmesi collect_coverage.sh adlı bir komut dosyası içinde sarmalanır .

Bu komut dosyası, kapsam toplamayı etkinleştirmek ve kapsam dosyalarının kapsam çalışma zamanları tarafından nereye yazıldığını belirlemek için test ortamını oluşturur. Ardından testi çalıştırır. Bir test birden fazla alt işlem çalıştırabilir ve birden çok farklı programlama dilinde (ayrı kapsam toplama çalışma zamanlarıyla) yazılmış parçalardan oluşabilir. Sarmalayıcı komut dosyası, gerektiğinde elde edilen dosyaları LCOV biçimine dönüştürmekten ve tek bir dosyada birleştirir.

collect_coverage.sh etkileşimi test stratejileri tarafından yapılır ve collect_coverage.sh öğesinin testin girişlerinde olması gerekir. Bu işlem, --coverage_support yapılandırma işaretinin değerine çözümlenen dolaylı özellik :coverage_support ile gerçekleştirilir (bkz. TestConfiguration.TestOptions.coverageSupport)

Bazı dillerde çevrimdışı araçlar kullanılır. Diğer bir deyişle, kapsam araçları derleme sırasında eklenir (C++ gibi) ve diğerleri online araçlar kullanır. Diğer bir deyişle, kapsam araçları yürütme sırasında eklenir.

Diğer bir temel kavram ise referans kapsamıdır. Bu, içinde herhangi bir kodun çalıştırılıp çalıştırılmadığını veya testin kapsamıdır. Çözümün çözdüğü sorun, bir ikili program için test kapsamını hesaplamak istediğinizde tüm testlerin kapsamını birleştirmenin yeterli olmamasıdır. Çünkü ikili programda herhangi bir teste bağlı olmayan kod olabilir. Bu nedenle, yalnızca kapsama dahil ettiğimiz satırların olmadığı, yalnızca kapsama aldığımız dosyaları içeren her ikili program için bir kapsam dosyası yayınlarız. Bir hedef için temel kapsam dosyası bazel-testlogs/$PACKAGE/$TARGET/baseline_coverage.dat şeklindedir . --nobuild_tests_only işaretini Bazel'e geçirirseniz testlerin yanı sıra ikili programlar ve kitaplıklar için de oluşturulur.

Temel kapsam şu anda kapalı.

Her kural için kapsam toplama amacıyla iki dosya grubu izleriz: enstrümantasyonlu dosya grubu ve araç meta veri dosyaları grubu.

Enstrümanlı dosya kümesi tam da araç için bir dizi dosyadan ibarettir. Çevrimiçi kapsama çalışma zamanlarında bu, hangi dosyaların kullanılacağına karar vermek için çalışma zamanında kullanılabilir. Temel kapsamı uygulamak için de kullanılır.

Araç meta veri dosyaları grubu, bir testin Bazel'ın bundan ihtiyaç duyduğu LCOV dosyalarını oluşturmak için ihtiyaç duyduğu ekstra dosyalar kümesidir. Pratikte bu, çalışma zamanına özel dosyalardan oluşur. Örneğin, gcc derleme sırasında .gcno dosyalarını yayar. Kapsam modu etkinse bunlar test işlemlerinin giriş grubuna eklenir.

Kapsamın toplanıp toplanmadığı BuildConfiguration içinde depolanır. Bu, test işlemini ve eylem grafiğini bu bite bağlı olarak değiştirmenin kolay bir yolu olduğu için kullanışlıdır. Ancak aynı zamanda, bu bit döndürülürse tüm hedeflerin yeniden analiz edilmesi gerekir (C++ gibi bazı diller, kapsamı toplayabilecek bir kod oluşturmak için farklı derleyici seçenekleri gerektirir ve bu da bu sorunu bir şekilde azaltır). Çünkü bu durumda yeniden analiz yapılması gerekir.

Kapsam desteği dosyaları, etiketler aracılığıyla dolaylı bağımlılık oluşturur. Böylece, Bazel'in farklı sürümleri arasında farklılık gösterebilmelerine olanak tanıyan çağrı politikası tarafından geçersiz kılınabilirler. İdeal olarak, bu farklar kaldırılır ve biz de bunlardan birini standart hale getirdik.

Ayrıca, bir Bazel çağrısındaki her test için toplanan kapsamı birleştiren bir "kapsam raporu" oluştururuz. Bu işlem CoverageReportActionFactory tarafından işlenir ve BuildView.createResult() içinden çağrılır . Yürütülen ilk testin :coverage_report_generator özelliğine bakarak ihtiyacı olan araçlara erişim sağlar.

Sorgu motoru

Bazel'in çeşitli grafikler hakkında çeşitli sorular sormak için küçük bir dili vardır. Aşağıdaki sorgu türleri sağlanır:

  • bazel query, hedef grafiği incelemek için kullanılır
  • bazel cquery, yapılandırılmış hedef grafiğini incelemek için kullanılır
  • bazel aquery, işlem grafiğini incelemek için kullanılır

Bunların her biri, AbstractBlazeQueryEnvironment alt sınıfına göre uygulanır. QueryFunction alt sınıflandırılarak ek sorgu işlevleri yapılabilir. Sorgu sonuçlarının akışına izin vermek amacıyla, sonuçları bir veri yapısına toplamak yerine QueryFunction işlevine bir query2.engine.Callback iletilir. Bu parametre, döndürmek istediği sonuçlar için çağrıda bulunur.

Bir sorgunun sonucu çeşitli şekillerde yayınlanabilir: Etiketler, etiketler ve kural sınıfları, XML, protobuf vb. Bunlar, OutputFormatter alt sınıfları olarak uygulanır.

Bazı sorgu çıktısı biçimlerinin (proto, kesinlikle) küçük bir gereksinimi de Bazel'in çıktıyı farklılaştırmak ve belirli bir hedefin değişip değişmediğini belirlemek için paket yüklemesinin sağladığı tüm bilgileri yayınlaması gerektiğidir. Sonuç olarak, özellik değerlerinin seri hale getirilebilir olması gerekir. Bu nedenle, karmaşık Starlark değerlerine sahip öznitelikleri olmayan çok az özellik türü vardır. Olağan geçici çözüm, bir etiket kullanmak ve karmaşık bilgileri bu etikete sahip kurala eklemektir. Bu pek tatmin edici bir çözüm değildir ve bu gereksinimi kaldırmak çok güzel olacaktır.

Modül sistemi

Bazel, modüller eklenerek genişletilebilir. Her modülün BlazeModule alt sınıfı (adı, Blaze olarak adlandırıldığı eski Bazel tarihinin kalıntılarıdır) ve bir komutun yürütülmesi sırasında çeşitli etkinlikler hakkında bilgi alması gerekir.

Çoğunlukla, yalnızca bazı Bazel sürümlerinin (Google'da kullandığımız gibi) ihtiyaç duyduğu, "temel olmayan" işlevlerin çeşitli parçalarını uygulamak için kullanılırlar:

  • Uzaktan yürütme sistemlerine yönelik arayüzler
  • Yeni komutlar

BlazeModule tarafından sunulan uzantı puanları grubu biraz şanssız. Bunu iyi tasarım ilkeleri örneği olarak kullanmayın.

Etkinlik otobüsü

BlazeModules'un Bazel'in geri kalanıyla iletişim kurmak için kullandığı ana yöntem bir etkinlik veri yoludur (EventBus): Her derleme için yeni bir örnek oluşturulur, Bazel'in çeşitli bölümleri bu örnekte etkinlik yayınlayabilir ve modüller, ilgilendikleri etkinlikler için dinleyicileri kaydedebilir. Örneğin, aşağıdakiler etkinlik olarak gösterilir:

  • Oluşturulacak derleme hedefleri listesi belirlendi (TargetParsingCompleteEvent)
  • Üst düzey yapılandırmalar belirlendi (BuildConfigurationEvent)
  • Bir hedefin oluşturulup oluşturulmadığı (TargetCompleteEvent)
  • Test çalıştırıldı (TestAttempt, TestSummary)

Bu etkinliklerden bazıları Etkinlik Oluşturma Protokolü'nde Bazel dışında temsil edilir (BuildEvent olarak adlandırılır). Bu, yalnızca BlazeModule'lerin değil, aynı zamanda Bazel işleminin dışındaki öğelerin de yapıyı gözlemlemesine olanak tanır. Bunlara, protokol mesajları içeren bir dosya olarak veya Bazel, etkinlikleri akışla aktarmak için bir sunucuya (Derleme Etkinliği Hizmeti denir) bağlanabilir.

Bu uygulama build.lib.buildeventservice ve build.lib.buildeventstream Java paketlerinde uygulanır.

Harici depolar

Bazel esasen bir monorepoda (inşa etmek için gereken her şeyi içeren tek kaynaklı bir ağaç) kullanılmak üzere tasarlanmış olsa da Bazel bunun doğru olmadığı bir dünyada yaşıyor. "Harici depolar" bu iki dünyayı birbirine bağlamak için kullanılan bir soyutlamadır. Bunlar, derleme için gerekli olan ancak ana kaynak ağacında yer almayan kodu temsil eder.

WORKSPACE dosyası

Harici depo kümesi, WORKSPACE dosyasının ayrıştırılmasıyla belirlenir. Örneğin, aşağıdaki gibi bir beyan:

    local_repository(name="foo", path="/foo/bar")

@foo adlı kod deposundaki sonuçlar kullanılabilir. Bu işin karmaşık hale geldiği durumlarda, Starlark dosyalarında yeni depo kuralları tanımlanabilir ve bu kurallar daha sonra yeni Starlark kodu yüklemek için kullanılabilir. Bu kodlar yeni depo kuralları tanımlamak için kullanılır.

Bu durumu işlemek için WORKSPACE dosyasının ayrıştırılması (WorkspaceFileFunction konumunda), load() ifadeleriyle tanımlanan parçalara bölünür. Yığın dizini WorkspaceFileKey.getIndex() ile gösterilir ve X dizini için WorkspaceFileFunction hesaplaması, X. load() ifadesine kadar değerlendirilmesi anlamına gelir.

Kod depoları getiriliyor

Kod deposunun kodu Bazel'in kullanımına sunulmadan önce fetched gerekir. Bu işlem sonucunda Bazel, $OUTPUT_BASE/external/<repository name> altında bir dizin oluşturur.

Kod deposunu getirme işlemi, aşağıdaki adımlarla gerçekleşir:

  1. PackageLookupFunction, bir kod deposuna ihtiyacı olduğunu fark eder ve SkyKey olarak RepositoryName oluşturur. Bu da RepositoryLoaderFunction
  2. RepositoryLoaderFunction, net olmayan nedenlerle isteği RepositoryDelegatorFunction adresine yönlendirir (kod, Skyframe yeniden başlatıldığında bir şeylerin yeniden indirilmesinden kaçınmasını söylediğini belirtir, ancak bu pek mantıklı bir neden değildir)
  3. RepositoryDelegatorFunction, istenen depo bulunana kadar WORKSPACE dosyasının parçalarını iterasyon yaparak getirmesi istenen depo kuralını öğrenir
  4. Depo getirme işlemini uygulayan uygun RepositoryFunction bulunur. Bu, deponun Starlark uygulaması veya Java'da uygulanan depolar için sabit kodlu bir haritadır.

Kod deposunu getirmek çok pahalı olabileceği için çeşitli önbelleğe alma katmanları vardır:

  1. Sağlama toplamı (RepositoryCache) tarafından anahtarlanan, indirilen dosyalar için bir önbellek vardır. Bu, sağlama toplamının WORKSPACE dosyasında bulunmasını gerektirir, ancak yine de hermetiklik için bu iyi bir sonuçtur. Bu erişim, hangi çalışma alanı veya çıkış tabanında çalıştıklarından bağımsız olarak aynı iş istasyonundaki her Bazel sunucu örneği tarafından paylaşılır.
  2. $OUTPUT_BASE/external altındaki her depo için bir "işaretçi dosyası", dosyayı getirmek amacıyla kullanılan kuralın sağlama toplamını içeren bir yazılır. Bazel sunucusu yeniden başlatılırsa ancak sağlama toplamı değişmezse tekrar getirilmez. Bu, RepositoryDelegatorFunction.DigestWriter özelliğinde uygulanır .
  3. --distdir komut satırı seçeneği, indirilecek yapıları aramak için kullanılan başka bir önbellek belirtir. Bu, Bazel'ın internetten rastgele bir şey getirmemesi gerektiği kurumsal ayarlarda yararlıdır. Bu, DownloadManager tarafından uygulanır .

Bir depo indirildikten sonra içindeki yapılar kaynak yapılar olarak değerlendirilir. Bu, bir sorun oluşturur. Çünkü Bazel genellikle kaynak yapıların güncelliğini bunlara stat() çağrısı yaparak kontrol eder ve bu yapılar, değişiklik oldukları deponun tanımı değiştiğinde de geçersiz kılınır. Bu nedenle, harici bir depodaki bir yapı için FileStateValue özelliklerinin harici depoya bağlı olması gerekir. Bu işlem ExternalFilesHelper tarafından gerçekleştiriliyor.

Depo eşlemeleri

Birden fazla depo, aynı depoya, ancak farklı sürümlerde (bu, "elmas bağımlılık sorunu"nun bir örneğidir) bağlı olmak isteyebilir. Örneğin, derlemedeki ayrı depolardaki iki ikili program Guava'ya güvenmek isterse her ikisi de @guava// ile başlayan etiketlerle Guava'ya atıfta bulunur ve bunun farklı sürümleri anlamına gelmesini bekler.

Bu nedenle Bazel, harici depo etiketlerinin yeniden eşlenmesine izin verir. Böylece @guava// dizesi, bir ikili programın deposunda bulunan bir Guava deposuna (@guava1// gibi) ve başka bir Guava deposuna (@guava2// gibi) diğerinin deposuna referans verebilir.

Alternatif olarak bu, elmaslara join için de kullanılabilir. Bir depo @guava1//, diğeri ise @guava2// kullanıyorsa depo eşlemesi, standart @guava// deposu kullanmak için bir deponun her iki depoyu yeniden eşlemesine izin verir.

Eşleme, WORKSPACE dosyasında bağımsız depo tanımlarının repo_mapping özelliği olarak belirtilir. Ardından Skyframe'de WorkspaceFileValue grubunun üyesi olarak görünür ve burada şu bağlantıya bağlanır:

  • Package.Builder.repositoryMapping, paketteki kuralların etiket değerli özelliklerini RuleClass.populateRuleAttributeValues()
  • Package.repositoryMapping, analiz aşamasında (yükleme aşamasında ayrıştırılmamış $(location) gibi şeyleri çözmek için) kullanılır
  • load() ifadelerindeki etiketleri çözmek için BzlLoadFunction

JNI bitleri

Bazel sunucusu çoğunlukla Java dilinde yazılır. Bunun istisnası, Java'yı uyguladığımızda kendi başına yapamayacağı ya da kendi başına yapamayacağı bölümlerdir. Bu işlem çoğunlukla dosya sistemi, süreç kontrolü ve diğer alt düzey öğelerle etkileşimle sınırlıdır.

C++ kodu src/main/native altında bulunur ve yerel yöntemlere sahip Java sınıfları şunlardır:

  • NativePosixFiles ve NativePosixFileSystem
  • ProcessUtils
  • WindowsFileOperations ve WindowsFileProcesses
  • com.google.devtools.build.lib.platform

Konsol çıkışı

Konsol çıkışını yaymak basit bir şey gibi görünse de birden fazla işlemi (bazen uzaktan çalıştırmanın), ayrıntılı önbelleğe almanın, güzel ve renkli bir terminal çıkışına sahip olma arzusu ve uzun süredir çalışan bir sunucuya sahip olma arzusu bu işi son derece basit hale getirir.

RPC çağrısı istemciden alındıktan hemen sonra, içine yazdırılan verileri istemciye ileten iki RpcOutputStream örneği oluşturulur (stdout ve stderr için). Bunlar daha sonra bir OutErr (stdout, stderr) çifti içinde sarmalanır. Konsolda yazdırılması gereken her şey bu akışlardan geçer. Daha sonra bu akışlar BlazeCommandDispatcher.execExclusively() adlı ağa aktarılır.

Çıkış, varsayılan olarak ANSI kaçış sıralarıyla yazdırılır. Bunlar istenmediğinde (--color=no) bir AnsiStrippingOutputStream ile çıkarılır. Buna ek olarak, System.out ve System.err bu çıkış akışlarına yönlendirilir. Böylece hata ayıklama bilgileri System.err.println() kullanılarak yazdırılabilir ve yine de istemcinin terminal çıktısına (sunucununkinden farklıdır) gönderilir. Bir işlem ile ikili çıkış üretilirse (bazel query --output=proto gibi) stdout'ların düzenlenmeyeceğine dikkat edilir.

Kısa mesajlar (hatalar, uyarılar vb.), EventHandler arayüzü aracılığıyla ifade edilir. Özellikle, bunlar EventBus üzerinde paylaşılan yayınlardan farklıdır (bu, kafa karıştırıcıdır). Her Event bir EventKind (hata, uyarı, bilgi ve birkaçı daha) içerir ve Location (kaynak kodunda etkinliğin gerçekleşmesine neden olan yer) bulunabilir.

Bazı EventHandler uygulamaları, aldıkları etkinlikleri depolar. Bu işlev, önbelleğe alınan çeşitli işlemlerin (ör. önbelleğe alınmış yapılandırılmış bir hedefin yayınladığı uyarılar) neden olduğu bilgileri kullanıcı arayüzünde tekrar oynatmak için kullanılır.

Bazı EventHandler'ler, sonunda etkinlik veri yoluna giden yolların yayınlanmasına da izin verir (normal Event'ler orada _görünmez). Bunlar ExtendedEventHandler uygulamalarıdır ve ana kullanımları, önbelleğe alınan EventBus etkinliklerini tekrar oynatmaktır. Bu EventBus etkinliklerinin tümü Postable işlemini uygular, ancak EventBus alanında gönderilen her şey bu arayüzü uygulamaz. Yalnızca ExtendedEventHandler tarafından önbelleğe alınanlar iyi olur ve çoğu şeyde bulunur, ancak zorunlu kılınmaz)

Terminal çıkışı çoğunlukla UiEventHandler üzerinden yayınlanır. Bu, Bazel'ın yaptığı tüm süslü çıkış biçimlendirme ve ilerleme raporlamasından sorumludur. İki girişi vardır:

  • Etkinlik otobüsü
  • Bildirici aracılığıyla içine aktarılan etkinlik akışı

Komut yürütme makinesinin (örneğin, Bazel'in geri kalanı) istemciye yönelik RPC akışıyla tek doğrudan bağlantısı, bu akışlara doğrudan erişim sağlayan Reporter.getOutErr() üzerindendir. Yalnızca bir komutun büyük miktarlarda olası ikili veri (bazel query gibi) dökümünü alması gerektiğinde kullanılır.

Bazel'in Profili Oluşturma

Bazel hızlı. Bazel aynı zamanda yavaştır, çünkü yapı daha dayanılması gerekene kadar büyümeye devam etmektedir. Bu nedenle Bazel, profil derlemeleri ve Bazel'in kendisi için kullanılabilecek bir profil aracı içerir. Doğru bir şekilde Profiler adlı bir sınıfta uygulanır. Bu özellik varsayılan olarak açıktır, ancak ek yükünün tolere edilebilir olması için yalnızca kısaltılmış verileri kaydeder; --record_full_profiler_data komut satırı, yapabildiği her şeyi kaydetmesini sağlar.

Chrome profil aracı biçiminde bir profil oluşturur; en iyi Chrome'da görüntülenir. Bu veri modeli, görev yığınlarıdır: Bir kişinin görevlere başlayabileceği ve görevleri sonlandırabileceği ve bunların birbirlerinin içine düzenli bir şekilde yerleştirilmesi gerekir. Her Java iş parçacığının kendi görev yığını vardır. TODO: Bu, işlemler ve devamlı geçiş stiliyle nasıl çalışır?

Profil oluşturucu sırasıyla BlazeRuntime.initProfiler() ve BlazeRuntime.afterCommand() adlı ülkelerde başlatılıp durdurulur ve her şeyin profilini çıkarabilmemiz için mümkün olduğunca uzun süre yayında kalmaya çalışır. Profile bir şeyler eklemek için Profiler.instance().profile() numaralı telefonu arayın. Kapanış değeri görevin sonunu temsil eden bir Closeable döndürür. Bu yöntemin en iyi kullanım alanı, “kaynakları deneyin” ifadeleridir.

MemoryProfiler ürününde de temel bellek profili oluşturma işlemi yaparız. Ayrıca her zaman açıktır ve çoğunlukla maksimum yığın boyutlarını ve GC davranışını kaydeder.

Bazel'i test etme

Bazel'da iki ana test türü vardır: Bazel'i "kara kutu" olarak gözlemleyenler ve yalnızca analiz aşamasını yürütenler. Birincisine "entegrasyon testleri" ve ikincisine "birim testleri" diyoruz, ancak bunlar daha az entegre olan entegrasyon testlerine benzerler. Ayrıca, gerekli olan bazı gerçek birim testlerimiz de vardır.

Entegrasyon testlerinin iki türü vardır:

  1. src/test/shell altında, oldukça ayrıntılı bir bash testi çerçevesi kullanılarak uygulanan uygulamalar
  2. Java'da uygulananlar. Bunlar, BuildIntegrationTestCase alt sınıfları olarak uygulanır

Çoğu test senaryosu için uygun donanıma sahip olduğundan tercih edilen entegrasyon test çerçevesi BuildIntegrationTestCase'dir. Bir Java çerçevesi olduğundan hata ayıklaması ve birçok yaygın geliştirme aracıyla sorunsuz entegrasyon sağlar. Bazel deposunda birçok BuildIntegrationTestCase sınıfı örneği bulabilirsiniz.

Analiz testleri, BuildViewTestCase alt sınıfları olarak uygulanır. BUILD dosyalarını yazmak için kullanabileceğiniz bir referans dosya sistemi bulunur. Ardından, çeşitli yardımcı yöntemler yapılandırılmış hedefler isteyebilir, yapılandırmayı değiştirebilir ve analizin sonucu hakkında çeşitli şeyler söyleyebilir.