Bazel kod tabanı

Sorun bildirin Kaynağı göster

Bu belge, kod tabanının ve Bazel'ın yapısının bir açıklamasıdır. Bu özellik, 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önde bulunan tepelerde neler olduğunu çok az kişi bilir.

Yolculuğun ortasında, yolun ortasında kaybolan bir ormanın karanlıkta kaybolmamaları için bu belge, kod tabanıyla ilgili genel bir bakış sunarak üzerinde çalışmaya başlamayı kolaylaştırmayı amaçlar.

Bazel'in kaynak kodunun herkese açık sürümü, github.com/bazelbuild/bazel adresindeki GitHub'da yer alır. Bu, "bilgi kaynağı" değildir. Google dışında yararlı olmayan ek işlevler içeren Google dahili kaynak ağacından türetilir. Uzun vadeli hedef, GitHub'ı bilginin kaynağı yapmaktır.

Katkılar, normal GitHub pull istek mekanizmasıyla kabul edilir, bir Google çalışanı tarafından dahili kaynak ağacına manuel olarak içe aktarılır, ardından 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 vardır: 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 bütünü olarak etkiler. İkinci seçenek ise "komut seçeneği" yalnızca tek bir komutu etkiler.

Her sunucu örneğinin tek bir ilişkilendirilmiş kaynak ağacı ("çalışma alanı") ve her çalışma alanının genellikle tek bir etkin sunucu örneği vardır. Bu, özel bir çıkış tabanı belirterek atlatılabilir (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 edilir. Aşağıdaki adımları uygulayarak uygun bir sunucu işlemi oluşturur:

  1. Kendisini zaten ayıklamış olup olmadığını kontrol eder. Öyle değilse bu şekilde çalışır. Sunucu uygulaması da buradan gelir.
  2. Çalışan etkin bir sunucu örneği 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ına sahip 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 ona bir gRPC arayüzü üzerinden iletilir, ardından Bazel çıkışı tekrar terminale aktarılır. Aynı anda yalnızca bir komut çalışabilir. Bu, C++ parçaları ve Java'daki bölümleriyle ayrıntılı bir kilitleme mekanizması kullanılarak uygulanır. bazel version komutunu başka bir komutla paralel olarak çalıştıramamak utanç verici olduğundan birden fazla komutu paralel olarak çalıştırmak için bazı altyapılar mevcuttur. Ana engelleyici, BlazeModule saniyelerinin ve BlazeRuntime bölgesindeki bazı durumların yaşam döngüsüdür.

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

Ctrl-C tuşlarına basıldığında istemci, bunu gRPC bağlantısındaki 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'den sonra istemci, bunun yerine 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 dilindedir .

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

Dizin düzeni

Bazel, derleme sırasında biraz karmaşık bir dizin kümesi oluşturuyor. Tam açıklama Çıkış dizini düzeninde mevcuttur.

"Çalışma alanı", Bazel'ın çalıştırıldığı kaynak ağacıdır. Bu genellikle kaynak kontrolünden baktığınız bir şeye karşılık gelir.

Bazel, tüm verilerini "çıkış kullanıcı kökü" altına yerleştirir. 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'ın 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ğlamasına göre bir alt dizin alır. Varsayılan olarak $OUTPUT_USER_ROOT/install şeklindedir 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, herhangi bir çalışma alanında aynı anda yalnızca bir Bazel örneğinin çalışabileceği sınırlamasını atlatmak için yararlıdır.

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

  • $OUTPUT_BASE/external konumundaki harici depolar getirildi.
  • Geçerli derlemenin tüm kaynak koduna sembolik bağlantıları içeren bir dizin olan exec kök dizini. Adresi: $OUTPUT_BASE/execroot. Derleme sırasında çalışma dizini $EXECROOT/<name of main repository>'dir. Çok uyumsuz bir değişiklik olduğundan uzun vadeli bir plan olsa da bunu $EXECROOT olarak değiştirmeyi planlıyoruz.
  • Derleme sırasında oluşturulan dosyalar.

Bir komut yürütme süreci

Bazel sunucusu kontrolü aldıktan ve yürütmesi gereken bir komut hakkında bilgilendirildikten sonra aşağıdaki olaylar dizisi gerçekleşir:

  1. BlazeCommandDispatcher yeni istek hakkında bilgilendirildi. Komutun içinde çalışacak bir çalışma alanı gerekip gerekmediğini (sürüm veya yardım gibi kaynak kodla ilgisi olmayan komutlar dışında hemen hemen her komut) ve başka bir komutun çalışıp çalışmayacağını belirler.

  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 veriler BlazeCommand üzerinde yöntemler tarafından açıklansa güzel olurdu)

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

  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ü'nün gözetimi kapsamında Bazel'in dışına aktarılıyor.

  5. Kontrolü komut alır. En ilginç komutlar bir derleme çalıştıran komutlardır: derleme, test, ç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ı, işlem grafiğini (derleme için yürütülmesi gereken komutların yönlendirilmiş bir düzensiz grafiği) üretmek 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 bir OptionsParsingResult nesnesinde açıklanmıştır. Bu nesne, "seçenek sınıfları"ndan seçeneklerin değerlerine kadar uzanan bir harita içerir. "Seçenek sınıfı", OptionsBase alt sınıfıdır ve birbirleriyle ilişkili komut satırı seçeneklerini birlikte gruplandırır. Örneğin:

  1. Bir programlama diliyle (CppOptions veya JavaOptions) ilgili seçenekler. 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ılacak şekilde tasarlanmıştır. Bunlardan bazıları (örneğin, C++'nın tarama dahil edilip edilmeyeceğini) yürütme aşamasında okunur, ancak BuildConfiguration o zaman kullanılamadığından her zaman 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 ve bu şekilde (SkyKeys öğesinin bir kısmı gibi) kullanıldığını varsaymak isteriz. Böyle bir durum söz konusu değildir ve bunları değiştirmek, Bazel'ı hata ayıklaması zor, kolayca kırmak için gerçekten iyi bir yöntemdir. Ne yazık ki, onları gerçekten sabit bir hale getirmek büyük bir girişim. (Bir FragmentOptions, yapım işleminden hemen sonra ve başka birisi buna referansta bulunma fırsatı bulmadan ve equals() veya hashCode() çağrılmadan önce üzerinde değişiklik yapılması herhangi bir soruna yol açmaz.)

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

  1. Bazıları Bazel'e (CommonCommandOptions) bağlı olarak kullanılıyor
  2. Her Bazel komutundaki @Command ek açıklamadan
  3. ConfiguredRuleClassProvider kaynağından (bunlar tek programlama dilleriyle 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ç), @Option ek açıklamasına sahip bir FragmentOptions alt sınıfının üye değişkenidir. Bu ek açıklama, bir yardım metniyle birlikte komut satırı seçeneğinin adını ve türünü belirtir.

Bir komut satırı seçeneğinin değerinin Java türü genellikle basit bir değerdir (dize, tam sayı, Boole, etiket vb.). Bununla birlikte, daha karmaşık tür seçenekleri de desteklenmektedir. Bu durumda, komut satırı dizesinden veri türüne dönüştürme işi, com.google.devtools.common.options.Converter kapsamına girer.

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

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

Depolar

"Depo", bir 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. Bazel ise kaynak kodu birden fazla depoya yayılan projeleri destekler. Bazel'ın çağrıldığı depo "ana depo", diğerleri ise "harici depolar" olarak adlandırılır.

Depo, kök dizininde WORKSPACE (veya WORKSPACE.bazel) adlı bir dosyayla işaretlenir. Bu dosya, tüm derleme için "genel" bilgiler (ör. mevcut harici depolar grubu) içerir. Normal bir Starlark dosyası gibi çalışır; bu da diğer Starlark dosyalarını load() yapabileceğiniz anlamına gelir. Bu, genellikle açıkça referans verilen bir deponun ihtiyaç duyduğu depoları çekmek için kullanılır (buna "deps.bzl kalıbı" adını veririz)

Harici depoların kodu, $OUTPUT_BASE/external altında sembollerle bağlanır veya indirilir.

Derlemeyi çalıştırırken kaynak ağacının tamamının birleştirilmesi gerekir. Bu işlem SymlinkForest tarafından yapılır. Bu işlem, ana depodaki her paketi $EXECROOT'a ve her harici kod deposunu $EXECROOT/external veya $EXECROOT/.. ile sembollere bağlar (ilk veri havuzu, ana depoda external adlı bir paketin bulunmasını elbette imkansız hale getirir. Bu nedenle, ana depodan geçiş yapıyoruz).

Paketler

Her depo; paketlerden, ilgili dosyaların bir koleksiyonundan ve bağımlılıkların bir 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'ın üst öğesi olan 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 bu nedenle, BUILD dosyasının varlığı yinelenmeyi durduracağından BUILD dosyalarının eklenmesi veya kaldırılması diğer paketleri _değiştirebilir.

BUILD dosyaları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 sahibi olmayı gerektirir. Paket yükleme işleminin sonucunda Package nesnesi oluşur. Çoğunlukla bir dizeden (hedefin adı) hedefe uzanan bir haritadır.

Paket yükleme sırasında karmaşıklığın büyük bir kısmı globalleştirmedir: Bazel, her kaynak dosyanın açıkça listelenmesini gerektirmez ve bunun yerine glob'ları (glob(["**/*.java"]) gibi) çalıştırabilir. Kabuktan farklı olarak, alt dizinlere inen (ancak alt paketlere inmeyen) yinelemeli glob'ları destekler. Bu işlem, dosya sistemine erişim gerektirir ve bu yavaş olabileceği için sistemin paralel ve verimli bir şekilde çalışmasını sağlamak 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'i kullanan ve "Skyframe yeniden başlatılmalarını" önlemek için eski globber'a geri dönen bir sürüm (aşağıda açıklanmıştır)

Package sınıfının kendisi, özel olarak WORKSPACE dosyasını 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, WORKSPACE dosyası normal paketlerin ayrıştırılmasından daha fazla ayrılmalıdır. Böylece Package, her iki paketin de ihtiyaçlarını karşılamak zorunda kalmaz. Ne yazık ki bunu yapmak, oldukça zordur çünkü ikisi birbiriyle iç içe geçmiştir.

Etiketler, Hedefler ve Kurallar

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

  1. Dosyalar: Derlemenin girişi veya çıkışı olan öğeler. Bazel dilinde bunlara eserler (başka yerde de anlatılmıştır) diyoruz. Derleme sırasında oluşturulan tüm dosyalar hedef değildir; Bazel çıktısının ilişkili bir etikete sahip olmaması yaygın görülen bir durumdur.
  2. Kurallar: Bunlar, çıktılarını girişlerden elde etmeye yönelik adımları açıklar. Bunlar genellikle bir programlama diliyle (cc_library, java_library veya py_library gibi) ilişkilendirilir ancak dilden bağımsız olanlar da vardır (ör. genrule veya filegroup)
  3. Paket grupları: Görünürlük bölümünde ele alını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 ise paketin dizinine göre dosyanın yoludur (etiket bir kaynak dosyaya başvuruda bulunuyorsa). Komut satırında bir hedefe atıfta bulunurken etiketin bazı kısımları atlanabilir:

  1. Depo atlanırsa etiketin ana depoda olduğu kabul edilir.
  2. Paket bölümü atlanırsa (name veya :name gibi) etiket, geçerli çalışma dizininin paketinde olduğu 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 (yani "yerel kurallar", RuleClass yazın) uygulanabilir. Uzun vadede, dile özgü her kural Starlark'ta uygulanacaktır, ancak bazı eski kural aileleri (Java veya C++ gibi) şimdilik Java'dadır.

Starlark kural sınıflarının, BUILD dosyalarının başında load() ifadesi kullanılarak içe aktarılması gerekir. Java kural sınıfları, ConfiguredRuleClassProvider ile kaydettirildiği için Bazel tarafından "doğuştan" olarak 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 eklenmiş yapılandırma geçişleri ve yönler
  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, genellikle bir kural sınıfı tarafından oluşturulan hedefi belirtmek için "Kural" ifadesini kullanırız. Ancak Starlark'ta ve kullanıcılara yönelik belgelerde "Rule" yalnızca kural sınıfını belirtmek 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 temelini oluşturan değerlendirme çerçevesine Skyframe denir. Bu modelin modeli, bir derleme sırasında derlenmesi 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şzamansız 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. Olması halinde (BuildConfigurationValue ve SkyKey öğelerinin üyesi olan BuildOptions bağımsız seçenek sınıflarında olduğu gibi) bunları değiştirmemek veya yalnızca dışarıdan gözlemlenemeyen şekillerde değiştirmek için gayretle çalışı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, grafiği her satıra bir SkyValue gelecek şekilde dağıtan bazel dump --skyframe=deps çalıştırmaktır. Bu işlemi küçük yapılarda yapmak en iyisidir, çünkü oldukça büyük olabilir.

Skyframe, com.google.devtools.build.skyframe paketinde yer alır. Benzer bir ş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 SkyValue olarak değerlendirmek için Skyframe, anahtarın türüne karşılık gelen SkyFunction işlevini ç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, söz konusu bağımlılıkları Skyframe'in dahili grafiğine kaydetmek gibi bir yan etkiye sahiptir. Böylece Skyframe, bağımlılıklarından herhangi biri 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 sn. ve SkyValue sn. ayrıntı düzeyinde çalışır.

Bir SkyFunction, kullanılamayan bir bağımlılık istediğinde getValue() boş sonucunu döndürür. Daha sonra işlev, kendi başına 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 işlevi baştan başlatır. Ancak bu sefer getValue() çağrısı null olmayan bir sonuçla başarılı olur.

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

  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. SkyFunction.Environment.getState() kullanarak veya geçici bir statik önbelleği "Skyframe'in arkasında" tutarak yeniden başlatmalar arasında durum depolama.

Esasen, bu tür geçici çözümlere ihtiyacımız var. Çünkü düzenli olarak yayındaki yüz binlerce Skyframe düğümümüz var ve Java hafif iş parçacıklarını desteklemiyor.

Starlark

Starlark, kullanıcıların Bazel'i yapılandırmak ve genişletmek için kullandığı alana özgü dildir. Python'un çok daha az türe, kontrol akışı üzerinde daha fazla kısıtlamaya ve en önemlisi, eşzamanlı okumalara olanak tanıyan güçlü sabitlik garantilerine sahip kısıtlanmış bir alt kümesi olarak düşünülür. Bu, tüm kullanıcıları değil, bazılarını dil içinde genel programlama görevlerini tamamlamaya çalışmaktan vazgeçiren Turing eksiksiz bir yaklaşım değildir.

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

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

  1. BUILD dili. Yeni kurallar burada tanımlanır. Bu bağlamda çalışan Starlark kodu, yalnızca BUILD dosyasının içeriğine ve bu dosya tarafından yüklenen .bzl dosyaya erişebilir.
  2. Kural tanımları. Yeni kurallar (yeni dil desteği gibi) bu şekilde tanımlanır. Bu bağlamda çalışan Starlark kodu, doğrudan bağımlılıklarının sağladığı yapılandırmaya ve verilere erişebilir (ileride daha fazla bilgi edinecektir).
  3. WORKSPACE dosyası. Harici depolar (ana kaynak ağacında yer almayan kod) tanımlanır.
  4. Kod deposu kuralı tanımları. Yeni harici depo türleri burada tanımlanır. Bu bağlamda çalışan Starlark kodu, Bazel'ın çalıştığı makinede rastgele kod çalıştırabilir ve çalışma alanının dışına erişebilir.

BUILD ve .bzl dosyalarının lehçeleri 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, aslında bir (hedef, yapılandırma) çifti olan "yapılandırılmış hedef"tir.

Bu aşama, daha önce serileştirilerek şimdi zamanla üst üste gelen iki ayrı bölüme ayrılabildiği için "yükleme/analiz aşaması" olarak adlandırılır:

  1. Paketler yükleniyor, yani BUILD dosyalarını onları temsil eden Package nesnelerine dönüştürme
  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 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. ("bu kuralın nasıl oluşturulacağı"; ö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. 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ılmış hedefin geçişli kapanışında bilgilerin "toplayıcısını" sağladıkları için bu şekilde adlandırılmıştır.)
  3. Hedefin kendisi. Bu, hedefin bulunduğu paketin yüklenmesinden kaynaklanır. Kurallar açısından, bu, genellikle önemli olan özellikleri de içerir.
  4. Yapılandırılmış hedefin uygulanması. Kurallar için bu, Starlark'ta veya Java'da olabilir. Kural olmayan tüm hedefler Java'da uygulanır.

Yapılandırılmış bir hedefin analiz edilmesinin sonucu:

  1. Buna 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 RuleContext API'dir. Bu API, Starlark kurallarının ctx bağımsız değişkenine eşdeğerdir. API'si daha güçlüdür ancak aynı zamanda Bad ThingsTM'i yapmak (ö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 sabit değerleri ihlal etmek (ör. 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() içinde bulunur.

Yapılandırmalar

Yapılandırmalar, hedef oluşturma işleminin "nasıl" yapıldığıdır: hangi platform için, hangi komut satırı seçenekleriyle vb.

Aynı derlemedeki birden fazla yapılandırma için aynı hedef oluşturulabilir. Örneğin, derleme sırasında çalıştırılan bir araç ve hedef kod için aynı kod kullanıldığında ve çapraz derleme yaparken veya yağsız bir Android uygulaması (birden fazla CPU mimarisi için yerel kod içeren) oluştururken bu yöntem yararlıdır.

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

Bu durum, örneğin yalnızca test hedeflerini etkilemesine rağmen istenen test çalıştırmalarının sayısı değişirse derlemenin tamamını yeniden analiz etmek zorunda kalma gibi anormalliklere neden olur (Yapılandırmaları "kırpmayı" planlıyoruz, böyle bir durum söz konusu değildir ancak henüz hazır değildir).

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

Bir kuralın yapılandırması, "üst" kuralının yapılandırmasıyla aynı olmayabilir. 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() içinde belirtilmiştir ve Rule (geçişin gerçekleştiği yer) ve BuildOptions (orijinal yapılandırma) ile bir veya daha fazla BuildOptions (çıkış yapılandırması) arasındaki işlevlerdir.
  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 bu nedenle yürütme mimarisinde derlenmesi gerektiğini beyan etmek
  2. Belirli bir bağımlılığın birden fazla mimari için derlenmesi 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ş denir.

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ğımlı olan diğer yapılandırılmış hedefler hakkında bilgi vermelerini sağlayan bir yöntemdir (ve _only _way). Adlarında "geçişli" ifadesinin olmasının nedeni, bunun genellikle yapılandırılmış bir hedefin geçişli kapanışının bir şekilde toplanmasıdır.

Java geçişli bilgi sağlayıcıları ile Starlark sağlayıcıları arasında genellikle bire bir eşleşme vardır (FileProvider, FilesToRunProvider ve RunfilesProvider ifadelerinin birleşimi olan DefaultInfo, bu API'nin Java'nın doğrudan harf çevirisinden daha Starlark benzeri bir sıralamaya sahip olduğu düşünülür). 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 alt sınıfıdır.
  2. Bir dize. Bu eski bir olaydır ve isim çakışmaları olabileceğinden kesinlikle önerilmez. Bu tür geçişli bilgi sağlayıcıları, build.lib.packages.Info adlı grubun 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ı oluşturmak için önerilen yöntemdir. Sembol, Java'da bir Provider.Key örneğiyle temsil edilir.

Java'da uygulanan yeni sağlayıcılar BuiltinProvider kullanılarak uygulanmalıdır. NativeProvider kullanımdan kaldırıldı (henüz kaldırmak için 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, "bu kuralın temsil ettiği dosya kümesi" gibi belirsiz bir kavramdır. Bunlar, yapılandırılmış hedef komut satırında veya bir türü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 filegroup 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ı gerekir. Belirgin bir örnek, giriş dosyaları gereken 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 dosyaları işaret eden bağımsız sembolik bağlantıların bulunduğu bir sembolik bağlantı ağacı olarak oluşturulur.

Bir çalıştırma dosyası 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 özelliğinden biraz daha karmaşıktır:

  • Çoğu zaman bir dosyanın runfiles yolu execpath ile aynıdır. Bu işlem RAM'den tasarruf etmek için kullanılır.
  • Runfile ağaçlarında, temsil edilmesi gereken çeşitli eski tür girişler bulunur.

Çalışma dosyaları RunfilesProvider kullanılarak toplanır: Bu sınıfın bir örneği, runfiles 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 küme 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, ardından elde edilen kurulumu bağımlılık grafiğinde yukarıya gönderir. RunfilesProvider örneği, biri kuralın "data" özelliği üzerinden bağımlı olduğu durumlarda ve diğer her tür gelen bağımlılık türü için olmak üzere iki Runfiles örneği içerir. Bunun nedeni, bir hedefin bazen bir veri özelliği aracılığıyla bağımlı olduğunda normalde farklı olan farklı çalıştırma dosyaları sunmasıdır. Bu, henüz kaldırmadığımız eski, istenmeyen bir davranıştır.

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

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

Bakış Açıları

Yönler, "işlemi bağımlılık grafiğine yaymanın" bir yoludur. Bunlar Bazel kullanıcıları için burada açıklanmıştır. İyi bir motivasyon örneği, protokol arabellekleridir: 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ı dildeki iki hedef aynı protokol arabelleğine bağlı olduğunda yalnızca bir kez derlenir.

Yapılandırılmış hedeflerde olduğu gibi, Skyframe'de de SkyValue olarak temsil edilirler ve yapılandırılma şekilleri, yapılandırılan 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ış hedefi ve sağlayıcılarını da bilir.

Bağımlılık grafiğinde aşağı yayılan en boy grubu, Attribute.Builder.aspects() işlevi kullanılarak her özellik için belirtilir. Sürece dahil olan, farklı adlara sahip birkaç sınıf vardır:

  1. AspectClass, özelliğin uygulanmasıdır. Java'da (bu durumda bir alt sınıf) veya Starlark'ta (bu durumda StarlarkAspectClass örneğidir) olabilir. RuleConfiguredTargetFactory ile benzerdir.
  2. AspectDefinition, özelliğin tanımıdır. Gerekli sağlayıcıları, sağladığı sağlayıcıları ve ilgili AspectClass örneği gibi uygulanmasına ilişkin bir referans içerir. RuleClass ile benzerdir.
  3. AspectParameters, bağımlılık grafiğinde aşağı doğru yayılan bir yönü parametrize etmenin bir yoludur. Şu anda dize eşlemesi. Neden faydalı olduğuna dair iyi bir örnek protokol arabellekleridir: Bir dilin birden fazla API'si varsa protokol arabelleklerinin hangi API için oluşturulması gerektiğiyle ilgili bilgiler bağımlılık grafiğinde dağıtı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önlerinin yayılması gerektiğini belirleyen işlevdir. Bu bir Rule -> Aspect işlevidir.

Beklenmedik bir sorun da bazı 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 almak isteyecektir. Ancak bunların bazıları protokol arabelleğidir. Bu durumda, IDE özelliği (proto_library kuralı + Java proto en boy oranı) çiftine eklemek isteyecektir.

Yönlerin karmaşıklığını AspectCollection dersinde öğrenebilirsiniz.

Platformlar ve araç zincirleri

Bazel, çok platformlu derlemeleri, yani derleme işlemlerinin çalıştığı birden fazla mimarinin ve kodun derlendiği birden fazla mimarinin bulunabileceğ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 (ör. x86_64 gibi belirli bir CPU) kadar bir anahtar/değer eşlemesi tarafından tanımlanır. @platforms deposunda en sık kullanılan kısıtlama ayarlarının ve değerlerinin bir "sözlüğü" vardır.

Araç zinciri kavramı, derlemenin hangi platformlarda çalıştığına ve hangi platformların hedeflendiğine bağlı olarak farklı derleyicilerin kullanılmasının gerekmesinden gelir. Örneğin, belirli bir C++ araç zinciri belirli bir işletim sisteminde çalışabilir ve diğer bazı işletim sistemlerini hedefleyebilir. Bazel, kümenin yürütmesine ve hedef platforma göre kullanılan C++ derleyicisini belirlemelidir (araç zincirleri için belgeleri burada bulabilirsiniz).

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

  1. Yürütme kümesini ve hedefi tanımlayan toolchain() kuralı, araç zincirinin desteklediğini ve ne tür (C++ veya Java) olduğunu (ör. C++ veya Java) söyler (ikincisi, 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ümlemesi yapmak için her araç zincirinin kısıtlamalarını bilmemiz gerekir. Dile özgü *_toolchain() kuralları bundan çok daha fazla bilgi içerir ve bu nedenle yüklenmeleri daha uzun sürer.

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

  1. WORKSPACE 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 platformlar listesidir çünkü zaman içinde birden çok hedef platformu desteklemek isteyeceğiz, ancak henüz uygulanmadı.

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

  • Kayıtlı araç zincirleri grubu (WORKSPACE dosyasında ve yapılandırmada)
  • İstenen yürütme ve hedef platformlar (yapılandırmada)
  • Yapılandırılmış hedefin gerektirdiği araç zinciri türleri grubu (UnloadedToolchainContextKey) içinde
  • UnloadedToolchainContextKey içindeki yapılandırılan 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, temelde araç zinciri türünden (ToolchainTypeInfo örneği olarak temsil edilir) seçilen araç zincirinin etiketine giden bir harita olan UnloadedToolchainContext elde edilir. Alet zincirlerinin kendisini değil, yalnızca etiketlerini içermesi nedeniyle "unload" olarak adlandırılmıştır.

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 işaretleriyle temsil edilen tek bir "ana makine" yapılandırması ve hedef yapılandırmaları olmasını gerektiren eski bir sistemimiz de mevcuttur . Kademeli olarak yukarıdaki sisteme geçiyoruz. Kullanıcıların eski yapılandırma değerlerinden yararlandığı durumları ele almak için eski işaretler ile yeni stil platform kısıtlamaları arasında geçiş yapmak üzere platform eşlemeleri uyguladık. Kodu PlatformMappingFunction dilinde ve Starlark dışında bir "küçük dil" kullanıyor.

Sınırlamalar

Bazen bir hedefin yalnızca birkaç platformla uyumlu olduğunu belirtmek isteriz. Bazel ne yazık ki bu amaca ulaşmak için birden çok mekanizmaya sahiptir:

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

Kurala özgü kısıtlamalar, çoğunlukla Java kuralları için Google'da kullanılır. Bu kısıtlamalar kullanımdan kaldırılmak üzeredir ve Bazel'de kullanılamaz, ancak kaynak kodu bunlara referanslar içerebilir. Bunu yöneten özelliğe constraints= adı verilir .

Çevre_grubu() ve ortam()

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

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

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

  1. restricted_to= özelliği aracılığıyla. Bu, en doğrudan ayrıntılandırma biçimidir; kuralın bu grup için desteklediği tam ortam grubunu belirtir.
  2. compatible_with= özelliği aracılığıyla. Bu işlem, 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, tematik olarak ilişkili benzer bir gruba ("CPU mimarileri", "JDK sürümleri" veya "mobil işletim sistemleri" gibi) aittir. Ortam grubu tanımı, bu ortamlardan hangilerinin restricted_to= / environment() özellikleriyle aksi belirtilmediği takdirde "varsayılan" tarafından desteklenmesi gerektiğini içerir. Bu tür özelliklere sahip olmayan bir kural, tüm varsayılanları devralır.
  5. Kural sınıfı varsayılanı üzerinden. 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 açıkça belirtilmesine gerek kalmadan tüm *_test kurallarını test edilebilir hale getirmek için kullanılabilir.

environment() normal bir kural olarak uygulanırken environment_group() hem Target alt sınıfıdır (Rule (EnvironmentGroup) değildir) hem de varsayılan olarak Starlark'tan (StarlarkLibrary.environmentGroup()) sunulan bir işlevdir ve sonuçta adsız bir hedef oluşturur. Bunun amacı, her ortamın ait olduğu ortam grubunu ve her ortam grubunun varsayılan ortamlarını bildirmesi gerektiği için ortaya çıkacak döngüsel 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 dillerindedir.

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

Çok sayıda geliştiriciyle (Google'da olduğu gibi) birlikte büyük bir kod tabanı üzerinde çalışıyorsanız herkesin kodunuza bağlı olarak rastgele çalışmasını engellemeye özen göstermek istersiniz. 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 belirtebilirsiniz. Bu özellik biraz özeldir çünkü bu etiket, bir etiket listesi içermesine rağmen, 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 beyanını temsil eder. Sabit (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 bile bunları basit kurallarla değiştirebiliriz. Bunların mantığı, //pkg/... gibi tek bir kalıpa 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 diğer birkaç yerde gerçekleştirilir.
  • Asıl kontrol CommonPrerequisiteValidator.validateDirectPrerequisiteVisibility() adımında yapılıyor

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

Yapılandırılmış bir hedef genellikle bağımlılıklarından bir dosya grubu toplar, kendine ekler ve toplu kümeyi geçişli bilgi sağlayıcısına sarmalar. Böylece, buna bağımlı yapılandırılmış hedefler de aynısını yapabilir. Örnekler:

  • Derleme için kullanılan C++ üstbilgi dosyaları
  • cc_library geçişinin 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

Bunu, örneğin List veya Set kullanarak basit şekilde yaparsak ikinci dereceden bellek kullanımına sahip oluruz: N kural zinciri varsa ve her kural bir dosya eklerse 1+2+...+N koleksiyon üyemiz olur.

Bu sorunu gidermek için NestedSet kavramını geliştirdik. 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, postorder, topolojik (düğüm her zaman üstlerinden sonra gelir) ve "umursamaz, ama her seferinde aynı olması gerekir".

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

Yapılar ve İşlemler

Gerçek 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. Bu grafikler, "eylem grafiği" adı verilen, iki taraflı, yönlendirilmiş, çembersel bir grafik halinde düzenlenmiştir.

Yapılar iki türde sunulur: kaynak yapılar (Bazel yürütmeye başlamadan önce kullanılabilir olanlar) ve türetilmiş yapılar (derlenmesi gerekenler). Türetilmiş yapıların kendileri birden fazla türde olabilir:

  1. **Düzenli eserler. **Bunların güncelliği, sağlamaları hesaplanarak güncelliği kontrol edilir. Kısayol olarak mtime kullanılır; saati değişmediyse dosyanın toplamını sunmayız.
  2. Düzeltilmemiş sembolik bağlantı yapıları. Bunlar, readlink() işlevi kullanılarak güncellik açısından kontrol edilir. Normal yapılardan farklı olarak, bunlar sallanan sembolik bağlantılar olabilir. Genellikle kullanıcının bazı dosyaları bir tür arşivde topladığı durumlarda kullanılır.
  3. Ağaç yapıları. Bunlar tek dosyalar değil, dizin ağaçlarıdır. İçindeki dosya grubu ve içerikleri kontrol edilerek güncel olup olmadıkları kontrol edilir. Bunlar bir TreeArtifact olarak temsil edilir.
  4. Sabit meta veri yapıları. Bu yapılarda yapılan değişiklikler yeniden derlemeyi tetiklemez. Bu, yalnızca yapı damgası bilgileri için kullanılır: Sırf geçerli saat değiştiği için yeniden oluşturma yapmak istemeyiz.

Kaynak yapılarının ağaç yapıları veya çözülmemiş sembolik bağlantı yapıları olmamasının temel bir nedeni yoktur. Sadece onu henüz uygulamamış olmamızdır (ancak BUILD dosyasında bir kaynak dizine referans vermek, Bazel'de uzun süredir var olan birkaç hatalılık sorunundan biridir. BAZEL_TRACK_SOURCE_DIRECTORIES=1 JVM özelliği tarafından etkinleştirilen bu tür bir uygulamamız vardır)

Önemli Artifact türü aracılardır. Bunlar, MiddlemanAction çıktıları olan Artifact örnekleriyle gösterilir. Bunlar bazı şeyleri özelleştirmek için kullanılır:

  • Toplama aracıları, yapıları birlikte gruplandırmak için kullanılır. Bunun nedeni, birçok işlem aynı büyük giriş grubunu kullanıyorsa N*M bağımlılık kenarlarımızın olmaması, yalnızca N+M (bunlar iç içe kümelerle değiştirilecektir)
  • Planlama bağımlılık aracıları, bir işlemin diğerinden önce çalışmasını sağlar. Çoğunlukla hata ayıklama için kullanılırlar, ancak aynı zamanda C++ derlemesi için de kullanılırlar (açıklama için CcCompilationContext.createMiddleman() bölümüne bakın)
  • Runfiles aracıları, bir runfiles ağacının varlığını sağlamak için kullanılır. Böylece, ayrı olarak çıkış manifestine ve runfiles ağacı tarafından başvurulan her bir yapıya bağımlı olmasına gerek kalmaz.

Eylemler; çalıştırılması gereken komutlar, komutun ihtiyaç duyduğu ortam ve ürettiği çıkış kümesi olarak tanımlanabilir. 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++'ın kendi işlem türleri (JavaCompileAction, CppCompileAction ve CppLinkAction) olsa da, işlemlerin çoğu bir SpawnAction veya StarlarkAction'dir (aynı şekilde, muhtemelen ayrı sınıflar olmamalıdır).

Nihayetinde her şeyi SpawnAction konumuna taşımak isteriz. JavaCompileAction çok yakın bir çözüm ama .d dosyası ayrıştırma ve taramayı dahil etme nedeniyle C++ biraz özel bir durum.

Eylem grafiği Skyframe grafiğine çoğunlukla "yerleştirilmiştir": Kavram olarak, bir işlemin yürütülmesi ActionExecutionFunction çağrısı olarak temsil edilir. Bir işlem grafiği bağımlılık ucundan Skyframe bağımlılık kenarına eşleme, ActionExecutionFunction.getInputDeps() ve Artifact.key() bölümlerinde açıklanmıştır ve Skyframe kenarlarının sayısını düşük tutmak için birkaç optimizasyon vardır:

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

Paylaşılan işlemler

Bazı işlemler birden fazla yapılandırılmış hedef tarafından oluşturulur; Starlark kurallarının türetilmiş işlemlerini yalnızca yapılandırmalarına ve paketlerine göre belirlenen bir dizine yerleştirmelerine (ancak aynı paketteki kurallar çakışsa bile) izin verildiği için Starlark kurallarının daha sınırlı olması gerekir. Ancak, Java'da uygulanan kurallar türetilmiş yapıları herhangi bir yere yerleştirebilir.

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

İki işlem aynı çıkış dosyasını oluşturuyorsa bunların tamamen aynı olması gerekir: 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() öğesinde uygulanır ve her işleme bakılarak analiz ve yürütme aşamaları arasında doğrulanır. Bu uygulama SkyframeActionExecutor.findAndStoreArtifactConflicts() ürününde uygulanır ve Bazel'de yapının "global" bir görünümünü gerektiren birkaç yerden biridir.

Yürütme aşaması

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

Bazel'ın analiz aşamasından sonra yaptığı ilk şey hangi Yapıların oluşturulması gerektiğini belirlemektir. Bunun mantığı TopLevelArtifactHelper olarak 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.

Bir 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 yürütülen işlemleri tam bir kaynak ağacıyla sağlaması gerekir. Bu, SymlinkForest sınıfı tarafından yönetilir ve analiz aşamasında kullanılan her hedef dikkate alınarak ve her paketi asıl konumundan kullanılan bir hedefle sembolikleştiren tek bir dizin ağacı oluşturarak çalışır. Bir alternatif, komutlara doğru yolları iletmektir (--package_path dikkate alınarak). Bu durum istenmeyen bir durumdur çünkü:

  • Bir paket, paket yolu girişinden diğerine taşındığında işlem komut satırlarını değiştirir (bu genellikle yaygın bir durumdur)
  • Bir işlem uzaktan çalıştırıldığında yerel olarak ç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ı ile C++ yolları arasındaki farka dikkat edin)
  • Bir işlemin komut satırı değiştirildiğinde, işlem önbelleği girişi geçersiz hale gelir
  • --package_path kademeli olarak kullanımdan kaldırılıyor

Ardından Bazel, işlem grafiğinde (eylemler ve bunların giriş/çıkış yapılarından oluşan iki taraflı, yönlendirilmiş grafik) gezinmeye ve işlemleri yürütmeye 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ında tutulabilecek birkaç önbelleğe alma katmanımız var:

  • ActionExecutionFunction.stateMap, Skyframe yeniden başlatma işlemlerini ActionExecutionFunction tutarında ucuza yapmak için veri içeriyor
  • Yerel işlem önbelleği, dosya sisteminin durumuyla ilgili veriler 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 çalıştırılsa bile yerel işlem önbelleğinde isabet olabilir. Yerel dosya sisteminin durumunu temsil eder ve diske serilenir. Yani, yeni bir Bazel sunucusu başlatıldığında Skyframe grafiği boş olsa bile yerel işlem önbelleği isabetleri alınabilir.

Bu önbellek, şu yöntem kullanılarak isabet olup olmadığı kontrol edilir: ActionCacheChecker.getTokenIfNeedToExecute() .

Adının aksine, türetilmiş bir eserin izlediği yoldan onu yayan eyleme kadar uzanan bir haritadır. İşlem şu şekilde açıklanır:

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

Ayrıca hâlâ geliştirilme aşamasında olan ve çok sayıda ö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 girişe sahip olmaktan daha karmaşıktır. Bir işlemin giriş grubunda yapılan değişiklikler iki biçimde gerçekleşir:

  • Bir eylem, yürütme işleminden önce yeni girişler keşfedebilir veya bazı girişlerinin aslında gerekli olmadığına karar verebilir. Google'ın standart örneği C++'dır. "Burada, C++ dosyasının geçişli kapanışında hangi başlık dosyalarını kullandığına dair bilgiye dayalı bir tahminde bulunmak daha iyidir. Böylece her dosyayı#include
  • Bir işlem, yürütme sırasında bazı dosyaların kullanılmadığını fark edebilir. C++'da buna ".d dosyaları" adı verilir: Derleyici, hangi başlık dosyalarının olaydan sonra 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, include tarayıcısından daha iyi bir tahmin sunar.

Bunlar, Action üzerindeki 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 kaynak yapılar olmalıdır. Böylece işlem grafiğinde, yapılandırılmış hedef grafiğinde 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() işleminin sonunda işlem, Bazel'a tüm girişlerinin gerekli olmadığını bildirmek için Action.updateInputs() işlevini çağırabilir. Kullanılan bir girişin kullanılmadığı bildirilirse bu durum hatalı artımlı derlemelere neden olabilir.

Bir işlem önbelleği yeni bir İşlem örneğine (sunucu yeniden başlatıldıktan sonra oluşturulan gibi) bir isabet döndürdüğünde Bazel, giriş kümesinin daha önce yapılan giriş keşfi ve budanma işleminin sonucunu yansıtması için updateInputs() 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 tanımlamak için bu özellikten yararlanabilir.

İşlemleri 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, ancak çeşitli korumalı alanlarda veya uzaktan yürütülebilir. Bunu içtenActionContextStrategy

İş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 eylem 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 arayüzü olan bir Java Class örneği tarafından tanımlanır.
  2. Mevcut olanlardan uygun işlem bağlamı seçilerek ActionExecutionContext ve BlazeExecutor'a yönlendirilir .
  3. İşlemler, ActionExecutionContext.getContext() ve BlazeExecutor.getStrategy() kullanan bağlamları istiyor (gerçekten bunu yapmanın tek bir yolu olmalıdır...)

Stratejiler, işlerini yapmaları için diğer stratejileri çağırmakta serbesttir. Bu, örneğin hem yerel hem de uzaktan işlem başlatan ve daha sonra hangi işlemi önce biterken kullanan dinamik stratejide kullanılır.

Kayda değer stratejilerden biri kalıcı çalışan süreçleri (WorkerSpawnStrategy) uygulayan stratejidir. Bu fikir, bazı araçların uzun başlatma süresinin olduğu ve bu nedenle her işlem için yeniden 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 olası 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. İşlemin hangi girişlerinin aracın parçasını, hangilerinin ise girişleri temsil ettiğinin bilinmesine bağlıdır. Bu, İş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.
  • Burada, hangisinin önce tamamlandığını görmek için hem yerel olarak hem de uzaktan işlem yaptığımız dinamik strateji hakkında bilgi edinebilirsiniz.
  • İşlemleri yerel olarak yürütmenin incelikleri hakkında bilgiye buradan ulaşabilirsiniz.

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şleme göre değişir: Bir işlem ne kadar çok kaynak gerektirirse yerel makineye aşırı yüklenilmesini önlemek için aynı anda daha az örneğin çalıştırılması gerekir.

Bu işlem ResourceManager sınıfında uygulanır: Her işlem, gerektirdiği yerel kaynakların tahmini bir ResourceSet örneği (CPU ve RAM) biçiminde ek açıklama olarak eklenmelidir. Daha sonra işlem bağlamları yerel kaynak 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, çıktıların yerleştirildiği çıkış dizininde ayrı bir yere ihtiyaç duyar. Türetilen yapıların konumu genellikle aşağıdaki gibidir:

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

Belirli bir yapılandırmayla ilişkili dizinin adı nasıl belirlenir? Çelişen iki istenen özellik vardır:

  1. Aynı derlemede iki yapılandırma oluşabiliyorsa bunların her ikisi de farklı dizinlere sahip olmalıdır. Böylece her iki yapılandırma da aynı çıkış dosyasını üreten bir işlemin komut satırı üzerinde anlaşamazsa, 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 sorunuyla benzerlikler taşıyan ilkeli bir yol bulamadık. Seçeneklerle ilgili daha uzun bir açıklamayı burada bulabilirsiniz. Başlıca sorunlu alanlar, “aynı" çıktı dosyasını üretebilecek unsurlar alanına başka bir boyut ekleyen Starlark kuralları (yazarları genelde Bazel’e pek aşina değildir) ve oranlardır.

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 yol açmaması için Starlark yapılandırma geçişleri kümesinin bir sağlama toplamı eklenir. Mükemmel değil. Bu işlem, 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, test çalıştırma konusunda kapsamlı 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 azaltmak veya toplamak için)
  • Parçalama testleri (hız için aynı testte test durumlarını birden fazla işleme bölme)
  • Kesintili testleri yeniden çalıştırma
  • Testleri test paketlerinde gruplandırma

Testler, testin nasıl çalıştırılması gerektiğini açıklayan bir TestProvider öğesine 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 yürütüleceğini belirlemek titiz bir süreçtir.

İlk olarak, hedef kalıp ayrıştırma sırasında test paketleri tekrarlı olarak genişletilir. Genişletme işlemi TestsForTargetPatternFunction politikasında uygulanır. Biraz şaşırtıcı bir şekilde, test paketinin hiçbir test belirtmezse paketindeki her teste atıfta bulunması da şaşırtıcıdır. Bu özellik, paket kurallarını test etmek için $implicit_tests adlı dolaylı bir özellik eklenerek Package.beforeBuild() ürününde uygulanır.

Ardından testler, komut satırı seçeneklerine göre boyut, etiket, zaman aşımı ve dile göre filtrelenir. Bu, TestFilter içinde uygulanır, hedef ayrıştırma sırasında TargetPatternPhaseFunction.determineTests() öğesinden çağrılır ve sonuç TargetPatternPhaseValue.getTestsToRunLabels() içine yerleştirilir. Filtrelenebilir kural özelliklerinin yapılandırılamamasının nedeni, bunun 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() içinde 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, ExecutionTool hangi testlerin çalıştırılacağını bilmek için AnalysisResult içine yerleştirilir.

Bu ayrıntılı işleme biraz şeffaflık kazandırmak amacıyla, komut satırında belirli bir hedef belirtildiğinde hangi testlerin çalıştırıldığını tests() sorgu operatörü (TestsFunction uygulamasında uygulanır) kullanabilir. Ne yazık ki bu bir yeniden uygulama, dolayısıyla muhtemelen birkaç farklı şekilde yukarıdakilerden sapmaktadır.

Testler yapma

Testler, önbellek durumu yapıları istenerek çalıştırılır. Bu daha sonra bir TestRunnerAction yürütülmesiyle sonuçlanır. Bu da sonunda, 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 neler beklediği ve testlerin Bazel'den neler bekleyebileceğiyle ilgili ayrıntılı açıklamayı burada bulabilirsiniz. En basit ifadeyle, 0 çıkış kodu başarı, diğer her şey başarısızlık anlamına gelir.

Her test işlemi, önbellek durumu dosyasına ek olarak 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 her bir test durumunu ayrıntılı şekilde açıklayan bir JUnit stili XML dosyası
  • test. stdout ve stderr çıkışının konsol çıkışı (test.log) ayrılmaz.
  • test.outputs, "bildirilmemiş çıkış dizini"dir. Bu, terminale yazdırdıkları verilerin yanı sıra dosyaların çıkışını da almak isteyen testler tarafından kullanılır.

Test yürütülürken normal hedefler oluşturulurken mümkün olmayan iki şey gerçekleşebilir: özel test yürütme ve çıkış akışı.

Bazı testlerin özel modda yürütülmesi gerekir (örneğin, diğer testlere paralel olarak değil). Bu, test kuralına tags=["exclusive"] eklenmesi veya testi --test_strategy=exclusive ile çalıştırılmasıyla elde edilebilir . Her özel test, "ana" derlemeden sonra testin yürütülmesini isteyen ayrı bir Skyframe çağrısı ile çalıştırılır. Bu uygulama SkyframeExecutor.runExclusiveTest() politikasında uygulanmıştır.

İşlem tamamlandığında terminal çıkışı düşen normal işlemlerin aksine kullanıcı, uzun süreli bir testin ilerleme durumu hakkında bilgi almak için test çıktısının akış şeklinde yayınlanmasını isteyebilir. Bu, --test_output=streamed komut satırı seçeneğiyle belirtilir ve farklı test çıkışlarının serpiştirilmemesi için özel test yürütmeyi gerektirir.

Bu işlem, tam olarak 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 atanarak çalışır.

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

Kapsam koleksiyonu

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

Kapsamı toplamak için her test yürütmesi collect_coverage.sh adlı bir komut dosyasında 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 fazla farklı programlama dilinde (ayrı kapsam toplama çalışma zamanlarıyla) yazılmış parçalardan oluşabilir. Sarmalayıcı komut dosyası, gerektiğinde sonuç dosyalarını LCOV biçimine dönüştürmekten sorumludur ve bunları tek bir dosyada birleştirir.

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

Bazı diller çevrimdışı araçlar kullanır. Yani kapsam enstrümantasyonu, derleme sırasında eklenir (C++ gibi) ve diğerleri online araçlar kullanır. Yani kapsam araçları yürütme sırasında eklenir.

Bir diğer temel kavram ise referans kapsamı'dır. Bu; kitaplık, ikili program veya içinde hiçbir kodun çalıştırılıp çalıştırılmadığı test edilmesini kapsar. Çözümün çözdüğü sorun, bir ikili program için test kapsamını hesaplamak istiyorsanız tüm testlerin kapsamını birleştirmek yeterli olmayacaktır. Çünkü ikili programda herhangi bir teste bağlı olmayan kod olabilir. Bu nedenle, yalnızca kapsam topladığımız dosyaları içeren ve kapsama dahil olmayan satırlar içermeyen her ikili program için bir kapsam dosyası yayınlarız. Hedef için referans kapsam dosyası şudur: bazel-testlogs/$PACKAGE/$TARGET/baseline_coverage.dat --nobuild_tests_only işaretini Bazel'e geçirmeniz durumunda testlerin yanı sıra ikili programlar ve kitaplıklar için de oluşturulur.

Temel kapsam şu anda kullanılamıyor.

Her kuralda kapsam koleksiyonu için iki dosya grubu izlenir: araçlı dosyalar grubu ve araç meta veri dosyaları grubu.

Araçlı dosyalar kümesi, tam da bir araç setinden ibarettir. Online kapsam ç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 dosyadan 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ı yayar. Kapsam modu etkinleştirilirse bunlar, test işlemleri giriş grubuna eklenir.

Kapsamın toplanıp toplanmayacağı BuildConfiguration özelliğ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 faydalıdır. Aynı zamanda, bu bit çevrilirse tüm hedeflerin yeniden analiz edilmesi gerektiği anlamına da gelir (C++ gibi bazı diller, kapsamı toplayabilecek bir kod oluşturmak için farklı derleyici seçenekleri gerektirir ve bu da yine de yeniden analiz yapılması gerektiğinden bu sorunu bir şekilde azaltır).

Kapsam destek dosyaları, örtülü bir bağımlılıkta etiketlerle kullanılır. Bu sayede çağrı politikası, farklı Bazel sürümleri arasında farklılık gösterebilmelerini sağlayan çağrı politikası tarafından geçersiz kılınabilir. İdeal olan, bu farklılıkların ortadan kaldırılmasıdır ve biz de bunlardan birini standartlaştırdık.

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() tarafından çağrılır . Yürütülen ilk testin :coverage_report_generator özelliğine bakarak ihtiyaç duyduğu araçlara erişim sağlar.

Sorgu motoru

Bazel'ın çeşitli grafikler hakkında çeşitli sorular sormak için küçük bir dili var. Aşağıdaki sorgu türleri sunulur:

  • 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ırma yaparak ek ek sorgu işlevleri yapılabilir. Sorgu akışı sonuçlarına izin vermek amacıyla sonuçları bir veri yapısında toplamak yerine, döndürmek istediği sonuçlar için çağıran QueryFunction işlevine bir query2.engine.Callback iletilir.

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 gerekliliği de, sonucun farklı olup belirli bir hedefin değişip değişmediğini belirleyebilmesi için Bazel'in paket yüklemesinin sağladığı bilgileri _tümünü _aktarmasıdır. Sonuç olarak, özellik değerlerinin seri hale getirilebilmesi 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 de tatmin edici bir çözüm değildir ve bu koşulu kaldırmak bizi çok memnun eder.

Modül sistemi

Bazel'a modüller eklenerek genişletilebilir. Her modül BlazeModule alt sınıfı (adı, eskiden Blaze olarak adlandırıldığında Bazel geçmişinin kalıntılarıdır) ve bir komutun yürütülmesi sırasında çeşitli olaylar hakkında bilgi alır.

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

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

BlazeModule tarafından sunulan uzantı noktaları grubu biraz şans eseri değildir. Bunu iyi tasarım ilkelerinin bir ö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 otobüsüdür (EventBus): Her derleme için yeni bir örnek oluşturulur, Bazel'ın çeşitli bölümleri bu örnekte etkinlik yayınlayabilir ve modüller, ilgilendikleri etkinlikler için işleyicileri kaydedebilir. Örneğin, aşağıdakiler etkinlik olarak temsil edilir:

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

Bu etkinliklerden bazıları, Etkinlik Derleme Protokolü'nde Bazel dışında temsil edilir (BuildEvent). Bu, sadece BlazeModule'lerin değil, Bazel işleminin dışındaki öğelerin de derlemeyi gözlemlemesine olanak tanır. Bunlara, protokol mesajları içeren bir dosya olarak erişilebilir veya Bazel, etkinlikleri yayınlamak için bir sunucuya (Derleme Etkinliği Hizmeti denir) bağlanabilir.

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

Harici depolar

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

WORKSPACE dosyası

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

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

@foo adlı depodaki sonuçlar kullanılabilir. Bu işin karmaşık hale geldiği nokta, Starlark dosyalarında yeni depo kuralları tanımlayabilmektir. Bu kurallar, daha sonra yeni Starlark kodu yüklemek için kullanılabilir. Bu kodlar yeni depo kuralları tanımlamak vb. için de kullanılabilir.

Bu durumu işlemek için WORKSPACE dosyasının (WorkspaceFileFunction bölgesinde) ayrıştırılması, load() ifadeleriyle tanımlanan parçalara bölünür. Yığın dizini WorkspaceFileKey.getIndex() ile gösterilir ve X dizini için WorkspaceFileFunction hesaplanırken, X. load() ifadesine kadar değerlendirme yapılması gerekir.

Kod depoları getiriliyor

Deponun kodu Bazel'ın kullanımına sunulmadan önce fetched gerekir. Bu işlem, Bazel'in $OUTPUT_BASE/external/<repository name> altında bir dizin oluşturmasına neden olur.

Kod deposunun getirilmesi aşağıdaki adımlarla gerçekleşir:

  1. PackageLookupFunction, kod deposuna ihtiyacı olduğunu fark eder ve SkyKey olarak bir RepositoryName oluşturur. Bu da RepositoryLoaderFunction çağrısına yol açar.
  2. RepositoryLoaderFunction, net olmayan nedenlerden dolayı isteği RepositoryDelegatorFunction adresine iletir (kodda, Skyframe yeniden başlatıldığında içeriklerin yeniden indirilmesinden kaçınılması gerektiği belirtilse de 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ğinden, çeşitli önbelleğe alma katmanları vardır:

  1. İndirilen dosyalar için, sağlama toplamı (RepositoryCache) tarafından anahtarlanan bir önbellek vardır. Bu, sağlama toplamının WORKSPACE dosyasında bulunmasını gerektirir, ancak yine de hermetik açıdan iyi sonuç verir. Bu görev, hangi çalışma alanında 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ı", kodu getirmek üzere kullanılan kuralın sağlamasını içeren bir "işaretçi dosyası" yazılır. Bazel sunucusu yeniden başlatılırsa ancak sağlama toplamı değişmezse sunucu yeniden getirilmez. Bu uygulama RepositoryDelegatorFunction.DigestWriter politikasında 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 öğeler getirmemesi gerektiği kurumsal ayarlarda faydalıdır. Bu yöntem, DownloadManager tarafından uygulanır .

Depo indirildikten sonra içindeki yapılar kaynak yapılar olarak değerlendirilir. Bu durum bir sorun teşkil eder. Çünkü Bazel genellikle kaynak yapılarının güncelliğini bunlar üzerinde stat() çağrısı yaparak kontrol eder ve bu yapılar, bulundukları depo tanımı değiştiğinde de geçersiz kılınır. Bu nedenle, harici depodaki bir yapı için FileStateValue'ler, bunların harici deposuna bağlı olmalıdır. Bu işlem ExternalFilesHelper tarafından yönetiliyor.

Yönetilen dizinler

Bazen harici depoların çalışma alanı kök dizinindeki dosyaları değiştirmesi gerekir (ör. indirilen paketleri kaynak ağacının bir alt dizininde barındıran paket yöneticisi). Bu yaklaşım, Bazel'in kaynak dosyaları tek başına değil, yalnızca kullanıcı tarafından değiştirildiği ve paketlerin, çalışma alanı kökü altındaki her dizine başvuruda bulunmasına izin verdiği varsayımıyla çelişmektedir. Bu tür bir harici deponun çalışmasını sağlamak için Bazel iki şey yapar:

  1. Kullanıcının, Bazel'ın erişmesine izin verilmeyen çalışma alanının alt dizinlerini belirtmesine izin verir. Bunlar, .bazelignore adlı bir dosyada listelenir ve işlev BlacklistedPackagePrefixesFunction işlevinde uygulanır.
  2. Eşlemeyi, çalışma alanının alt dizininden, işlendiği harici depoya ManagedDirectoriesKnowledge içinde kodlar ve bunlara referansta bulunan FileStateValue öğelerini normal harici depolarla aynı şekilde işleriz.

Depo eşlemeleri

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

Bu nedenle Bazel, birinin harici depo etiketlerini yeniden eşlemesine izin verir. Böylece @guava// dizesi, bir ikili programın deposundaki bir Guava deposuna (ör. @guava1//), diğerinin deposundaki başka bir Guava deposuna (@guava2// gibi) işaret edebilir.

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

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 şuna bağlanır:

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

JNI bitleri

Bazel sunucusu çoğunlukla Java'da yazılır. İstisna olarak, Java'nın kendi başına ya da biz uygulandığında kendi başına yapamayacağı kısımlardır. Bu, çoğunlukla dosya sistemi, süreç kontrolü ve diğer alt düzey şeylerle 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 iş gibi görünse de birden fazla işlemin (bazen uzaktan çalıştırılması), ayrıntılı önbelleğe almanın, güzel ve renkli bir terminal çıkışına sahip olma arzusu ve uzun çalışan bir sunucuya sahip olma arzusu bunu önemsiz hale getirmez.

İstemciden RPC çağrısı geldikten hemen sonra, kendilerine yazdırılan verileri istemciye yönlendiren 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. Ardından bu akışlar BlazeCommandDispatcher.execExclusively() adlı ağa aktarılır.

Çıktı varsayılan olarak ANSI kod dışına alma sıralarıyla yazdırılır. Bunlar istenmediğinde (--color=no) bir AnsiStrippingOutputStream ile çıkarılır. Ayrıca 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 çıkışına (sunucununkinden farklıdır) gönderilir. Bir süreç ikili çıkış (bazel query --output=proto gibi) üretiyorsa stdout'un düzeltilmemesine dikkat edilir.

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

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

Bazı EventHandler'ler, sonunda etkinlik otobüsüne giden etkinliklerin 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 sayfasında yayınlanan her şey bu arayüzü zorunlu kılmaz, yalnızca ExtendedEventHandler tarafından önbelleğe alınanlar (Güzel olur ve çoğu şey işe yarar, 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 raporlama işlemlerinden sorumludur. İki girişi vardır:

  • Etkinlik otobüsü
  • Etkinlik akışı Bildiren kişi aracılığıyla kanala aktarılır

Komut yürütme makinesinin (örneğin, Bazel'ın geri kalanı) RPC akışıyla istemciye yönelik 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 miktarda olası ikili verinin (bazel query gibi) dökümünü alması gerektiğinde kullanılır.

Bazel Profil Oluşturma

Bazel hızlı. Ayrıca Bazel yavaştır, çünkü yapılamalar dayanılması gereken bir sınıra kadar büyüme eğilimindedir. Bu nedenle Bazel, profil oluşturmaları ve Bazel'ın kendisi için kullanılabilecek bir profilci içerir. Bu, Profiler olarak adlandırılmış bir sınıfta uygulanır. Varsayılan olarak açıktır. Ancak, ek yüküne izin verilmeyeceği şekilde yalnızca kısaltılmış verileri kaydeder. Komut satırı --record_full_profiler_data, mümkün olan her şeyi kaydetmesini sağlar.

Chrome profil aracı biçiminde bir profil oluşturur. En iyi şekilde Chrome'da görüntülenir. Veri modeli, görev yığınlarıdır: Bir kişi görevlere başlayabilecek ve görevleri bitirebilir ve bunların düzenli olarak iç içe yerleştirilmiş olması 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() bölgelerinde 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 öğe eklemek için Profiler.instance().profile() numaralı telefonu arayın. Kapatılması görevin sonunu temsil eden bir Closeable döndürür. Kaynakları deneyin ifadeleriyle kullanılması önerilir.

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

Test Bazelleri

Bazel temel olarak iki tür teste sahiptir: Bazel'i "siyah kutu" olarak gözlemleyenler ve yalnızca analiz aşamasını yürüten testler. İlklerine "entegrasyon testleri" ve ikincilerine "birim testleri" diyoruz. Ancak bunlar daha az entegre olan entegrasyon testlerine benzer. Ayrıca, gerekli olan bazı gerçek birim testlerimiz de vardır.

Entegrasyon testlerinde iki tür test bulunur:

  1. src/test/shell altında, çok 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, BuildIntegrationTestCase tercih edilen entegrasyon testi çerçevesidir. Java çerçevesi olduğundan hata ayıklaması yapılabilirlik ve yaygın olarak kullanılan birçok 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 ödül dosya sistemi vardır ve 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 iddia edebilir.