Bazel kod tabanı

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

Bu doküman, kod tabanının ve Bazel'in nasıl yapılandırıldığının bir açıklamasıdır. Son kullanıcılar için değil, Bazel'e katkıda bulunmak isteyenler için tasarlanmıştır.

Giriş

Bazel'in kod tabanı büyüktür (~350 KLOC üretim kodu ve ~260 KLOC test kodu) ve hiç kimse tüm manzarayı tanımıyor: Herkes kendi vadisini çok iyi bilir, ancak her yöndeki tepelerin arkasında ne olduğunu çok az kişi bilir.

Yolun yarısında kendilerini karanlık bir ormanda, düz bir yol bulamayarak kaybolmuş hissetmemeleri için bu dokümanda, kod tabanına genel bir bakış sunulmaya çalışılarak üzerinde çalışmaya başlamaları kolaylaştırılmıştır.

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

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

İstemci/sunucu mimarisi

Bazel'in büyük kısmı, derlemeler arasında RAM'de kalan bir sunucu işleminde yer alır. Bu sayede Bazel, derlemeler arasında durumu koruyabilir.

Bu nedenle Bazel komut satırında iki tür seçenek vardır: başlangıç 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) sonra gelir. Önceki tür "başlangıç seçeneği" olarak adlandırılır ve sunucu sürecini bir bütün olarak etkiler. "Komut seçeneği" olarak adlandırılan ikinci tür ise yalnızca tek bir komutu etkiler.

Her sunucu örneğinin tek bir ilişkili kaynak ağacı ("çalışma alanı") ve her çalışma alanının genellikle tek bir etkin sunucu örneği vardır. Bu sorun, ö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ü alır. Aşağıdaki adımları kullanarak uygun bir sunucu işlemi oluşturur:

  1. Dosyanın daha önce ayıklanıp ayıklanmadığını kontrol eder. İlişkilendirilmediğinde bu mümkün değildir. Sunucunun uygulanması buradan gelir.
  2. Çalışan, doğru başlangıç seçeneklerine sahip ve doğru çalışma alanı dizini kullanan etkin bir sunucu örneği olup olmadığını kontrol eder. $OUTPUT_BASE/server dizinine bakarak çalışan sunucuyu bulur. Bu dizinde, sunucunun dinlediği bağlantı noktasıyla birlikte bir kilit dosyasının bulunduğu bir kilit dosyası bulunur.
  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 olduktan sonra, çalıştırılması gereken komut bir gRPC arayüzü üzerinden iletilir ve ardından Bazel'in çıkışı terminale geri aktarılır. Aynı anda yalnızca bir komut çalışabilir. Bu, C++ ve Java'da parçaları olan ayrıntılı bir kilitleme mekanizması kullanılarak uygulanır. bazel version'ü başka bir komutla paralel olarak çalıştıramamak biraz utanç verici olduğundan, birden fazla komutu paralel olarak çalıştırmaya yönelik bazı altyapılar vardır. Ana engelleyici, BlazeModule öğelerinin yaşam döngüsü ve BlazeRuntime'daki bazı durumlardır.

Bazel sunucusu, bir komutun sonunda istemcinin döndürmesi gereken çıkış kodunu iletir. bazel run komutunun uygulanması ilginç bir ayrıntıdır: Bu komutun görevi, Bazel'in yeni oluşturduğu bir şeyi çalıştırmaktır ancak terminali olmadığı için bunu sunucu sürecinden yapamaz. Bunun yerine, istemciye hangi ikili dosyayı ujexec() ve hangi bağımsız değişkenlerle çalıştırması gerektiğini söyler.

Ctrl-C tuşlarına basıldığında istemci, bu tuş kombinasyonunu gRPC bağlantısında bir İptal çağrısına dönüştürür. Bu çağrı, komutu en kısa sürede sonlandırmaya çalışır. Üçüncü Ctrl-C'den sonra istemci, bunun yerine sunucuya SIGKILL gönderir.

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

Sunucunun ana giriş noktası BlazeRuntime.main() şeklindedir 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 dizi dizin oluşturur. Tam açıklamayı Çıkış dizini düzeninde bulabilirsiniz.

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

Bazel, tüm verilerini "output user root" altına yerleştirir. Bu değer genellikle $HOME/.cache/bazel/_bazel_${USER} olur ancak --output_user_root başlangıç seçeneği kullanılarak geçersiz kılınabilir.

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

"Çıkış tabanı", belirli bir çalışma alanına bağlı Bazel örneğinin yazdığı yerdir. Her çıkış tabanında, herhangi bir zamanda en fazla bir Bazel sunucu örneği çalışır. Normalde 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 özelliklerin yanı sıra herhangi bir çalışma alanında aynı anda yalnızca bir Bazel örneğinin çalıştırılabileceği sınırlamalarını aşmak için kullanışlıdır.

Çıkış dizininde şunlar bulunur:

  • $OUTPUT_BASE/external adresindeki getirilen harici depolar.
  • Mevcut derlemenin tüm kaynak kodunun sembolik bağlantılarını içeren bir dizin olan exec kökü. Adresi: $OUTPUT_BASE/execroot. Derleme sırasında çalışma dizini $EXECROOT/<name of main repository> olur. Bu özelliği $EXECROOT olarak değiştirmeyi planlıyoruz. Ancak bu, çok uyumsuz bir değişiklik olduğu için uzun vadeli bir plandır.
  • Derleme sırasında oluşturulan dosyalar.

Komut yürütme işlemi

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

  1. BlazeCommandDispatcher, yeni istek hakkında bilgilendirilir. Komutun çalıştırılabilmesi için bir çalışma alanına ihtiyaç duyup duymadığına (sürüm veya yardım gibi kaynak kodla hiçbir ilgisi olmayanlar hariç neredeyse tüm komutlar) 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 anti-modeldir. Bir komutun ihtiyaç duyduğu tüm meta verilerin BlazeCommand'teki yöntemlerle açıklanması iyi olur).

  3. Komut satırı seçenekleri ayrıştırılır. Her komutun farklı komut satırı seçenekleri vardır. Bu seçenekler @Command ek açıklamalarında açıklanmaktadır.

  4. Bir etkinlik otobüsü oluşturulur. Etkinlik veri yolu, derleme sırasında gerçekleşen etkinlikler için bir akıştır. Bunlardan bazıları, derlemenin nasıl gittiğini dünyaya bildirmek için Derleme Etkinliği Protokolü kapsamında Bazel'in dışına aktarılır.

  5. Komut kontrolü alır. En ilginç komutlar, derleme çalıştıranlardır: derle, test et, çalıştır, kapsam vb. Bu işlev BuildTool tarafından uygulanır.

  6. Komut satırındaki hedef kalıp grubu ayrıştırılır ve //pkg:all ve //pkg/... gibi joker karakterler çözülür. Bu, AnalysisPhaseRunner.evaluateTargetPatterns() içinde uygulanır ve Skyframe'da TargetPatternPhaseValue olarak somutlaştırılır.

  7. Yükleme/analiz aşaması, işlem grafiğini (derleme için yürütülmesi gereken komutların yönlendirilmiş, döngüsel olmayan bir grafiği) oluşturmak üzere çalıştırılır.

  8. Yürütme aşaması çalıştırılır. Yani, istenen üst düzey hedefleri oluşturmak için gereken her işlemin çalıştırılması gerekir.

Komut satırı seçenekleri

Bazel çağrısı için komut satırı seçenekleri, bir OptionsParsingResult nesnesinde açıklanır. Bu nesne de "option sınıflarından" seçeneklerin değerlerine bir eşleme içerir. "Seçenek sınıfı", OptionsBase alt sınıfıdır ve birbirleriyle ilgili komut satırı seçeneklerini 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'in 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) tüketilmek üzere tasarlanmıştır. Bunlardan bazıları (örneğin, C++ dahil etme taramasının yapılıp yapılmayacağı) yürütme aşamasında okunur ancak BuildConfiguration o sırada kullanılamadığından bu işlem her zaman açık bağlantı gerektirir. Daha fazla bilgi için "Yapılandırmalar" bölümüne bakın.

UYARI: OptionsBase örneklerini değiştirilemezmiş gibi göstermeyi ve bu şekilde kullanmayı (SkyKeys'ın bir parçası gibi) tercih ederiz. Ancak durum böyle değildir ve bunları değiştirmek, Bazel'i hata ayıklamanın zor olduğu ince yollardan bozmanın çok iyi bir yoludur. Maalesef bu öğeleri gerçekten değiştirilemez hale getirmek büyük bir çaba gerektiriyor. (FragmentOptions öğesini, başka bir kullanıcının referans alma şansı bulamadan ve equals() veya hashCode() çağrılmadan hemen sonra değiştirmek sorun değildir.)

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

  1. Bazıları Bazel'e (CommonCommandOptions) bağlıdır.
  2. Her Bazel komutundaki @Command ek açıklamasından
  3. ConfiguredRuleClassProvider sürümünden (bunlar, ayrı programlama dilleriyle ilgili komut satırı seçenekleridir)
  4. Starlark kuralları da kendi seçeneklerini tanımlayabilir (buraya bakın).

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

Bir komut satırı seçeneğinin değerinin Java türü genellikle basit bir şeydir (dize, tam sayı, Boole, etiket vb.). Ancak daha karmaşık türlerde seçenekleri de destekliyoruz. Bu durumda, komut satırı dizesinden veri türüne dönüştürme işlemi com.google.devtools.common.options.Converter uygulamasına bırakılır.

Bazel tarafından görülen kaynak ağacı

Bazel, kaynak kodu okuyup yorumlayarak yazılım oluşturma işi yapar. Bazel'in üzerinde çalıştığı kaynak kodunun bütünlüğü "çalışma alanı" olarak adlandırılır ve depolar, paketler ve kurallar halinde yapılandırılır.

Kod depoları

"Depo", geliştiricinin üzerinde çalıştığı bir kaynak ağacıdır ve genellikle tek bir projeyi temsil eder. Bazel'in atası Blaze, monorepo'da (yani derlemeyi çalıştırmak için kullanılan tüm kaynak kodu içeren tek bir kaynak ağacında) çalışırdı. Buna karşılık Bazel, kaynak kodu birden fazla depoyu kapsayan projeleri destekler. Bazel'in çağrıldığı depoya "ana depo", diğerlerine ise "harici depolar" adı verilir.

Depo, kök dizininde WORKSPACE (veya WORKSPACE.bazel) adlı bir dosyayla işaretlenir. Bu dosya, derlemenin tamamı için "evrensel" olan bilgileri (ör. kullanılabilir harici depolar grubu) içerir. Normal bir Starlark dosyası gibi çalışır. Diğer Starlark dosyalarını load() edebilirsiniz. Bu genellikle açıkça referans verilen deponun ihtiyaç duyduğu depoları çekmek için kullanılır (buna "deps.bzl kalıbı" denir)

Harici depoların kodları $OUTPUT_BASE/external altında sembolik bir şekilde bağlanır veya indirilir.

Derleme işlemi sırasında kaynak ağacının tamamının birleştirilmesi gerekir. Bu işlem, ana depodaki her paketi $EXECROOT ile ve her harici depoyu $EXECROOT/external veya $EXECROOT/.. ile sembolik bağlantı oluşturan SymlinkForest tarafından gerçekleştirilir (ilki, ana depoda external adlı bir paketin olmasını imkansız hale getirir. Bu nedenle bu yöntemden uzaklaşıyoruz).

Paketler

Her depo, paketlerden, ilgili dosyalardan oluşan bir koleksiyondan 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 dosya adını tercih eder. BUILD dosyalarının hâlâ kabul edilmesinin nedeni, Bazel'in atası Blaze'ın bu dosya adını kullanmasıdır. Ancak özellikle dosya adlarının büyük/küçük harf duyarlı olmadığı Windows'ta yaygın olarak kullanılan bir yol segmenti olduğu ortaya çıktı.

Paketler birbirinden bağımsızdır: Bir paketin BUILD dosyasında yapılan değişiklikler diğer paketlerin değişmesine neden olmaz. Yinelenen glob'lar paket sınırlarında durduğundan ve dolayısıyla BUILD dosyasının varlığı yinelemeyi durdurduğundan, BUILD dosyalarının eklenmesi veya kaldırılması diğer paketleri _değiştirebilir_.

BUILD dosyasının değerlendirilmesine "paket yükleme" adı verilir. PackageFactory sınıfında uygulanır, Starlark yorumlayıcısını çağırarak çalışır ve mevcut kural sınıfları hakkında bilgi sahibi olmayı gerektirir. Paket yüklemenin sonucu bir Package nesnesidir. Çoğunlukla bir dizeyi (hedefin adı) hedefin kendisine eşleyen bir eşlemedir.

Paket yükleme sırasında karmaşıklığın büyük bir kısmı, genelleştirmeden kaynaklanır: Bazel, her kaynak dosyanın açıkça listelenmesini gerektirmez ve bunun yerine genelleştirmeleri (glob(["**/*.java"]) gibi) çalıştırabilir. Kabuğun aksine, alt dizinlere inen (ancak alt paketlere inmeyen) yinelenen genelleştirmeleri destekler. Bu işlem için dosya sistemine erişim gerekir. Bu işlem yavaş olabileceğinden, paralel olarak ve olabildiğince verimli bir şekilde çalışması için her türlü hileyi uygularız.

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

  • LegacyGlobber, hızlı ve keyifli bir SkyFrame'in farkında olmayan küresel küre
  • SkyframeHybridGlobber: Skyframe kullanan ve "Skyframe'ın yeniden başlatılmasını" (aşağıda açıklanmıştır) önlemek için eski toplayıcıya geri dönen bir sürüm

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. Normal paketleri tanımlayan nesneler başka bir şeyi tanımlayan alanlar içermediğinden bu bir tasarım hatası. Bunlardan bazıları:

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

İdeal olarak, WORKSPACE dosyasını ayrıştırmak ile normal paketleri ayrıştırmak arasında daha fazla ayrım olması gerekir. Böylece Package, her ikisinin de ihtiyaçlarını karşılamaya gerek kalmaz. Bu ikisi birbiriyle iç içe geçmiş olduğundan maalesef bunu yapmak zordur.

Etiketler, Hedefler ve Kurallar

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

  1. Dosyalar: Derlemenin girdisi veya çıkışı olan öğeler. Bazel dilinde bunlara yapılar denir (başka bir yerde ele alınmıştır). Derleme sırasında oluşturulan dosyaların tümü hedef değildir. Bazel'in çıkışında ilişkili bir etiket olmaması normaldir.
  2. Kurallar: Bunlar, girişlerden çıkışları elde etmeye yönelik adımları açıklar. Bunlar genellikle bir programlama diliyle ilişkilendirilir (cc_library, java_library veya py_library gibi), ancak dilden bağımsız bazı türler de vardır (genrule veya filegroup gibi).
  3. Paket grupları: Görünürlük bölümünde ele alınmıştır.

Hedefin adına etiket denir. Etiketlerin söz dizimi @repo//pac/kage:name şeklindedir. Burada repo, etiketin bulunduğu deponun adı, pac/kage, BUILD dosyasının bulunduğu dizin ve name, paketin dizine göre dosyanın yoludur (etiket bir kaynak dosyayı ifade ediyorsa). Komut satırında bir hedeften bahsederken etiketin bazı bölümleri atlanabilir:

  1. Depo atlanırsa etiketin ana depoda olduğu kabul edilir.
  2. Paket bölümü çıkarılırsa (name veya :name gibi) etiket, geçerli çalışma dizininin paketine dahil edilir (üst düzey referanslar (..) içeren göreli yollara izin verilmez)

Bir kural türüne ("C++ kitaplığı" gibi) "kural sınıfı" adı verilir. Kural sınıfları Starlark'ta (rule() işlevi) veya Java'da ("yerel kurallar" olarak da bilinir, RuleClass türü) uygulanabilir. Uzun vadede her dile özgü kural Starlark'ta uygulanacak olsa da bazı eski kural aileleri (Java veya C++) şimdilik Java'dadır.

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

Kural sınıfları aşağıdaki gibi bilgiler içerir:

  1. Özellikleri (ör. srcs, deps): türleri, varsayılan değerleri, kısıtlamaları vb.
  2. Her bir özelliğe bağlı yapılandırma geçişleri ve yönler (varsa)
  3. Kuralın uygulanması
  4. Kuralın "genellikle" 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 ifade etmek için "Kural" kullanılır. Ancak Starlark'ta ve kullanıcılara yönelik belgelerde, "Kural" yalnızca kural sınıfını belirtmek için kullanılmalıdır. Hedef yalnızca bir "hedeftir". Ayrıca, RuleClass adında "class" olmasına rağmen bir kural sınıfı ile bu türdeki hedefler arasında Java devralma ilişkisi olmadığını unutmayın.

Skyframe

Bazel'in temel aldığı değerlendirme çerçevesine Skyframe adı verilir. Modeli, bir derleme sırasında oluşturulması gereken her şeyin, kenarları herhangi bir veri parçasından bağımlılıklarına işaret eden, yani yapıyı oluşturmak için bilinmesi gereken diğer veri parçalarına sahip, yönlendirilmiş bir döngüsel grafik halinde düzenlenir.

Grafikteki düğümlere SkyValue ve adları SkyKey olarak adlandırılır. Her ikisi de tamamen sabittir; bu kaynaklardan yalnızca sabit nesnelere erişilebilir. Bu değişken hemen hemen her zaman geçerliliğini korur ve geçerli değilse (örneğin, BuildConfigurationValue ve SkyKey grubunun üyesi olan BuildOptions bağımsız seçenek sınıfları için) bunları değiştirmemeye veya yalnızca dışarıdan gözlemlenemeyecek şekilde değiştirmemeye büyük çaba harcarız. Bu doğrultuda, Skyframe içinde hesaplanan her şeyin (yapılandırılmış hedefler gibi) sabit olması gerektiği sonucuna varır.

Skyframe grafiğini gözlemlemenin en uygun yolu, grafiği satır başına bir SkyValue olacak şekilde döken bazel dump --skyframe=deps komutunu çalıştırmaktır. Oldukça büyük bir dosya oluşturabileceği için bunu küçük derlemeler için yapmanız önerilir.

Skyframe, com.google.devtools.build.skyframe paketinde bulunur. Benzer ada sahip com.google.devtools.build.lib.skyframe paketi, Skyframe'in üzerine Bazel uygulamasını içerir. Skyframe hakkında daha fazla bilgiye buradan ulaşabilirsiniz.

Skyframe, belirli bir SkyKey değerini SkyValue olarak değerlendirmek için anahtarın türüne karşılık gelen SkyFunction işlevini çağırır. İşlev değerlendirilirken SkyFunction.Environment.getValue() işlevinin çeşitli aşırı yüklemelerini çağırarak Skyframe'dan başka bağımlılıklar isteyebilir. Bu, bu bağımlılıkları Skyframe'ın dahili grafiğine kaydetme yan etkisine sahiptir. Böylece Skyframe, bağımlılıkları değiştiğinde işlevi yeniden değerlendirmeyi bilir. Başka bir deyişle, Skyframe'in önbelleğe alma ve artımlı hesaplaması, SkyFunction ve SkyValue ayrıntı düzeyinde çalışır.

SkyFunction, kullanılamayan bir bağımlılık istediğinde getValue(), null değerini döndürür. Ardından işlev, null döndürerek kontrolü Skyframe'a geri vermelidir. Skyframe, daha sonra kullanılamayan bağımlılığı değerlendirir ve işlevi baştan başlatır. Ancak bu kez getValue() çağrısı, null olmayan bir sonuçla başarılı olur.

Bunun bir sonucu olarak, yeniden başlatmadan önce SkyFunction içinde yapılan tüm hesaplamaların tekrarlanması gerekir. Ancak bu süreye, önbelleğe alınan SkyValues bağımlılığını değerlendirmek için yapılan çalışmalar dahil değildir. Bu nedenle, genellikle aşağıdaki şekillerde bu sorunu çözmeye çalışıyoruz:

  1. Yeniden başlatma sayısını sınırlamak için bağımlılıklarını gruplar halinde bildirme (getValuesAndExceptions() kullanarak).
  2. Bir SkyValue'ü, bağımsız olarak hesaplanıp önbelleğe alınabilmeleri için farklı SkyFunction'lar tarafından hesaplanan ayrı parçalara ayırma. Bellek kullanımını artırma potansiyeli olduğundan bu işlem stratejik bir şekilde yapılmalıdır.
  3. SkyFunction.Environment.getState() kullanarak veya "Skyframe'ın arkasında" geçici bir statik önbelleğe sahip olarak yeniden başlatmalar arasında durumu depolama.

Temel olarak bu tür geçici çözümlere ihtiyacımız var çünkü rutin olarak yüz binlerce uçuştaki 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ü bir dildir. Python'un kısıtlı bir alt kümesi olarak düşünülür. Bu alt küme ç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 sahiptir. Turing-tam değil. Bu durum, bazı kullanıcıların (tüm kullanıcıların değil) dilde genel programlama görevlerini gerçekleştirmeye çalışmasını engeller.

Starlark, net.starlark.java paketinde uygulanır. Ayrıca bağımsız bir Go uygulaması da burada mevcuttur. Bazel'de kullanılan Java uygulaması şu anda bir yorumcu.

Starlark çeşitli bağlamlarda kullanılır. Örneğin:

  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 dosyalarına erişebilir.
  2. Kural tanımları. Yeni kurallar (yeni bir dil desteği gibi) bu şekilde tanımlanır. Bu bağlamda çalışan Starlark kodu, doğrudan bağımlılıkları tarafından sağlanan yapılandırmaya ve verilere erişebilir (bu konuda ilerleyen kısımlarda daha fazla bilgi vereceğiz).
  3. WORKSPACE dosyası. Harici depolar (ana kaynak ağacında olmayan kod) burada tanımlanır.
  4. Depo kural tanımları. Yeni harici depo türleri burada tanımlanır. Bu bağlamda çalışan Starlark kodu, Bazel'in çalıştığı makinede rastgele kod çalıştırabilir ve çalışma alanının dışına çıkabilir.

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

Starlark hakkında daha fazla bilgiye buradan ulaşabilirsiniz.

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, makul bir şekilde bir (hedef, yapılandırma) çifti olan "yapılandırılmış hedef"tir.

"Yükleme/analiz aşaması" olarak adlandırılır çünkü iki farklı bölüme ayrılabilir. Bu bölümler önceden serileştiriliyordu ancak artık zaman içinde çakışabilir:

  1. Paketleri yükleme, yani BUILD dosyalarını onları temsil eden Package nesnelerine dönüştürme
  2. Yapılandırılmış hedefleri analiz etmek, yani eylem grafiğini oluşturmak için kuralların uygulanmasını çalıştırmak

Komut satırında istenen yapılandırılmış hedeflerin geçişli kapatma işlemindeki her yapılandırılmış hedef, aşağıdan yukarıya doğru analiz edilmelidir. Yani önce yaprak düğümler, ardından komut satırındakiler analiz edilmelidir. Tek bir yapılandırılmış hedefin analizine yönelik girişler şunlardır:

  1. Yapılandırma. ("Bu kuralın nasıl oluşturulacağı"; örneğin, hedef platform, kullanıcının C++ derleyicisine iletilmesini istediği komut satırı seçenekleri gibi ayarlar)
  2. Doğrudan bağımlılıklar. Geçiş bilgi sağlayıcıları, analiz edilen kural tarafından kullanılabilir. Bu şekilde adlandırılırlar çünkü yapılandırılmış hedefin geçişli kapanmasında, sınıf yolundaki tüm .jar dosyaları veya bir C++ ikili programına bağlanması gereken tüm .o dosyaları gibi bilgilerin "toplayıcısı" bulunur.
  3. Hedefin kendisi. Bu, hedefin bulunduğu paketin yüklenmesi sonucunda ortaya çıkar. Kurallar için ise özellikler, genellikle önem teşkil eder.
  4. Yapılandırılmış hedefin uygulanması. Kurallar için bu, Starlark veya Java'da olabilir. Kural olmayan tüm yapılandırılan hedefler Java'da uygulanır.

Yapılandırılmış bir hedef analizinin sonucu:

  1. Kendisine bağlı hedefleri yapılandıran geçişli bilgi sağlayıcılar erişebilir
  2. Oluşturabildiği yapıları ve bu yapıları oluşturan işlemleri.

Java kurallarına sunulan API, Starlark kurallarının ctx bağımsız değişkeninin eşdeğeri olan RuleContext bağımsız değişkenidir. API'si daha güçlüdür ancak aynı zamanda Kötü Şeyler™ yapmanın da daha kolay olduğu bir yapıdır. Örneğin, zaman veya alan karmaşıklığı ikinci dereceden (veya daha kötü) olan kod yazmak, Bazel sunucusunu Java istisnasıyla kilitlemek ya da değişmezlikleri ihlal etmek (ör. bir Options örneğini yanlışlıkla değiştirmek veya yapılandırılmış bir hedefi değişken hale getirmek)

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, bir hedefin nasıl oluşturulacağını belirtir: Hangi platform için, hangi komut satırı seçenekleriyle vb.

Aynı hedef, aynı derlemedeki birden fazla yapılandırma için oluşturulabilir. Bu, ö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 yaptığımızda veya büyük bir Android uygulaması (birden fazla CPU mimarisi için yerel kod içeren) oluşturduğumuzda kullanışlıdır.

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

Bu durum, yalnızca test hedeflerini etkilemesine rağmen, örneğin istenen test çalıştırmalarının sayısı değişirse derlemenin tamamını yeniden analiz etmek zorunda kalmak gibi anormalliklere yol açar (bu durumu olmaması için yapılandırmaları "kırpma" planlarımız mevcuttur ancak henüz hazır değildir).

Bir kural uygulamasının yapılandırmanın bir parçası olması gerektiğinde, bunu RuleClass.Builder.requiresConfigurationFragments() kullanarak tanımında belirtmesi gerekir. Bunun amacı hem hatalardan kaçınmak (ör. Java parçasını kullanan Python kuralları) hem de Python seçenekleri değiştiğinde C++ hedeflerinin yeniden analiz edilmesine gerek kalmaması için yapılandırma kırpma işlemini kolaylaştırmaktır.

Bir kuralın yapılandırması, "üst" kuralının yapılandırmasıyla aynı olmayabilir. Bağımlılık kenarındaki 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 kenarında. Bu geçişler Attribute.Builder.cfg() içinde belirtilir ve bir Rule (geçişin gerçekleştiği yer) ile bir BuildOptions (orijinal yapılandırma) arasından bir veya daha fazla BuildOptions'ye (çıktı yapılandırması) giden işlevlerdir.
  2. Yapılandırılmış bir hedefe gelen herhangi bir kenarda. 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 dolayısıyla yürütme mimarisinde derlenmesi gerektiğini belirtmek için
  2. Belirli bir bağımlığın birden fazla mimari için derlenmesi gerektiğini beyan etmek (ör. büyük Android APK'larındaki yerel kod için)

Bir yapılandırma geçişi birden fazla yapılandırmayla sonuçlanırsa buna bölünmüş geçiş denir.

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

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

Geçiş bilgi sağlayıcıları, yapılandırılmış hedeflerin kendisine bağlı olan diğer yapılandırılmış hedefler hakkında bilgi vermesinin bir yoludur (ve _tek_ yoludur). Adında "geçişli" ifadesinin bulunmasının nedeni, genellikle yapılandırılmış bir hedefin geçişli kapatmasının bir tür toplama işlemi olmasıdır.

Java geçişli bilgi sağlayıcıları ile Starlark sağlayıcıları arasında genellikle bire bir yazışma olur (Bu API'nin doğrudan Java’nın harf çevirisinden daha Starlark tarzında olduğu düşünüldüğünde FileProvider, FilesToRunProvider ve RunfilesProvider öğelerinin birleşiminden oluşan DefaultInfo istisnadır). Anahtarları aşağıdakilerden biridir:

  1. Java sınıfı nesnesi. Bu özellik yalnızca Starlark'tan erişilemeyen sağlayıcılar için kullanılabilir. Bu sağlayıcılar TransitiveInfoProvider sınıfının alt sınıfıdır.
  2. Dize. Bu yöntem eskidir ve ad çakışmasına yol açabileceğinden kullanılması önerilmez. Bu tür geçişli bilgi sağlayıcılar, build.lib.packages.Info sınıfının doğrudan alt sınıflarıdır .
  3. Sağlayıcı sembolü. Bu, provider() işlevi kullanılarak Starlark'tan oluşturulabilir ve yeni sağlayıcılar oluşturmanın önerilen yoludur. Bu simge, Java'da bir 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ırmaya zamanımız olmadı) ve TransitiveInfoProvider alt sınıflarına Starlark'tan erişilemez.

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 tarafından yapılandırılan hedefler StarlarkRuleConfiguredTargetUtil.buildRule() üzerinden oluşturulur.

Yapılandırılmış hedef fabrikalar, döndürülen değerlerini oluşturmak için RuleConfiguredTargetBuilder kullanmalıdır. Şunlardan oluşur:

  1. filesToBuild, "bu kuralın temsil ettiği dosya grubu" şeklindeki belirsiz bir kavramdır. Bunlar, yapılandırılmış hedef, komut satırında veya bir Genrule dokümanının src'lerinde olduğunda oluşturulan dosyalardır.
  2. Normal ve veri çalıştırma dosyaları.
  3. Çıkış grupları. Bunlar, kuralın oluşturabileceği çeşitli "diğer dosya grupları"dır. Bunlara BUILD'deki filegroup kuralının output_group özelliği ve Java'daki OutputGroupInfo sağlayıcısı kullanılarak erişilebilir.

Çalışma dosyaları

Bazı ikili dosyaların çalışması için veri dosyalarına ihtiyacı vardır. Giriş dosyalarına ihtiyaç duyan testler buna örnek gösterilebilir. Bu, Bazel'de "runfiles" kavramıyla temsil edilir. "Çalışma dosyası ağacı", belirli bir ikili dosyanın veri dosyalarının dizin ağacıdır. Dosya sisteminde, çıkış ağaçları kaynağındaki dosyaları işaret eden ayrı simge bağlantıları içeren bir simge bağlantısı ağacı olarak oluşturulur.

Bir çalışma dosyası grubu, Runfiles örneği olarak temsil edilir. Kavramsal olarak, runfiles ağacındaki bir dosyanın yolundan, dosyayı temsil eden Artifact örneğine giden bir haritadır. İki nedenden dolayı tek bir Map'ten biraz daha karmaşıktır:

  • Bir dosyanın runfiles yolu çoğu zaman execpath ile aynıdır. Bu yöntemi, RAM'den tasarruf etmek için kullanırız.
  • Runfiles ağaçlarında, temsil edilmesi gereken çeşitli eski giriş türleri vardır.

Runfile'ler RunfilesProvider kullanılarak toplanır: Bu sınıfın bir örneği, yapılandırılmış bir hedefin (ör. kitaplık) runfile'lerini ve onun geçişli kapatma ihtiyaçlarını temsil eder ve iç içe yerleştirilmiş bir küme gibi toplanır (aslında, arka planda iç içe yerleştirilmiş kümeler kullanılarak uygulanır): Her hedef, bağımlılıklarının runfile'lerini birleştirir, kendi runfile'lerini ekler ve elde edilen kümeyi bağımlılık grafiğinde yukarı doğru gönderir. Bir RunfilesProvider örneği, biri kuralın "data" özelliği üzerinden bağımlı olduğu durumlar için, diğeri de gelen bağımlılık türleri için olmak üzere iki Runfiles örneği içerir. Bunun nedeni, bir hedefin bir veri özelliğine bağlı olduğunda bazen aksi takdirdekinden farklı çalıştırma dosyaları sunmasıdır. Bu, henüz kaldırmadığımız istenmeyen eski bir davranıştır.

İkili dosyaların çalıştırma dosyaları RunfilesSupport örneği olarak temsil edilir. RunfilesSupport, yalnızca bir eşleme olan Runfiles'den farklı olarak gerçekten oluşturulabilir. Bu işlem için aşağıdaki ek bileşenler gerekir:

  • Runfiles manifesti. Bu, çalıştırma dosyaları ağacının serileştirilmiş bir açıklamasıdır. Runfiles ağacının içeriği 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ştiğini varsayar.
  • Çıkış çalışma dosyası manifesti. Bu, özellikle de bazen sembolik bağlantıları desteklemeyen Windows'da, çalıştırma dosyaları ağaçlarını işleyen çalışma zamanı kitaplıkları tarafından kullanılır.
  • Çalıştırma dosyaları aracısı. Runfiles ağacının var olması için sembolik bağlantı ağacını ve sembolik bağlantıların işaret ettiği yapıyı derlemeniz gerekir. Bağımlılık kenarlarının sayısını azaltmak için runfiles aracısı, bunların tümünü temsil etmek üzere kullanılabilir.
  • RunfilesSupport nesnesinin temsil ettiği çalışma dosyalarını içeren ikili dosyayı çalıştırmak için komut satırı bağımsız değişkenleri.

Yönler

Oranlar, "hesaplamayı bağımlılık grafiğinde yaymanın" bir yoludur. Bu özellikler, Bazel kullanıcıları için burada açıklanmıştır. Protokol arabellekleri iyi bir motive edici örnektir: proto_library kuralı belirli bir dili bilmemelidir. Ancak herhangi bir programlama dilinde protokol arabellek mesajının ("protokol arabelleklerinin "temel birimi") uygulanmasını oluşturmak, proto_library kuralına bağlanmalıdır. Böylece aynı dildeki iki hedef aynı protokol arabelleğine bağlıysa mesaj yalnızca bir kez derlenir.

Yapılandırılmış hedefler gibi, Skyframe'da SkyValue olarak temsil edilirler ve yapılandırılmaları, yapılandırılmış hedeflerin yapılandırılmasına çok benzer: RuleContext'ye erişimi olan ConfiguredAspectFactory adlı bir fabrika sınıfları vardır ancak yapılandırılmış hedef fabrikalarının aksine, bağlı oldukları yapılandırılmış hedef ve sağlayıcıları hakkında da bilgi sahibidirler.

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

  1. AspectClass, özelliğin uygulanmasıdır. Java'da (bu durumda alt sınıftır) veya Starlark'ta (bu durumda StarlarkAspectClass örneğidir) olabilir. RuleConfiguredTargetFactory ile benzerdir.
  2. AspectDefinition, boyutun tanımıdır; gerektirdiği sağlayıcıları, sağladığı sağlayıcıları içerir ve uygun AspectClass örneği gibi uygulamaya dair bir referans içerir. RuleClass ile benzerdir.
  3. AspectParameters, bağımlılık grafiğinde yayılan bir unsuru parametriye etmenin bir yoludur. Şu anda dize ile dize eşlemesi olarak kullanılmaktadır. Protokol arabelleklerinin neden yararlı olduğuna dair iyi bir örnek protokol arabellekleridir: Bir dilin birden fazla API'si varsa protokol arabelleklerinin hangi API için oluşturulması gerektiğine dair bilgiler bağımlılık grafiğinde aşağıya doğru dağıtılmalıdır.
  4. Aspect, bağımlılık grafiğinde aşağı doğru yayılan bir yönü hesaplamak için gereken tüm verileri temsil eder. Bu öğe, görünüm sınıfından, tanımından ve parametrelerinden oluşur.
  5. RuleAspect, belirli bir kuralın hangi yönlerin yayılması gerektiğini belirleyen işlevdir. Bu, Rule -> Aspect fonksiyonudur.

Biraz beklenmedik bir karışıklık, bazı yönlerin başka yönlere de eklenebiliyor olmasıdır. Ö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 bunlardan bazıları protokol arabellekleridir. Bu durumda IDE yönü, (proto_library kuralı + Java proto yönü) çiftine bağlanmak ister.

Yönlere ilişkin özelliklerin karmaşıklığı AspectCollection sınıfında yakalanır.

Platformlar ve araç zincirleri

Bazel, çok platformlu derlemeleri destekler. Yani, derleme işlemlerinin çalıştırıldığı birden fazla mimari ve kodun derlendiği birden fazla mimari olabilir. Bu mimarilere Bazel dilinde platform adı verilir (tam dokümanlar burada).

Platform, kısıt ayarları ("CPU mimarisi" kavramı gibi) ile kısıt değerleri ("x86_64" gibi belirli bir CPU gibi) arasındaki bir anahtar/değer eşlemesi ile açıklanı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ı derleyiciler kullanılmasının gerekebilmesinden kaynaklanır. Örneğin, belirli bir C++ araç zinciri belirli bir işletim sisteminde çalışabilir ve başka işletim sistemlerini hedefleyebilir. Bazel, belirlenen yürütme ve hedef platforma göre kullanılan C++ derleyiciyi belirlemelidir (araç zincirleriyle ilgili dokümanlar burada).

Bunun için araç zincirleri, destekledikleri yürütme ve hedef platform kısıtlamaları kümesiyle ek açıklamaya sahiptir. Bunun için araç zinciri tanımı iki bölüme ayrılır:

  1. Bir araç zincirinin desteklediği yürütme ve hedef kısıtlamalarını tanımlayan ve ne tür bir araç zinciri (ör. C++ veya Java) olduğunu belirten bir toolchain() kuralı (ikincisi toolchain_type() kuralı ile gösterilir)
  2. Gerçek araç zincirini tanımlayan dile özgü bir kural (cc_toolchain() gibi)

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ı da bundan çok daha fazla bilgi içerdiğinden 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. Komut satırında --extra_execution_platforms komut satırı seçeneğini kullanarak

Kullanılabilir yürütme platformları grubu RegisteredExecutionPlatformsFunction içinde hesaplanır .

Yapılandırılmış bir hedefin hedef platformu PlatformOptions.computeTargetPlatform() tarafından belirlenir . Bu, nihayetinde birden fazla hedef platformu desteklemek istediğimiz için platformların listesidir ancak henüz uygulanmamıştır.

Yapılandırılmış bir hedef için kullanılacak araç zinciri grubu ToolchainResolutionFunction tarafından belirlenir. Aşağıdakilerin işlevidir:

  • Kayıtlı araç zincirleri grubu (WORKSPACE dosyasında ve yapılandırmada)
  • İstenilen 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çinde, 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 bulunur. Bu, temelde araç zinciri türünden (ToolchainTypeInfo örneği olarak temsil edilir) seçili araç zincirinin etiketine kadar olan bir haritadır. Araçlar zincirlerini değil, yalnızca etiketlerini içerdiği için "yüklenmemiştir" olarak adlandırılır.

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

Ayrıca, tek bir "ana makine" yapılandırması ve hedef yapılandırmaların --cpu gibi çeşitli yapılandırma işaretleriyle temsil edildiği eski bir sistemimiz de var. Yukarıdaki sisteme kademeli olarak geçiş yapıyoruz. Kullanıcıların eski yapılandırma değerlerini kullandığı durumları ele almak için eski işaretler ile yeni stil platform kısıtlamaları arasında çeviri yapmak üzere platform eşlemeleri uyguladık. Kodları PlatformMappingFunction dilindedir ve Starlark dışında bir "küçük dil" kullanır.

Sınırlamalar

Bazen bir hedefi yalnızca birkaç platformla uyumlu olarak tanımlamak isteyebilirsiniz. Bazel'in bu amaca ulaşmak için maalesef birden fazla mekanizması vardır:

  • Kuralla ilgili 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 kısıtlamalar kullanımdan kaldırılıyor ve Bazel'de kullanılamıyor ancak kaynak kodunda bu kısıtlamalara referanslar bulunabilir. Bunu yöneten özelliğe constraints= denir .

media_group() veenvironment()

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

Tüm derleme kuralları, hangi "ortamlar" için derlenebileceklerini belirtebilir. Burada "ortam", environment() kuralının bir örneğidir.

Bir kural için desteklenen ortamlar çeşitli şekillerde belirtilebilir:

  1. restricted_to= özelliği aracılığıyla. Bu, spesifikasyonun en doğrudan biçimidir; kuralın bu grup için desteklediği tam ortam grubunu belirtir.
  2. compatible_with= özelliği aracılığıyla. Bu, varsayılan olarak desteklenen "standart" ortamlara ek olarak bir kuralın desteklediği ortamları belirtir.
  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 alakalı bir grup eşe (ör. "CPU mimarileri", "JDK sürümleri" veya "mobil işletim sistemleri") aittir. Bir ortam grubunun tanımı, restricted_to= / environment() özellikleri tarafından başka şekilde belirtilmediği takdirde bu ortamlardan hangilerinin "varsayılan" olarak destekleneceğini içerir. Bu tür özellikleri olmayan bir kural, tüm varsayılanları devralır.
  5. Kural sınıfı varsayılan ayarıyla. Bu, belirtilen kural sınıfının tüm örnekleri için global varsayılanları geçersiz kılar. Bu, örneğin, her örneğin bu özelliği açıkça belirtmesi gerekmeden tüm *_test kurallarını test edilebilir hale getirmek için kullanılabilir.

environment() normal bir kural olarak uygulanırken environment_group() hem Target (EnvironmentGroup) değil Rule alt sınıfı hem de Starlark'ta (StarlarkLibrary.environmentGroup()) varsayılan olarak bulunan ve sonunda aynı adlı bir hedef oluşturan bir işlevdir. Bunun nedeni, her ortamın ait olduğu ortam grubunu, her ortam grubunun kendi varsayılan ortamlarını bildirmesi gerektiği için ortaya çıkacak döngüsel bağımlılığı önlemektir.

Derleme, --target_environment komut satırı seçeneğiyle belirli bir ortamla sınırlandırılabilir.

Kısıtlama kontrolü RuleContextConstraintSemantics ve TopLevelConstraintSemantics'te uygulanmaktadır.

Platform kısıtlamaları

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

Görünürlük

Çok sayıda geliştiricinin bulunduğu büyük bir kod tabanında çalışıyorsanız (Google gibi), diğer herkesin kodunuza keyfi olarak bağımlı olmasını önlemek için dikkatli olmanız gerekir. Aksi takdirde, Hyrum yasası uyarınca kullanıcılar uygulama ayrıntıları olarak kabul ettiğiniz davranışlar kullanmaya başlar.

Bazel, bunu görünürlük adlı mekanizmayla destekler: Belirli bir hedefe yalnızca görünürlük özelliğini kullanarak güvenilebileceğini belirtebilirsiniz. Bu özellik biraz özeldir çünkü etiket listesi içermesine rağmen bu etiketler, belirli bir hedefe işaret eden işaretçi yerine paket adları üzerinde bir kalıp kodlayabilir. (Evet, bu bir tasarım hatası.)

Bu, 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) ya da etiket listesi olabilir.
  • Etiketler, paket gruplarını (önceden tanımlanmış paket listesi), doğrudan paketleri (//pkg:__pkg__) veya paketlerin alt ağaçlarını (//pkg:__subpackages__) referans alabilir. Bu, //pkg:* veya //pkg/... kullanılan komut satırı söz diziminden farklıdır.
  • Paket grupları kendi hedefleri (PackageGroup) ve yapılandırılmış hedefleri (PackageGroupConfiguredTarget) olarak uygulanır. İstersek bunları basit kurallarla değiştirebiliriz. Mantıkları şu öğelerin yardımıyla uygulanır: //pkg/... gibi tek bir kalıba karşılık gelen PackageSpecification; tek bir package_group'ın packages özelliğine karşılık gelen PackageGroupContents ve bir package_group ile onun geçişli includes özelliğini toplayan PackageSpecificationProvider.
  • Görünürlük etiketi listelerinden bağımlılıklara geçiş, DependencyResolver.visitTargetVisibility ve birkaç başka yerde yapılır.
  • Gerçek kontrol CommonPrerequisiteValidator.validateDirectPrerequisiteVisibility()

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

Yapılandırılmış hedefler genellikle bağımlılıklarından bir dosya grubu toplar, kendi dosyalarını ekler ve toplanan grubu, kendisine bağlı olan yapılandırılmış hedeflerin de aynısını yapabilmesi için geçişli bir bilgi sağlayıcısına sarar. Örnekler:

  • Derleme için kullanılan C++ üstbilgi dosyaları
  • Bir cc_library nesnesinin geçişli kapatmasını temsil eden nesne dosyaları
  • Bir Java kuralının derlenmesi veya çalıştırılması için sınıf yolu klasöründe bulunması gereken .jar dosyası grubu
  • Bir Python kuralının geçişli kapatma kümesinde bulunan Python dosyası grubu

Bunu, örneğin List veya Set kullanarak saf bir şekilde yaparsak dörtlü bir bellek kullanımıyla karşılaşırız: N kural zinciri varsa ve her kural bir dosya ekliyorsa 1+2+...+N koleksiyon üyesi olur.

Bu sorunu çözmek için NestedSet kavramını ortaya çıkardık. Diğer NestedSet örneklerinden ve kendi üyelerinden oluşan bir veri yapısıdır. Bu sayede, kümelerden oluşan yönlendirilmiş bir döngüsel olmayan grafik oluşturur. Değişmezler ve üyeleri üzerinde iterasyon yapılabilir. Birden fazla iterasyon sırası (NestedSet.Order) tanımlarız: ön sipariş, son sipariş, topolojik (bir düğüm her zaman atalarının ardından gelir) ve "önemsizdir ancak her seferinde aynı olmalıdır".

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 çıktıyı oluşturmak için çalıştırılması gereken bir komut grubundan oluşur. Komutlar Action sınıfının örnekleri olarak, dosyalar ise Artifact sınıfının örnekleri olarak gösterilir. Bunlar, "işlem grafiği" adı verilen iki parçalı, yönlendirilmiş, döngüsel olmayan bir grafikte düzenlenir.

Yapılar iki türdedir: kaynak yapılar (Bazel çalışmaya başlamadan önce mevcut olan yapılar) ve türetilmiş yapılar (oluşturulması gereken yapılar). Türetilmiş yapı taşları da birden fazla türde olabilir:

  1. **Normal yapılar. **Bunlar, sağlama toplamları hesaplanarak güncelliği kontrol edilir ve kısayol olarak mtime kullanılır. Saat değişmemişse dosyayı sağlamayız.
  2. Çözümlenmemiş sembolik bağlantı yapıları. readlink() çağrısı yapılarak güncel olup olmadıkları kontrol edilir. Normal yapıların aksine bunlar, sarkan sembolik bağlantılar olabilir. Genellikle bazı dosyaları bir tür arşive paketlemenin ardından kullanılır.
  3. Ağaç yapıları. Bunlar tek dosyalar değil, dizin ağaçlarıdır. Dosyalardaki dosya grubu ve içerikleri kontrol edilerek bu dosyaların güncelliği kontrol edilir. Bu değerler 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: Mevcut saat değiştiği için yeniden derleme yapmak istemeyiz.

Kaynak yapıların ağaç yapıları veya çözülmemiş sembolik bağlantı yapıları olmaması için temel bir neden yoktur. Bunun nedeni, henüz bu özelliği uygulamamış olmamızdır (Ancak uygulamamız gerekir. BUILD dosyasında bir kaynak dizine referans vermek, Bazel'de bilinen ve uzun süredir devam eden birkaç yanlışlık sorunundan biridir. BAZEL_TRACK_SOURCE_DIRECTORIES=1 JVM mülkü tarafından etkinleştirilen ve bu tür durumlarda işe yarayan bir uygulamamız var).

Kayda değer bir Artifact türü aracıdır. Bunlar, MiddlemanAction işlevinin çıkışları olan Artifact örnekleriyle gösterilir. Bunlar, bazı durumları özel olarak belirtmek için kullanılır:

  • Toplayıcı aracılar, yapıları gruplandırmak için kullanılır. Bu, birçok işlem aynı büyük giriş grubunu kullanıyorsa N*M bağımlılık kenarı yerine yalnızca N+M (iç içe yerleştirilmiş kümelerle değiştirilir) bağımlılık kenarı olmasını sağlar.
  • Bağımlılık aracılarını planlamak, bir işlemin diğerinden önce çalıştırılmasını sağlar. Bunlar çoğunlukla linting için kullanılır ancak C++ derlemesi için de kullanılır (açıklama için CcCompilationContext.createMiddleman() bölümüne bakın).
  • Runfiles aracıları, bir runfiles ağacının bulunduğundan emin olmak için kullanılır. Böylece, çıkış manifest dosyasına ve runfiles ağacının referans verdiği her yapıya ayrı ayrı bağımlı olmanıza gerek kalmaz.

İşlemler, çalıştırılması gereken bir komut, ihtiyaç duyduğu ortam ve ürettiği çıkışlar olarak anlaşılır. Bir işlemin açıklamasının ana bileşenleri şunlardır:

  • Çalıştırılması gereken komut satırı
  • İhtiyacı olan giriş yapıları
  • Ayarlanması gereken ortam değişkenleri
  • Çalıştırılması gereken ortamı (platform gibi) açıklayan ek açıklamalar \

Bazel'in bildiği içeriğe sahip bir dosya yazmak gibi birkaç özel durum daha vardır. Bunlar AbstractAction sınıfının alt sınıfıdır. Java ve C++'nun kendi işlem türleri (JavaCompileAction, CppCompileAction ve CppLinkAction) olsa da işlemlerin çoğu bir SpawnAction veya StarlarkAction'tir (aynıdır, ayrı sınıflar olmamalıdır).

Sonunda her şeyi SpawnAction'e taşımak istiyoruz; JavaCompileAction oldukça yakın, ancak C++ .d dosyalarını ayrıştırması ve dahil etme taraması nedeniyle biraz özel bir durum.

İşlem grafiği çoğunlukla Skyframe grafiğine "yerleştirilir": Kavramsal olarak, bir işlemin yürütülmesi ActionExecutionFunction çağrısı olarak temsil edilir. Bir işlem grafiği bağımlılık kenarından Skyframe bağımlılık kenarına eşleme, ActionExecutionFunction.getInputDeps() ve Artifact.key()'te açıklanmaktadır ve Skyframe kenarlarının sayısını düşük tutmak için birkaç optimizasyona sahiptir:

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

Paylaşılan işlemler

Bazı eylemler, yapılandırılmış birden fazla hedef tarafından oluşturulur. Starlark kuralları, türetilen işlemlerini yalnızca yapılandırmaları ve paketleri tarafından belirlenen bir dizine yerleştirmelerine izin verilir. Ancak aynı paketteki kurallar çakışabilir ancak Java'da uygulanan kurallar, türetilmiş yapıları herhangi bir yere yerleştirebilir.

Bu bir hata olarak kabul edilir ancak örneğin bir kaynak dosyanın bir şekilde işlenmesi gerektiğinde ve bu dosyaya birden fazla kural tarafından referans verildiğinde (el hareketi-el hareketi) yürütme süresinde önemli ölçüde tasarruf sağladığı için bu hatadan kurtulmak gerçekten zordur. Bu, RAM'in bir kısmının kullanılmasına neden olur: Paylaşılan bir işlemin her örneğinin bellekte ayrı olarak depolanması gerekir.

İki işlem aynı çıkış dosyasını oluşturuyorsa tam olarak aynı olmalıdır: Aynı girişlere, aynı çıkışlara sahip olmalı ve aynı komut satırını çalıştırmalıdır. Bu eşdeğerlik ilişkisi Actions.canBeShared() içinde uygulanır ve her işleme bakılarak analiz ile yürütme aşamaları arasında doğrulanır. Bu işlem SkyframeActionExecutor.findAndStoreArtifactConflicts() içinde uygulanır ve Bazel'de derlemenin "evrensel" bir görünümünü gerektiren az sayıdaki yerden biridir.

Yürütme aşaması

Bazel, bu aşamada çıkış üreten komutlar gibi derleme işlemlerini başlatır.

Bazel, analiz aşamasından sonra ilk olarak hangi yapıların oluşturulması gerektiğini belirler. Bunun mantığı TopLevelArtifactHelper içinde kodlanmıştır; kabaca söylemek gerekirse, komut satırındaki yapılandırılmış hedeflerin filesToBuild'ı ve "bu hedef komut satırındaysa bu yapıları derle" ifadesini açıkça belirtmek için özel bir çıkış grubunun içeriğidir.

Sonraki adım, yürütme kökünü oluşturmaktır. Bazel, dosya sistemindeki farklı konumlardan kaynak paketleri (--package_path) okuma seçeneğine sahip olduğu için tam kaynak ağacıyla yerel olarak gerçekleştirilen işlemler sağlamalıdır. Bu, SymlinkForest sınıfı tarafından yönetilir ve analiz aşamasında kullanılan her hedefi not alarak ve her paketi, gerçek konumundan kullanılan bir hedefle sembolize eden tek bir dizin ağacı oluşturarak çalışır. Alternatif olarak, komutlara doğru yolları iletmek de (--package_path dikkate alınarak) mümkündür. Bunun nedeni:

  • Bir paket bir paket yolu girişinden diğerine taşındığında işlem komut satırlarını değiştirir (eskiden yaygın bir durumdu)
  • Bir işlem uzaktan çalıştırıldığında yerel olarak çalıştırıldığından farklı komut satırları oluşur.
  • Kullanılan araca özel bir komut satırı dönüşümü gerektirir (Java sınıf yolları ile C++ dahil etme yolları arasındaki farkı göz önünde bulundurun)
  • Bir işlemin komut satırı değiştirildiğinde işlem önbelleği girişi geçersiz kılınır
  • --package_path yavaş yavaş kullanımdan kaldırılıyor

Ardından Bazel, işlem grafiğini (işlemlerden ve bunların giriş ve çıkış yapılarından oluşan iki parçalı, yönlendirilmiş grafik) taramaya ve işlemleri çalıştırmaya başlar. Her işlemin yürütülmesi, SkyValue sınıfının ActionExecutionValue örneğiyle temsil edilir.

Bir işlemin çalıştırılması pahalı olduğundan Skyframe'ın arkasından erişilebilecek birkaç önbelleğe alma katmanımız vardır:

  • ActionExecutionFunction.stateMap, ActionExecutionFunction için Skyframe'ın yeniden başlatılmasını ucuz hale getirecek veriler içeriyor
  • 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 bulunan 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. Bu, yeni bir Bazel sunucusu başlatıldığında Skyframe grafiği boş olsa bile yerel işlem önbelleği isabetlerini alabileceği anlamına gelir.

Bu önbellekte, ActionCacheChecker.getTokenIfNeedToExecute() yöntemi kullanılarak isabet olup olmadığı kontrol edilir .

Adının aksine, türetilmiş yapının yolundan onu yayınlayan işleme giden bir haritadır. İşlem şu şekilde açıklanır:

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

Ayrıca, hâlâ geliştirme aşamasında olan ve önbelleğe çok fazla gitmemek için geçişli karma oluşturma işlemleri kullanan son derece deneysel bir "yukarıdan aşağıya işlem önbelleği" de vardır.

Giriş keşfi ve giriş budama

Bazı işlemler, yalnızca bir giriş grubuna sahip olmaktan daha karmaşıktır. Bir işlemin giriş grubunda yapılan değişiklikler iki şekilde gerçekleşir:

  • Bir işlem, yürütülmeden önce yeni girişler keşfedebilir veya girişlerinin bazılarının aslında gerekli olmadığına karar verebilir. C++ bu konudaki kanonik örnektir. Burada, her dosyayı uzak yürütücülere göndermek zorunda kalmamak için bir C++ dosyasının hangi başlık dosyalarını kullandığı konusunda bilinçli bir tahminde bulunmak daha iyidir. Bu nedenle, her başlık dosyasını "giriş" olarak kaydetmemeyi tercih edebiliriz. Bunun yerine, kaynak dosyayı geçişli olarak dahil edilen başlıklar için tarar ve yalnızca bu başlık dosyalarını #include ifadelerinde belirtilen girişler olarak işaretleriz (tam bir C önişleyici uygulamak zorunda kalmamak için fazladan tahminde bulunuruz). Bu seçenek şu anda Bazel'de "false" olarak sabitlenmiştir ve yalnızca Google'da kullanılır.
  • Bir işlem, yürütülmesi sırasında bazı dosyaların kullanılmadığını fark edebilir. C++'da buna ".d dosyaları" denir: Derleyici, hangi üstbilgi dosyalarının kullanıldığını daha sonra belirtir ve Bazel, Make'ten daha kötü bir artımlılığa sahip olmanın utancını yaşamamak için bu gerçeği kullanır. Bu, derleyiciye dayandığı için dahil etme tarayıcısına kıyasla daha iyi bir tahmin sunar.

Bunlar, İşlem'deki yöntemler kullanılarak uygulanır:

  1. Action.discoverInputs() çağrıldığında Gerekli olduğu belirlenen bir dizi iç içe yerleştirilmiş yapı döndürmelidir. Bunların kaynak yapılar olması gerekir. Böylece, eylem grafiğinde yapılandırılan hedef grafikte eşdeğeri olmayan bağımlılık kenarları olmaz.
  2. İşlem, Action.execute() çağrılarak yürütülür.
  3. Action.execute() işleminin sonunda, Bazel'e girişlerinin tümünün gerekli olmadığını bildirmek için Action.updateInputs() çağrılabilir. Kullanılan bir giriş kullanılmamış olarak raporlanırsa bu durum yanlış artımlı derlemelere neden olabilir.

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

Starlark işlemleri, ctx.actions.run() işlevinin unused_inputs_list= bağımsız değişkenini kullanarak bazı girişleri kullanılmamış olarak beyan etme olanağı sunar.

İşlem yürütmenin çeşitli yolları: Stratejiler/ActionContexts

Bazı işlemler farklı şekillerde çalıştırılabilir. Örneğin, bir komut satırı yerel olarak, yerel olarak ancak çeşitli türde korumalı alanlarda veya uzaktan çalıştırılabilir. Bunu somutlaştıran kavrama ActionContext (veya Strategy, çünkü yeniden adlandırma işleminin yarısını başarıyla tamamladık...) denir.

Bir iş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 sınıfının kurucusunda gerçekleşir. İşlem bağlamı türleri, ActionContext alt arayüzünü ifade eden ve işlem bağlamının uygulaması gereken arayüzü ifade eden Java Class örneğiyle tanımlanır.
  2. Mevcut işlem bağlamları arasından uygun olan seçilir ve ActionExecutionContext ile BlazeExecutor'e yönlendirilir .
  3. İşlemler, ActionExecutionContext.getContext() ve BlazeExecutor.getStrategy() kullanarak bağlam ister (bunu yapmanın tek bir yolu vardır...)

Stratejiler, işlerini yapmak için diğer stratejileri çağırabilir. Bu, örneğin, hem yerel hem de uzaktan işlem başlatan ve ardından hangisi önce biterse onu kullanan dinamik stratejide kullanılır.

Kalıcı işleyici süreçlerini (WorkerSpawnStrategy) uygulayan stratejiler dikkate değerdir. Buradaki fikir, bazı araçların uzun bir başlatma süresine sahip olması ve bu nedenle her işlem için yeni bir işlem başlatmak yerine işlemler arasında yeniden kullanılması gerektiğidir (Bazel, işleyici sürecinin bağımsız istekler arasında gözlemlenebilir durum taşımadığına dair sözünü güvence olarak kullandığından bu, olası bir doğruluk sorununu temsil eder).

Araç değişirse işleyici işleminin yeniden başlatılması gerekir. Bir işleyicinin yeniden kullanılıp kullanılamayacağı, WorkerFilesHash kullanılarak kullanılan aracın sağlama toplamının hesaplanması ile belirlenir. Bu, işlemin hangi girişlerinin aracın bir bölümünü temsil ettiğini ve hangilerinin girişleri temsil ettiğini bilmeye dayanır. Bu, işlemin yaratıcısı tarafından belirlenir: Spawn.getToolFiles() ve Spawn'un çalıştırma dosyaları aracın bir parçası olarak sayılır.

Stratejiler (veya işlem bağlamları) hakkında daha fazla bilgi:

  • İşlem çalıştırmayla ilgili çeşitli stratejiler hakkında bilgi edinmek için burayı ziyaret edin.
  • Bir işlemi hem yerel hem de uzaktan çalıştırarak hangisinin daha önce biteceğini görmek için kullandığımız dinamik strateji hakkında bilgi edinmek için burayı ziyaret edebilirsiniz.
  • İş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şlemden işleme değişir: Bir işlemin gerektirdiği kaynaklar ne kadar fazlaysa yerel makinenin aşırı yüklenmesini önlemek için aynı anda o kadar az örnek çalışmalıdır.

Bu, ResourceManager sınıfında uygulanır: Her işlemin, gerektirdiği yerel kaynakların tahmini bir ResourceSet örneği (CPU ve RAM) biçiminde ek açıklamayla belirtilmesi gerekir. Ardından işlem bağlamları yerel kaynaklar gerektiren bir işlem yaptığında ResourceManager.acquireResources() işlevini çağırır ve gerekli kaynaklar kullanılabilene kadar engellenir.

Yerel kaynak yönetiminin daha ayrıntılı bir açıklamasını burada bulabilirsiniz.

Çıkış dizininin yapısı

Her işlem için çıkış dizininde, çıkışlarının yerleştirileceği ayrı bir yer gerekir. Türetilmiş 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? İki istenilen özellik çakışıyor:

  1. Aynı derlemede iki yapılandırma oluşabilirse her ikisinin de aynı işlemin kendi sürümüne sahip olabilmesi için farklı dizinlere sahip olmaları gerekir. Aksi takdirde, iki yapılandırma aynı çıkış dosyasını oluşturan bir işlemin komut satırı gibi konularda anlaşamazsa Bazel hangi işlemi seçeceğini bilemez ("işlem çakışması").
  2. İki yapılandırma "kabaca" aynı şeyi temsil ediyorsa bu iki yapılandırmanın aynı ada sahip olması gerekir. Böylece, bir yapılandırmada yürütülen işlemler, komut satırları eşleşirse diğerinde yeniden kullanılabilir. Örneğin, Java derleyici için komut satırı seçeneklerinde yapılan değişiklikler, C++ derleme işlemlerinin yeniden çalıştırılmasına yol açmamalıdır.

Şu ana kadar, bu sorunu çözmek için yapılandırma kırpma sorunuyla benzerlikleri olan ilkeli bir yöntem bulamadık. Seçenekler hakkında daha ayrıntılı bilgiyi burada bulabilirsiniz. Başlıca sorunlu alanlar, Starlark kuralları (yazarlar genellikle Bazel'i tam olarak bilmezler) ve özelliklerdir. Bunlar, "aynı" çıktı dosyasını oluşturabilecek şeylerin alanına yeni bir boyut katar.

Mevcut yaklaşım, yapılandırmanın yol segmentinin, Java'da uygulanan yapılandırma geçişlerinin işlem çakışmalarına yol açmaması için çeşitli sonekler eklenmiş şekilde <CPU>-<compilation mode> olmasıdır. Buna ek olarak, kullanıcıların işlem çakışmalarına neden olmaması için Starlark yapılandırma geçişleri grubunun bir sağlama toplamı eklenir. Bu yöntem mükemmel değildir. Bu, OutputDirectories.buildMnemonic() içinde uygulanır ve her yapılandırma parçasının çıkış dizininin adına kendi parçasını eklemesine dayanır.

Testler

Bazel, test çalıştırma için zengin bir desteğe sahiptir. Şunları destekler:

  • Testleri uzaktan çalıştırma (uzak yürütme arka ucu mevcutsa)
  • Testleri paralel olarak birden çok kez çalıştırma (flaking'i kaldırmak veya zamanlama verileri toplamak için)
  • Testleri bölme (hız için aynı testteki test durumlarını birden fazla işleme bölme)
  • Güvenilir olmayan testleri yeniden çalıştırma
  • Testleri test paketleri halinde gruplandırma

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

  • Derlemesi testin çalıştırılmasına neden olan yapılardır. Bu, serileştirilmiş TestResultData mesajı içeren bir "önbellek durumu" dosyasıdır
  • Testin çalıştırılma sayısı
  • 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 işlemi sırasında test paketleri yinelemeli bir şekilde genişletilir. Genişletme işlemi TestsForTargetPatternFunction üzerinde uygulanır. Biraz şaşırtıcı olan bir nokta, bir test paketinde test belirtilmemişse paketteki her testin kastedildiğidir. Bu, test grubu kurallarına $implicit_tests adlı bir gizli özellik ekleyerek Package.beforeBuild()'te uygulanır.

Ardından, komut satırı seçeneklerine göre testler 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()'ten çağrılır. Sonuç TargetPatternPhaseValue.getTestsToRunLabels() içine yerleştirilir. Filtrelenebilecek kural özelliklerinin yapılandırılamamasının nedeni, bu işlemin analiz aşamasından önce gerçekleşmesidir, bu nedenle yapılandırmanın kullanılamamasıdır.

Bu değer daha sonra BuildView.createResult() içinde daha fazla işlenir: Analizi başarısız olan hedefler filtrelenir ve testler, özel ve münhasır olmayan testlere ayrılır. Ardından AnalysisResult içine yerleştirilir. Bu sayede ExecutionTool hangi testlerin çalıştırılacağını bilir.

Bu ayrıntılı sürece biraz şeffaflık kazandırmak için, komut satırında belirli bir hedef belirtildiğinde hangi testlerin çalıştırılacağını öğrenmek amacıyla tests() sorgu operatörü (TestsFunction uygulamasında uygulanmıştır) kullanılabilir. Ne yazık ki bir yeniden uygulama olduğundan muhtemelen birkaç şekilde yukarıdakilerden sapacaktır.

Test çalıştırma

Testler, önbellek durumu yapılarını isteyerek çalıştırılır. Bu, bir TestRunnerAction'ün yürütülmesine neden olur. Bu TestRunnerAction, testi istenen şekilde çalıştıran --test_strategy komut satırı seçeneği tarafından seçilen TestActionContext'ı çağırır.

Testler, kendilerinden ne beklendiğini belirtmek için ortam değişkenlerini kullanan ayrıntılı bir protokole göre çalıştırılır. Bazel'in testlerden ne beklediğini ve testlerin Bazel'den ne beklediğini ayrıntılı olarak burada bulabilirsiniz. En basit şekilde, 0 olan çıkış kodunun başarılı olduğu, diğer her şey ise başarısız olduğu anlamına gelir.

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

  • test.xml: Test bölümündeki test durumlarını ayrıntılı olarak açıklayan JUnit tarzı bir XML dosyası
  • test.log, testin konsol çıkışıdır. stdout ve stderr ayrılmaz.
  • test.outputs, "tanımlanmamış çıkış dizini"dir. Terminale yazdıklarının yanı sıra dosya da yayınlamak isteyen testler tarafından kullanılır.

Test yürütme sırasında, normal hedefleri oluştururken yapılamayan iki işlem yapılabilir: özel test yürütme ve çıkış aktarımı.

Bazı testlerin, diğer testlerle paralel olarak değil, özel modda çalıştırılması gerekir. Bu, test kuralına tags=["exclusive"] ekleyerek veya testi --test_strategy=exclusive ile çalıştırarak elde edilebilir . Her özel test, "ana" derlemeden sonra testin yürütülmesini isteyen ayrı bir Skyframe çağrısı tarafından çalıştırılır. Bu, SkyframeExecutor.runExclusiveTest()'te uygulanır.

İşlem tamamlandığında terminal çıkışı dökümü alınan normal işlemlerin aksine, kullanıcı uzun süren bir testin ilerleme durumu hakkında bilgi edinmek için testlerin çıkışının akış şeklinde sunulmasını isteyebilir. Bu, --test_output=streamed komut satırı seçeneğiyle belirtilir ve farklı testlerin çıktılarının birbirine karışmaması için özel test yürütme anlamına gelir.

Bu, uygun şekilde adlandırılmış StreamedTestOutput sınıfında uygulanır ve söz konusu testin test.log dosyasında yapılan değişiklikleri sorgulayarak ve Bazel'in geçerli olduğu terminale yeni baytlar atarak çalışır.

Çalıştırılan testlerin sonuçları, çeşitli etkinlikleri (ör. TestAttempt, TestResult veya TestingCompleteEvent) gözlemleyerek etkinlik arabasında kullanılabilir. Bu sonuçlar, Build Event Protocol'a aktarılır ve AggregatingTestListener tarafından konsola yayınlanır.

Kapsam koleksiyonu

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

Kapsam toplamak için her test yürütme işlemi, collect_coverage.sh adlı bir komut dosyası içine alınır.

Bu komut dosyası, kapsam toplamayı etkinleştirmek ve kapsam dosyalarının kapsam çalışma zamanları tarafından nereye yazılacağını belirlemek için testin ortamını oluşturur. Ardından testi çalıştırır. Bir testin kendisi birden fazla alt işlem çalıştırabilir ve birden fazla farklı programlama dilinde yazılmış parçalardan (ayrı kapsam toplama çalışma zamanlarıyla) oluşabilir. Sarmalayıcı komut dosyası, gerekirse ortaya çıkan dosyaları LCOV biçimine dönüştürmekten ve bunları tek bir dosyada birleştirmekten sorumludur.

collect_coverage.sh'ün araya girmesi test stratejileri tarafından yapılır ve collect_coverage.sh'ün testin girişlerinde olması gerekir. Bu, yapılandırma işareti --coverage_support değerine çözülen :coverage_support gizli özelliğiyle yapılır (TestConfiguration.TestOptions.coverageSupport bölümüne bakın).

Bazı diller çevrimdışı enstrümasyon kullanır. Bu durumda, kapsam enstrümasyonu derleme zamanında eklenir (ör. C++). Diğer diller ise online enstrümasyon kullanır. Bu durumda, kapsam enstrümasyonu yürütme zamanında eklenir.

Referans kapsamı da temel kavramlardan biridir. Bu, bir kütüphanenin, ikili programın veya testin kapsamıdır. Bu yöntem, bir ikili programın test kapsamını hesaplamak istediğinizde tüm testlerin kapsamını birleştirmenin yeterli olmaması sorununu çözer. Bunun nedeni, ikili programda herhangi bir teste bağlı olmayan kodlar bulunabilmesidir. Bu nedenle, her ikili için yalnızca kapsam toplanan dosyaları içeren ve kapsamlı satırlar içermeyen bir kapsam dosyası yayınlarız. Bir hedefin temel kapsam dosyası bazel-testlogs/$PACKAGE/$TARGET/baseline_coverage.dat adresindedir . --nobuild_tests_only işaretini Bazel'e iletirseniz testlerin yanı sıra ikili dosyalar ve kitaplıklar için de oluşturulur.

Temel kapsam şu anda bozuk.

Her kural için kapsam toplama amacıyla iki dosya grubunu izleriz: enstrümante edilmiş dosya grubu ve enstrümantasyon meta veri dosyası grubu.

Enstrümante edilmiş dosya grubu, enstrümante edilecek bir dosya grubudur. Çevrimiçi kapsam çalışma zamanlarında, hangi dosyaların enstrümante edileceğine karar vermek için çalışma zamanında kullanılabilir. Ayrıca temel kapsamı uygulamak için de kullanılır.

Araç meta veri dosyaları grubu, bir testin Bazel'in ihtiyaç duyduğu LCOV dosyalarını oluşturması için gereken ekstra dosyalar kümesidir. Pratikte bu, çalışma zamanına özgü dosyalardan oluşur. Örneğin, gcc derleme sırasında .gcno dosyaları oluşturur. Kapsam modu etkinleştirilirse bunlar test işlemleri giriş grubuna eklenir.

Kapsamın toplanıp toplanmadığı BuildConfiguration içinde saklanır. Test işlemini ve işlem grafiğini bu bite göre değiştirmenin kolay bir yolu olduğu için bu kullanışlıdır ancak bu bit değiştirilirse tüm hedeflerin yeniden analiz edilmesi gerektiği anlamına da gelir (C++ gibi bazı diller, kapsam toplayabilen kod yayınlamak için farklı derleyici seçenekleri gerektirir. Bu durumda zaten yeniden analiz yapılması gerektiğinden bu sorun biraz hafifletilir).

Kapsam destek dosyaları, çağrı politikası tarafından geçersiz kılınabilmeleri için etiketler aracılığıyla dolaylı bir bağımlılık ilişkisi içinde kullanılır. Bu sayede, Bazel'in farklı sürümleri arasında farklılık gösterebilirler. İdeal olarak bu farklılıklar kaldırılır ve bunlardan biri standartlaştırılır.

Ayrıca, Bazel çağrısında her test için toplanan kapsamı birleştiren bir "kapsam raporu" da oluştururuz. Bu işlem CoverageReportActionFactory tarafından yönetilir ve BuildView.createResult() çağrısından alını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'in çeşitli grafikler hakkında çeşitli sorular sormak için kullandığı küçük bir dil 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ği 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ıyla uygulanır. QueryFunction sınıfını alt sınıflandırarak ek sorgu işlevleri yapılabilir. Sorgu sonuçlarını akış olarak yayınlamaya izin vermek için, sonuçları bir veri yapısında toplamak yerine QueryFunction işlevine bir query2.engine.Callback iletilir. QueryFunction işlevi, döndürmek istediği sonuçlar için bu işlevi çağırır.

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

Bazı sorgu çıkış biçimlerinin (proto kesinlikle proto) ince bir şartı, paketi yüklemenin çıktıyı farklılaştırabilmesi ve belirli bir hedefin değişip değişmediğini belirleyebilmesi için Bazel'in paket yüklemenin sağladığı bilgileri yaymasıdır. Bu nedenle, özellik değerlerinin serileştirilebilir olması gerekir. Bu nedenle, karmaşık Starlark değerlerine sahip özellikler içermeyen özellik türleri çok azdır. Genelde, bir etiket kullanmak ve karmaşık bilgileri bu etiketle kurala eklemek geçici çözüm olarak kullanılır. Bu geçici çözüm pek tatmin edici değil ve bu şartın kaldırılması çok iyi olur.

Modül sistemi

Bazel'e modüller ekleyerek genişletilebilir. Her modül BlazeModule alt sınıfı olmalıdır (Bazel eskiden Blaze olarak biliniyordu. Bu geçmişe ait bir kalıntıdır) ve bir komut yürütülürken çeşitli etkinlikler hakkında bilgi verilir.

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

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

BlazeModule tarafından sunulan uzantı noktaları grubu rastgele. İyi tasarım ilkelerine örnek olarak kullanmayın.

Etkinlik otobüsü

BlazeModules'in Bazel'in geri kalanıyla iletişim kurmasının ana yolu bir etkinlik veri yoludur (EventBus): Her derleme için yeni bir örnek oluşturulur, Bazel'in çeşitli bölümleri buraya etkinlik gönderebilir ve modüller ilgilendikleri etkinlikler için dinleyici 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)
  • Hedef oluşturuldu (başarılı veya başarısız) (TargetCompleteEvent)
  • Bir test çalıştırıldı (TestAttempt, TestSummary)

Bu etkinliklerin bazıları Bazel dışında Derleme Etkinliği Protokolü'nde temsil edilir (BuildEvent olarak adlandırılır). Bu sayede, yalnızca BlazeModule'ler değil, Bazel sürecinin dışındaki öğeler de derlemeyi gözlemleyebilir. Bunlara protokol mesajları içeren bir dosya olarak erişilebilir veya Bazel, etkinlikleri yayınlamak için bir sunucuya (Derleme Etkinliği Hizmeti olarak adlandırılır) bağlanabilir.

Bu işlem, build.lib.buildeventservice ve build.lib.buildeventstream Java paketlerinde uygulanır.

Harici depolar

Bazel başlangıçta bir monorepo'da (yapılması gereken her şeyi içeren tek bir kaynak ağacında) kullanılmak üzere tasarlanmıştı. Ancak Bazel bunun her zaman geçerli olmadığı bir dünyada yaşıyor. "Harici depolar", bu iki dünya arasında köprü oluşturmak için kullanılan bir soyutlamadır: Derleme için gerekli olan ancak ana kaynak ağacında bulunmayan kodu temsil eder.

WORKSPACE dosyası

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

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

@foo adlı depoda sonuçlar kullanılabilir. Bunun karmaşık hale gelmesinin nedeni, Starlark dosyalarında yeni depo kuralları tanımlanabilmesidir. Bu kurallar daha sonra yeni Starlark kodu yüklemek için kullanılabilir. Bu kod da yeni depo kuralları tanımlamak için kullanılabilir.

Bu durumu ele almak için WORKSPACE dosyasının (WorkspaceFileFunction içinde) ayrıştırması, load() ifadeleriyle tanımlanan parçalara bölünür. Parça dizini WorkspaceFileKey.getIndex() ile gösterilir ve X dizine kadar WorkspaceFileFunction hesaplamak, X. load() ifadesine kadar değerlendirmek anlamına gelir.

Depoları getirme

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

Depoyu getirme işlemi aşağıdaki adımlarda gerçekleşir:

  1. PackageLookupFunction, bir depoya ihtiyacı olduğunu fark eder ve SkyKey olarak RepositoryLoaderFunction çağıran bir RepositoryName oluşturur
  2. RepositoryLoaderFunction, isteği belirsiz nedenlerle RepositoryDelegatorFunction'e yönlendirir (kodda bunun Skyframe'ın yeniden başlatılması durumunda öğelerin yeniden indirilmesini önlemek için olduğu belirtilir ancak bu çok sağlam bir gerekçe değildir)
  3. RepositoryDelegatorFunction, istenen depo bulunana kadar WORKSPACE dosyasının parçalarını iterleyerek getirmesi istenen depo kuralını bulur
  4. Depo getirmeyi uygulayan uygun RepositoryFunction bulunur. Bu, deponun Starlark uygulaması veya Java'da uygulanan depolar için sabit kodlanmış bir haritadır.

Bir deposu getirme işlemi çok pahalı olabileceğinden çeşitli önbelleğe alma katmanları vardır:

  1. İndirilen dosyalar için sağlamalarına (RepositoryCache) göre anahtarlanmış bir önbellek vardır. Bunun için sağlamanın WORKSPACE dosyasında bulunması gerekir ancak bu, hermetiklik açısından zaten iyi bir şeydir. Bu, çalıştırıldığı çalışma alanından veya çıkış tabanından bağımsız olarak aynı iş istasyonundaki her Bazel sunucu örneği tarafından paylaşılır.
  2. $OUTPUT_BASE/external altında her depo için, depoyu getirmek için kullanılan kuralın sağlama toplamı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 yeniden getirilmez. Bu, RepositoryDelegatorFunction.DigestWriter üzerinde uygulanır .
  3. --distdir komut satırı seçeneği, indirilecek yapıları aramak için kullanılan başka bir önbelleği belirtir. Bu, Bazel'in internetten rastgele öğeler getirmemesi gereken kurumsal ayarlarda kullanışlıdır. Bu, DownloadManager tarafından uygulanır.

Bir depo indirildikten sonra, içindeki yapıların kaynak yapı olarak değerlendirilmesi gerekir. Bazel genellikle kaynak yapıların güncelliğini kontrol etmek için stat() işlevini çağırır. Bu yapıların bulunduğu deposun tanımı değiştiğinde de yapıların geçerliliği iptal edilir. Bu nedenle, bu durum bir sorun teşkil eder. Bu nedenle, harici bir depoda bulunan bir yapının FileStateValue'lerinin, harici depolarına bağlı olması gerekir. Bu işlem ExternalFilesHelper tarafından yönetilir.

Yönetilen dizinler

Bazen harici depoların, Workspace kök dizininin altındaki dosyaları (ör. indirilen paketleri kaynak ağacın bir alt dizininde barındıran bir paket yöneticisi) değiştirmesi gerekir. Bu, Bazel'in kaynak dosyaların yalnızca kullanıcı tarafından değiştirildiği ve kendisi tarafından değiştirilmediği varsayımı ile çelişir ve paketlerin, çalışma alanı kökünün altındaki her dizine atıfta bulunmasına olanak tanır. Bu tür harici depoların çalışmasını sağlamak için Bazel iki şey yapar:

  1. Kullanıcının, Bazel'in erişmesine izin verilmeyen Workspace alt dizinlerini belirtmesine olanak tanır. Bunlar .bazelignore adlı bir dosyada listelenir ve işlev BlacklistedPackagePrefixesFunction'te uygulanır.
  2. Workspace'in alt dizininden, yönetildiği harici depolamaya giden eşlemeyi ManagedDirectoriesKnowledge olarak kodlarız ve bunlara atıfta bulunan FileStateValue'leri normal harici depolar için olduğu gibi yönetiriz.

Depo eşlemeleri

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

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

Alternatif olarak, elmasları birleştirmek için de kullanılabilir. Bir depo @guava1//, bir diğeri ise @guava2// bağımlıysa depo eşleme, standart @guava// deposu kullanmak için her iki deponun da yeniden eşlenmesine olanak tanır.

Eşleme, WORKSPACE dosyasında tekil depo tanımları için repo_mapping özelliği olarak belirtilir. Ardından Skyframe'da WorkspaceFileValue üyesi olarak görünür ve aşağıdakilere bağlanır:

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

JNI bitleri

Bazel sunucusu _çoğunlukla _Java ile yazılmıştır. Java'nın tek başına yapamadığı veya uyguladığımızda tek başına yapamadığı kısımlar istisnadır. Bu, çoğunlukla dosya sistemiyle etkileşim, işlem kontrolü ve diğer çeşitli düşük düzeyli işlemlerle 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ışı yayınlamak basit bir şey gibi görünse de birden fazla işlemin (bazen uzaktan) çalıştırılması, ayrıntılı önbelleğe alma, güzel ve renkli bir terminal çıkışı elde etme ve uzun süre çalışan bir sunucuya sahip olma isteği, bu işlemin basit olmadığını gösterir.

RPC çağrısı istemciden geldikten hemen sonra, yazdırılan verileri istemciye yönlendiren iki RpcOutputStream örneği oluşturulur (stdout ve stderr için). Bunlar daha sonra bir OutErr içine (bir (stdout, stderr) çifti) alınır. Konsolda yazdırılması gereken her şey bu akışlardan geçer. Ardından bu akışlar BlazeCommandDispatcher.execExclusively()'e aktarılır.

Çıktı, varsayılan olarak ANSI kaçış sıralarıyla yazdırılır. Bunlar istenmediğinde (--color=no) bir AnsiStrippingOutputStream tarafından kaldırı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 istemcinin terminal çıkışında (sunucudan farklıdır) bulunabilir. Bir işlem ikili çıkış (bazel query --output=proto gibi) üretirse stdout'un karıştırılmamasına dikkat edilir.

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

Bazı EventHandler uygulamaları, aldıkları etkinlikleri depolar. Bu, önbelleğe alınmış çeşitli işlemlerden (ör. önbelleğe alınmış yapılandırılmış bir hedef tarafından yayınlanan uyarılar) kaynaklanan bilgileri kullanıcı arayüzünde yeniden oynatmak için kullanılır.

Bazı EventHandler'ler, sonunda etkinlik otobüsüne ulaşan etkinliklerin yayınlanmasına da izin verir (normal Event'lar burada _not _görünmez). Bunlar ExtendedEventHandler'ün uygulamalarıdır ve ana kullanım alanları, önbelleğe alınmış EventBus etkinliklerini yeniden oynatmaktır. Bu EventBus etkinliklerinin tümü Postable'u uygular ancak EventBus'te yayınlanan her şey bu arayüzü uygulamaz. Yalnızca bir ExtendedEventHandler tarafından önbelleğe alınanlar bu arayüzü uygular (bunun yapılması iyi olur ve çoğu şey bunu yapar ancak zorunlu tutulmaz).

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

  • Etkinlik otobüsü
  • Reporter aracılığıyla aktarılan etkinlik akışı

Komut yürütme mekanizmasının (örneğin, Bazel'in geri kalanı) istemciye giden RPC akışıyla tek doğrudan bağlantısı, bu akışlara doğrudan erişime olanak tanıyan Reporter.getOutErr() üzerindendir. Yalnızca bir komutun büyük miktarlarda olası ikili program verisi (bazel query gibi) dökümü alması gerektiğinde kullanılır.

Bazel'i profilleme

Bazel hızlıdır. Bazel de yavaştır çünkü derlemeler, dayanılabilirliğin sınırına kadar büyür. Bu nedenle Bazel, derlemeleri ve Bazel'in kendisini profillemek için kullanılabilecek bir profilleyici içerir. Profiler adlı bir sınıfta uygulanır. Varsayılan olarak etkindir. Bununla birlikte, ek yükünün tolere edilebilir olması için yalnızca kısaltılmış verileri kaydeder. Komut satırı --record_full_profiler_data, mümkün olan her şeyi kaydetmesini sağlar.

Chrome profilleyici biçiminde bir profil oluşturur. Bu profili en iyi şekilde Chrome'da görüntüleyebilirsiniz. Veri modeli, görev yığınlarına benzer: Görevler başlatılabilir ve sonlandırılabilir ve bunların birbirine düzgün bir şekilde yerleştirilmesi gerekir. Her Java iş parçacığı kendi görev yığınını alır. TODO: Bu, işlemler ve devam etme aktarma stiliyle nasıl çalışır?

Profilleyici sırasıyla BlazeRuntime.initProfiler() ve BlazeRuntime.afterCommand()'te başlatılır ve durdurulur. Her şeyi profilleyebilmemiz için mümkün olduğunca uzun süre etkin olmaya çalışır. Profilinize bir şey eklemek için Profiler.instance().profile() numaralı telefonu arayın. Kapanışı görevin sonunu temsil eden bir Closeable döndürür. En iyi performansı try-with-resources ifadeleriyle gösterir.

Ayrıca MemoryProfiler'te basit bellek profilleme de yapıyoruz. 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'in iki ana testi vardır: Bazel'i "kara kutu" olarak gözlemleyen ve yalnızca analiz aşamasını çalıştıran testler. İlkine "entegrasyon testleri", ikincisine ise "birim testleri" diyoruz. Ancak bunlar daha az entegre olan entegrasyon testlerine daha çok benziyor. Ayrıca, gerekli olduğu bazı gerçek birim testlerimiz de vardır.

Entegrasyon testlerinin iki türü vardır:

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

BuildIntegrationTestCase, çoğu test senaryosu için iyi donanıma sahip olduğundan tercih edilen entegrasyon testi çerçevesidir. Bir Java çerçevesi olduğundan, birçok yaygın geliştirme aracıyla hata ayıklama ve sorunsuz entegrasyon sağlar. Bazel deposunda birçok BuildIntegrationTestCase sınıfı örneği bulunmaktadır.

Analiz testleri, BuildViewTestCase sınıfının alt sınıfları olarak uygulanır. BUILD dosyaları yazmak için kullanabileceğiniz bir geçici dosya sistemi vardır. Ardından çeşitli yardımcı yöntemler, yapılandırılmış hedefler isteyebilir, yapılandırmayı değiştirebilir ve analiz sonucuyla ilgili çeşitli iddialarda bulunabilir.