Bazel kod tabanı

Sorun bildir Kaynağı görüntüle Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

Bu belgede, kod tabanı ve Bazel'in yapısı açıklanmaktadır. Bu doküman, son kullanıcılar için değil, Bazel'e katkıda bulunmak isteyen kişiler için hazırlanmıştır.

Giriş

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

Bu belge, yolculuğun ortasında olanların kendilerini düz yolun kaybolduğu karanlık bir ormanda bulmaması için kod tabanına genel bir bakış sunarak üzerinde çalışmaya başlamayı kolaylaştırmayı amaçlamaktadır.

Bazel'in kaynak kodunun herkese açık sürümü, GitHub'da github.com/bazelbuild/bazel adresinde bulunur. Bu, "doğruluğun kaynağı" değildir. Google dışında kullanışlı olmayan ek işlevler içeren Google'a ait bir kaynak ağacından türetilmiştir. Uzun vadeli hedef, GitHub'ı bilgi kaynağı haline getirmektir.

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

İstemci/sunucu mimarisi

Bazel'in büyük bir kısmı, derlemeler arasında RAM'de kalan bir sunucu sürecinde bulunur. Bu, Bazel'in derlemeler arasında durumu korumasına olanak tanır.

Bu nedenle, Bazel komut satırında iki tür seçenek bulunur: 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. İlk tür "başlangıç seçeneği" olarak adlandırılır ve sunucu sürecini bir bütün olarak etkilerken ikinci tür olan "komut seçeneği" yalnızca tek bir komutu etkiler.

Her sunucu örneğinin ilişkili tek bir çalışma alanı (kaynak ağaçları koleksiyonu, "depolar" olarak bilinir) vardır ve her çalışma alanında genellikle tek bir etkin sunucu örneği bulunur. Özel bir çıkış tabanı belirtilerek bu durum önlenebilir (daha fazla bilgi için "Dizin düzeni" bölümüne bakın).

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

  1. Kendini daha önce çıkarıp çıkarmadığını kontrol eder. Aksi takdirde, bu işlem yapılır. Sunucunun uygulanması bu noktada devreye girer.
  2. Çalışan etkin bir sunucu örneği olup olmadığını kontrol eder: Çalışıyor, doğru başlangıç seçeneklerine sahip ve doğru çalışma alanı dizinini kullanıyor. Çalışan sunucuyu, sunucunun dinlediği bağlantı noktasını içeren bir kilit dosyasının bulunduğu $OUTPUT_BASE/serverdizine bakarak bulur.
  3. Gerekirse eski sunucu sürecini sonlandırır.
  4. Gerekirse yeni bir sunucu süreci başlatır.

Uygun bir sunucu süreci hazır olduğunda, çalıştırılması gereken komut bir gRPC arayüzü üzerinden iletilir ve Bazel'in çıkışı terminale geri yönlendirilir. Aynı anda yalnızca bir komut çalıştırılabilir. Bu, C++ ve Java'da parçalar içeren ayrıntılı bir kilitleme mekanizması kullanılarak uygulanır. bazel version komutunun başka bir komutla paralel olarak çalıştırılamaması biraz utanç verici olduğundan, birden fazla komutu paralel olarak çalıştırmak için bazı altyapılar vardır. Ana engel, BlazeModules'nin yaşam döngüsü ve BlazeRuntime içindeki bazı durumlardır.

Bir komutun sonunda Bazel sunucusu, istemcinin döndürmesi gereken çıkış kodunu iletir. İlginç bir nokta da bazel run komutunun uygulanması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 işleminden yapamaz. Bu nedenle, bunun yerine istemciye hangi ikiliyi exec() ve hangi bağımsız değişkenlerle kullanması gerektiğini söyler.

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

İstemcinin kaynak kodu src/main/cpp altında, sunucuyla iletişim kurmak için kullanılan protokol ise src/main/protobuf/command_server.proto içinde yer alır .

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

Dizin düzeni

Bazel, derleme sırasında biraz karmaşık bir dizin grubu oluşturur. Tam açıklamayı Çıkış dizini düzeni bölümünde bulabilirsiniz.

"Ana depo", Bazel'in çalıştırıldığı kaynak ağaçtır. Genellikle kaynak kontrolünden kullanıma aldığınız bir öğeye karşılık gelir. Bu dizinin köküne "çalışma alanı kökü" adı verilir.

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

"install base", Bazel'in çıkarıldığı yerdir. Bu işlem otomatik olarak yapılır ve her Bazel sürümü, yükleme tabanı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 eklenen Bazel örneğinin yazdığı yerdir. Her çıkış tabanında herhangi bir zamanda en fazla bir Bazel sunucu örneği çalışır. Genellikle $OUTPUT_USER_ROOT/<checksum of the path to the workspace>'da olur. Bu seçenek, --output_base başlangıç seçeneği kullanılarak değiştirilebilir. Bu seçenek, diğer şeylerin yanı sıra herhangi bir çalışma alanında herhangi bir zamanda yalnızca bir Bazel örneğinin çalıştırılabileceği sınırlamasından kurtulmak için kullanışlıdır.

Çıkış dizini aşağıdakileri içerir:

  • $OUTPUT_BASE/external adresindeki getirilen harici depolar.
  • Yürütülebilir kök, geçerli derlemenin tüm kaynak kodlarına yönelik sembolik bağlantıları içeren bir dizindir. $OUTPUT_BASE/execroot adresinde bulunuyor. Derleme sırasında çalışma dizini $EXECROOT/<name of main repository> olur. Bu değişikliğin çok uyumsuz bir değişiklik olması nedeniyle uzun vadeli bir plan olsa da bunu $EXECROOT olarak değiştirmeyi planlıyoruz.
  • Derleme sırasında oluşturulan dosyalar.

Komut yürütme süreci

Bazel sunucusu kontrolü ele geçirip 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ışması için bir çalışma alanı gerekip gerekmediğine (kaynak koduyla ilgisi olmayanlar hariç neredeyse tüm komutlar, örneğin sürüm veya yardım) ve başka bir komutun çalışıp çalışmadığına karar verir.

  2. Doğru komut bulunur. Her komut, BlazeCommand arayüzünü uygulamalı ve @Command ek açıklamasına sahip olmalıdır (Bu biraz antipattern'dir. Bir komutun ihtiyaç duyduğu tüm meta verilerin BlazeCommand üzerindeki yöntemlerle açıklanması iyi olurdu).

  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çıklamasında açıklanmıştı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 bildirmek için Build Event Protocol (Derleme Etkinliği Protokolü) kapsamında Bazel'in dışına aktarılır.

  5. Komut kontrolü ele geçirir. En ilginç komutlar, derleme çalıştıran komutlardır: build, test, run, coverage 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 ile //pkg/... gibi joker karakterler çözümlenir. Bu, AnalysisPhaseRunner.evaluateTargetPatterns() içinde uygulanır ve Skyframe'de 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üsüz grafiği) oluşturmak üzere ç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 çalıştırılması anlamına gelir.

Komut satırı seçenekleri

Bir Bazel çağırma işlemi için komut satırı seçenekleri, bir OptionsParsingResult nesnesinde açıklanır. Bu nesne de "option classes"dan seçeneklerin değerlerine kadar olan bir haritayı 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. Programlama diliyle (CppOptions veya JavaOptions) ilgili seçenekler. Bunlar FragmentOptions'nin alt sınıfı olmalı ve sonunda BuildOptions nesnesine sarmalanmalıdır.
  2. Bazel'in işlemleri yürütme şekliyle ilgili seçenekler (ExecutionOptions)

Bu seçenekler, analiz aşamasında kullanılmak üzere tasarlanmıştır ve (Java'da RuleContext.getFragment() veya Starlark'ta ctx.fragments aracılığıyla) kullanılabilir. Bazıları (ör. C++ dahil etme taraması yapılıp yapılmayacağı) yürütme aşamasında okunur ancak BuildConfiguration o sırada kullanılamadığından bu her zaman açık bir kanal gerektirir. Daha fazla bilgi için "Yapılandırmalar" bölümüne bakın.

UYARI: OptionsBase örneklerinin değişmez olduğunu varsaymayı ve bunları bu şekilde kullanmayı (ör. SkyKeys'ün bir parçası olarak) tercih ederiz. Ancak bu doğru değildir ve bunları değiştirmek, Bazel'i hata ayıklaması zor olan ince yöntemlerle bozmanın iyi bir yoludur. Ancak bu öğeleri gerçekten değiştirilemez hale getirmek büyük bir çaba gerektirir. (FragmentOptions oluşturulduktan hemen sonra, başka biri referansını tutma şansı bulmadan ve equals() veya hashCode() çağrılmadan önce değiştirilmesi sorun teşkil etmez.)

Bazel, seçenek sınıfları hakkında aşağıdaki yöntemlerle bilgi edinir:

  1. Bazıları Bazel'e doğrudan bağlıdır (CommonCommandOptions).
  2. Her Bazel komutundaki @Command ek açıklamasından
  3. ConfiguredRuleClassProvider (Bunlar, tek tek programlama dilleriyle ilgili komut satırı seçenekleridir.)
  4. Starlark kuralları kendi seçeneklerini de tanımlayabilir (bkz. burası).

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ına sahip 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 basittir (dize, tam sayı, Boole, etiket vb.). Ancak daha karmaşık türlerdeki seçenekleri de destekliyoruz. Bu durumda, komut satırı dizesinden veri türüne dönüştürme işi com.google.devtools.common.options.Converter'nın uygulanmasına düşer.

Bazel'in gördüğü kaynak ağacı

Bazel, kaynak kodunu okuyup yorumlayarak yazılım oluşturma işiyle uğraşır. Bazel'in üzerinde çalıştığı kaynak kodun tamamına "çalışma alanı" adı verilir ve bu alan; depolar, paketler ve kurallar şeklinde 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ı olan Blaze, monorepo üzerinde çalışıyordu. Monorepo, derlemeyi çalıştırmak için kullanılan tüm kaynak kodunu içeren tek bir kaynak ağacıdır. Bunun aksine Bazel, kaynak kodu birden fazla depoda bulunan projeleri destekler. Bazel'in çağrıldığı depoya "ana depo", diğerlerine ise "harici depolar" adı verilir.

Depolar, kök dizinlerinde bir depo sınırı dosyası (MODULE.bazel, REPO.bazel veya eski bağlamlarda WORKSPACE ya da WORKSPACE.bazel) ile işaretlenir. Ana depo, Bazel'i çağırdığınız kaynak ağacıdır. Harici depolar çeşitli şekillerde tanımlanır. Daha fazla bilgi için Harici bağımlılıklara genel bakış başlıklı makaleyi inceleyin.

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

Derleme çalıştırılırken tüm kaynak ağacının bir araya getirilmesi gerekir. Bu işlem, ana depodaki her paketi $EXECROOT'ye ve her harici depoyu $EXECROOT/external veya $EXECROOT/..'e sembolik olarak bağlayan SymlinkForest tarafından yapılır.

Paketler

Her depo, paketlerden (ilgili dosyaların bir koleksiyonu) ve bağımlılıkların bir spesifikasyonundan oluşur. Bunlar, BUILD veya BUILD.bazel adlı bir dosya tarafından belirtilir. İkisi de varsa Bazel BUILD.bazel dosyasını tercih eder. BUILD dosyalarının hâlâ kabul edilmesinin nedeni, Bazel'in atası olan Blaze'in bu dosya adını kullanmasıdır. Ancak bu yol segmentinin özellikle dosya adlarının büyük/küçük harfe duyarlı olmadığı Windows'da yaygın olarak kullanıldığı 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 olamaz. Yinelemeli 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. Sınıfta PackageFactory uygulanır, Starlark yorumlayıcısı çağrılarak çalışır ve kullanılabilir kural sınıfları kümesi hakkında bilgi sahibi olmayı gerektirir. Paket yüklemenin sonucu Package nesnesidir. Çoğunlukla bir dizeden (hedefin adı) hedefe giden bir haritadır.

Paket yükleme sırasında karmaşıklığın büyük bir kısmı globbing'dir: Bazel, her kaynak dosyanın açıkça listelenmesini gerektirmez ve bunun yerine glob'ları (ör. glob(["**/*.java"])) çalıştırabilir. Kabuktan farklı olarak, alt dizinlere (ancak alt paketlere değil) inen yinelemeli glob'ları destekler. Bu işlem için dosya sistemine erişim gerekir. Bu erişim yavaş olabileceğinden, işlemi paralel ve olabildiğince verimli şekilde çalıştırmak için her türlü yöntemi kullanırız.

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

  • LegacyGlobber, hızlı ve Skyframe'den habersiz bir globber
  • Skyframe'i kullanan ve "Skyframe yeniden başlatmalarını" (aşağıda açıklanmıştır) önlemek için eski globber'a geri dönen bir sürüm olan SkyframeHybridGlobber

Package sınıfının kendisi, yalnızca "harici" paketi (harici bağımlılıklarla ilgili) ayrıştırmak için kullanılan ve gerçek paketler için anlamlı olmayan bazı üyeler içerir. Bu bir tasarım hatasıdır. Çünkü normal paketleri açıklayan nesneler, başka bir şeyi açıklayan alanlar içermemelidir. Bunlardan bazıları:

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

İdeal olarak, Package'nın her ikisinin de ihtiyaçlarını karşılaması gerekmemesi için "harici" paketin ayrıştırılması ile normal paketlerin ayrıştırılması arasında daha fazla ayrım olmalıdır. İkisi birbirine çok bağlı olduğundan bu işlemi yapmak maalesef zordur.

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 yapılar (artifacts) denir (başka bir yerde ele alınmıştır). Derleme sırasında oluşturulan tüm dosyalar hedef değildir. Bazel'in çıktısının ilişkili bir etiketi olmaması yaygın bir durumdur.
  2. Kurallar: Çıkışları girişlerden elde etme adımlarını açıklar. Genellikle bir programlama diliyle (ör. cc_library, java_library veya py_library) ilişkilendirilirler 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ınmıştır.

Bir 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 dizinine göre dosyanın (etiket bir kaynak dosyayı ifade ediyorsa) yoludur. Komut satırında bir hedeften bahsederken etiketin bazı kısımları atlanabilir:

  1. Depo atlanırsa etiketin ana depoda olduğu varsayılır.
  2. Paket kısmı atlanırsa (ör. name veya :name), etiketin geçerli çalışma dizininin paketinde olduğu varsayılır (üst düzey referanslar (..) içeren göreli yollara izin verilmez).

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

Starlark kural sınıfları, BUILD dosyalarının başında load() ifadesi kullanılarak içe aktarılmalıdır. Java kural sınıfları ise ConfiguredRuleClassProvider ile kaydedildikleri için Bazel tarafından "doğal olarak" bilinir.

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

  1. Özellikleri (ör. srcs, deps): türleri, varsayılan değerleri, kısıtlamaları vb.
  2. Her bir özelliğe eklenen yapılandırma geçişleri ve yönleri (varsa)
  3. Kuralın uygulanması
  4. Kuralın "genellikle" oluşturduğu geçişli bilgiler

Terminoloji notu: Kod tabanında, kural sınıfı tarafından oluşturulan hedefi ifade etmek için genellikle "Kural" terimini kullanırız. Ancak Starlark'ta ve kullanıcıya yönelik dokümanlarda "kural" yalnızca kural sınıfının kendisini ifade etmek için kullanılmalıdır. Hedef yalnızca bir "hedef"tir. 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ı da unutmayın.

Skyframe

Bazel'in temelini oluşturan değerlendirme çerçevesine Skyframe adı verilir. Bu sistemin modeli, derleme sırasında oluşturulması gereken her şeyin, herhangi bir veri parçasından bağımlılıklarına (yani oluşturulması için bilinmesi gereken diğer veri parçaları) işaret eden kenarlara sahip yönlendirilmiş döngüsüz bir grafikte düzenlenmesidir.

Grafikteki düğümlere SkyValue, adlarına ise SkyKey adı verilir. Her ikisi de değişmezdir. Bunlardan yalnızca değişmez nesnelere ulaşılabilir. Bu değişmez kural neredeyse her zaman geçerlidir. Geçerli olmadığı durumlarda (ör. BuildOptions, BuildConfigurationValue ve SkyKey'nin üyesi olan bireysel seçenek sınıfları için) bunları değiştirmemeye veya yalnızca dışarıdan gözlemlenemeyecek şekilde değiştirmeye çalışırız. Bu nedenle, Skyframe'de hesaplanan her şeyin (ör. yapılandırılmış hedefler) de değişmez olması gerekir.

Skyframe grafiğini gözlemlemenin en kolay yolu, grafiği satır başına bir SkyValue olacak şekilde boşaltan bazel dump --skyframe=deps komutunu çalıştırmaktır. Oldukça büyük olabileceğinden, en iyisi bunu küçük derlemeler için yapmaktır.

Skyframe, com.google.devtools.build.skyframe paketinde bulunur. Benzer adlı paket com.google.devtools.build.lib.skyframe, Skyframe üzerinde Bazel'in uygulanmasını içerir. Skyframe hakkında daha fazla bilgiye buradan ulaşabilirsiniz.

Skyframe, belirli bir SkyKey değerini SkyValue değerine dönüştürmek için anahtarın türüne karşılık gelen SkyFunction işlevini çağırır. İşlevin değerlendirilmesi sırasında, SkyFunction.Environment.getValue() işlevinin çeşitli aşırı yüklemeleri çağrılarak Skyframe'den başka bağımlılıklar istenebilir. Bu, söz konusu bağımlılıkları Skyframe'in dahili grafiğine kaydetme gibi bir yan etkiye sahiptir. Böylece Skyframe, bağımlılıklarından herhangi biri değiştiğinde işlevi yeniden değerlendirmesi gerektiğini bilir. Diğer bir deyişle, Skyframe'in önbelleğe alma ve artımlı hesaplama işlemleri SkyFunction ve SkyValue ayrıntı düzeyinde çalışır.

Bir SkyFunction, kullanılamayan bir bağımlılık istediğinde getValue() null değerini döndürür. İşlev daha sonra null döndürerek kontrolü Skyframe'e geri vermelidir. Skyframe, daha sonraki bir noktada kullanılamayan bağımlılığı değerlendirir ve işlevi baştan yeniden başlatır. Ancak bu kez getValue() çağrısı boş 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üre, önbelleğe alınan SkyValues bağımlılığını değerlendirmek için yapılan çalışmaları kapsamaz. Bu nedenle, bu sorunu genellikle şu şekilde çözüyoruz:

  1. Yeniden başlatma sayısını sınırlamak için bağımlılıkları gruplar halinde (getValuesAndExceptions() kullanarak) bildirme.
  2. Bir SkyValue öğesini farklı SkyFunction'ler tarafından hesaplanan ayrı parçalara bölerek bu parçaların bağımsız olarak hesaplanmasını ve önbelleğe alınmasını sağlar. Bu işlem, bellek kullanımını artırabileceğinden stratejik olarak yapılmalıdır.
  3. Yeniden başlatmalar arasında durumu depolama (SkyFunction.Environment.getState() kullanılarak veya "Skyframe'in arkasında" geçici bir statik önbellek tutularak). Karmaşık SkyFunctions ile yeniden başlatmalar arasında durum yönetimi zorlaşabilir. Bu nedenle, StateMachines, SkyFunction içinde hiyerarşik hesaplamaları askıya alma ve devam ettirme kancaları da dahil olmak üzere mantıksal eşzamanlılığa yapılandırılmış bir yaklaşım için kullanıma sunuldu. Örnek: DependencyResolver#computeDependencies StateMachine ile getState() kullanarak yapılandırılmış bir hedefin potansiyel olarak çok büyük olan doğrudan bağımlılıklar kümesini hesaplar. Aksi takdirde bu, pahalı yeniden başlatmalara neden olabilir.

Temel olarak, Bazel'in bu tür geçici çözümlere ihtiyacı vardır. Çünkü yüz binlerce devam eden Skyframe düğümü yaygındır ve Java'nın hafif iş parçacıkları desteği, 2023 itibarıyla StateMachine uygulamasından daha iyi performans göstermez.

Starlark

Starlark, kullanıcıların Bazel'i yapılandırmak ve genişletmek için kullandığı alana özgü bir dildir. Bu dil, Python'ın kısıtlanmış bir alt kümesi olarak tasarlanmıştır. Çok daha az türü, kontrol akışıyla ilgili daha fazla kısıtlaması ve en önemlisi, eşzamanlı okumaları etkinleştirmek için güçlü değişmezlik garantileri vardır. Turing-complete değildir. Bu nedenle bazı (ancak tümü değil) kullanıcılar, dil içinde genel programlama görevlerini tamamlamaya çalışmaktan vazgeçer.

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

Starlark, aşağıdakiler dahil olmak üzere çeşitli bağlamlarda kullanılır:

  1. BUILD dosyası. Yeni derleme hedefleri 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. MODULE.bazel dosyası. Dış bağımlılıklar burada tanımlanır. Bu bağlamda çalışan Starlark kodu, yalnızca birkaç önceden tanımlanmış yönergeye çok sınırlı erişime sahiptir.
  3. .bzl dosyası. Yeni derleme kuralları, depo kuralları ve modül uzantıları burada tanımlanır. Buradaki Starlark kodu, yeni işlevler tanımlayabilir ve diğer .bzl dosyalarından yükleme yapabilir.

BUILD ve .bzl dosyaları için kullanılabilen lehçeler, farklı şeyler ifade ettikleri için biraz farklıdır. Farkları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 gerektiğini belirler. Temel birimi, oldukça mantıklı bir şekilde (hedef, yapılandırma) çifti olan "yapılandırılmış hedef"tir.

Bu aşama, iki ayrı bölüme ayrılabildiği için "yükleme/analiz aşaması" olarak adlandırılır. Bu bölümler önceden sıralı olarak çalışıyordu ancak artık aynı anda çalışabilir:

  1. Paketleri yükleme (diğer bir deyişle, BUILD dosyalarını bunları temsil eden Package nesnelerine dönüştürme)
  2. Yapılandırılmış hedefleri analiz etme (yani işlem grafiğini oluşturmak için kuralların uygulanmasını çalıştırma)

Komut satırında istenen yapılandırılmış hedeflerin geçişli kapanımındaki 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 analizindeki girişler şunlardır:

  1. Yapılandırma. (Bu kuralın nasıl oluşturulacağı; örneğin, hedef platformun yanı sıra kullanıcının C++ derleyicisine iletilmesini istediği komut satırı seçenekleri gibi şeyler)
  2. Doğrudan bağımlılıklar. Geçişli bilgi sağlayıcıları, analiz edilen kural için kullanılabilir. Bunlara bu adın verilmesinin nedeni, yapılandırılmış hedefin geçişli kapanımındaki bilgilerin (ör. sınıf yolundaki tüm .jar dosyaları veya bir C++ ikilisine bağlanması gereken tüm .o dosyaları) "toplu" bir görünümünü sağlamalarıdır.
  3. Hedefin kendisi. Bu, paketin yüklendiği hedefteki sonuçtur. Kurallar için bu, genellikle önemli olan özelliklerini içerir.
  4. Yapılandırılan hedefin uygulanması. Kurallar için bu, Starlark veya Java'da olabilir. Kural dışı yapılandırılmış tüm hedefler Java'da uygulanır.

Yapılandırılmış bir hedefi analiz etmenin sonucu:

  1. Kendisine bağlı hedefleri yapılandıran geçişli bilgi sağlayıcılar erişebilir.
  2. Oluşturabileceği yapılar ve bunları üreten işlemler.

Java kurallarına sunulan API, Starlark kurallarının ctx bağımsız değişkenine eşdeğer olan RuleContext'dır. API'si daha güçlü olsa da aynı zamanda Bad Things™ yapmak (ör. zaman veya alan karmaşıklığı kuadratik olan (ya da daha kötü) kod yazmak, Bazel sunucusunun Java istisnasıyla kilitlenmesine neden olmak veya değişmezleri ihlal etmek (ör. Options örneğini yanlışlıkla değiştirerek ya da yapılandırılmış bir hedefi değiştirilebilir hale getirerek)) daha kolaydır.

Yapılandırılmış bir hedefin doğrudan bağımlılıklarını belirleyen algoritma DependencyResolver.dependentNodeMap() içinde yer alır.

Yapılandırmalar

Yapılandırmalar, hedef oluşturmanın "nasıl" kısmıdır: hangi platform için, hangi komut satırı seçenekleriyle vb.

Aynı hedef, aynı derlemede birden fazla yapılandırma için oluşturulabilir. Örneğin, aynı kod derleme sırasında çalıştırılan bir araç ve hedef kod için kullanıldığında ve çapraz derleme yaptığımızda veya çoklu CPU mimarileri için yerel kod içeren bir fat Android uygulaması oluşturduğumuzda bu kullanışlıdır.

Kavramsal olarak yapılandırma bir BuildOptions örneğidir. Ancak uygulamada BuildOptions, BuildConfiguration ile sarmalanır ve bu da çeşitli ek işlevler sağlar. 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, örneğin istenen test çalıştırmalarının sayısı değiştiğinde tüm derlemenin yeniden analiz edilmesi gibi anormalliklere yol açar. Bu durum yalnızca test hedeflerini etkilese de (bu durumun yaşanmaması için yapılandırmaları "kırpmayı" planlıyoruz ancak bu özellik henüz hazır değil)

Bir kural uygulaması, yapılandırmanın bir bölümüne ihtiyaç duyduğunda RuleClass.Builder.requiresConfigurationFragments() kullanarak tanımında bunu bildirmesi gerekir. Bu, hem hataları önlemek (ör. Java parçasını kullanan Python kuralları) hem de yapılandırmayı kırpma işlemini kolaylaştırmak için yapılır. Böylece, Python seçenekleri değişirse C++ hedeflerinin yeniden analiz edilmesi gerekmez.

Bir kuralın yapılandırması, "üst" kuralının yapılandırmasıyla aynı olmayabilir. Bağımlılık kenarında 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 Rule (geçişin gerçekleştiği yer) ile BuildOptions (orijinal yapılandırma) öğesinden bir veya daha fazla BuildOptions (çıkış yapılandırması) öğesine geçiş işlevleridir.
  2. Yapılandırılmış bir hedefe gelen tüm girişlerde. Bunlar RuleClass.Builder.cfg() içinde belirtilir.

İlgili sınıflar TransitionFactory ve ConfigurationTransition'dir.

Yapılandırma geçişleri örneğin şu durumlarda kullanılır:

  1. Belirli bir bağımlılığın derleme sırasında kullanıldığını ve bu nedenle yürütme mimarisinde oluşturulması gerektiğini bildirmek için
  2. Belirli bir bağımlılığın birden fazla mimari için (ör. fat Android APK'larındaki yerel kod için) oluşturulması gerektiğini belirtmek üzere

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

Yapılandırma geçişleri Starlark'ta da uygulanabilir (belgeler �burada).

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

Geçişli bilgi sağlayıcılar, yapılandırılmış hedeflerin kendilerine bağlı olan diğer yapılandırılmış hedefler hakkında bilgi edinmesinin (ve _tek_ yoludur) ve kendileri hakkında bilgi vermesinin tek yoludur. Bu metriklerin adında "geçişli" ifadesinin yer almasının nedeni, genellikle yapılandırılmış bir hedefin geçişli kapanımının bir tür birleştirilmiş halini temsil etmesidir.

Genellikle Java geçişli bilgi sağlayıcıları ile Starlark sağlayıcıları arasında 1:1 karşılık vardır (DefaultInfo, FileProvider, FilesToRunProvider ve RunfilesProvider'ün birleşimi olduğu için istisnadır. Bunun nedeni, bu API'nin Java API'nin doğrudan çevirisinden daha çok Starlark'a uygun olarak kabul edilmesidir). Anahtarları aşağıdakilerden biri olabilir:

  1. Java Class nesnesi. Bu özellik yalnızca Starlark'tan erişilemeyen sağlayıcılar için kullanılabilir. Bu sağlayıcılar, TransitiveInfoProvider alt sınıfıdır.
  2. Bir dize. Bu yöntem, ad çakışmalarına karşı hassas olduğundan eski bir yöntemdir ve kullanılması kesinlikle önerilmez. Bu tür geçişli bilgi sağlayıcılar, build.lib.packages.Info öğesinin doğrudan alt sınıflarıdır .
  3. Sağlayıcı simgesi. Bu, provider() işlevi kullanılarak Starlark'tan oluşturulabilir ve yeni sağlayıcılar oluşturmak için önerilen yöntemdir. Bu simge, Java'da Provider.Key örneğiyle gösterilir.

Java'da uygulanan yeni sağlayıcılar BuiltinProvider kullanılarak uygulanmalıdır. NativeProvider kullanımdan kaldırıldı (henüz kaldırmaya zaman bulamadık) ve TransitiveInfoProvider alt sınıflarına Starlark'tan erişilemiyor.

Yapılandırılmış hedefler

Yapılandırılan hedefler RuleConfiguredTargetFactory olarak uygulanır. Java'da uygulanan her kural sınıfı için bir alt sınıf vardır. Starlark yapılandırılmış hedefler, 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. Aşağıdakilerden oluşur:

  1. filesToBuild, "bu kuralın temsil ettiği dosyalar kümesi" kavramı. Bunlar, yapılandırılan hedef komut satırında veya bir genrule'un srcs'inde olduğunda oluşturulan dosyalardır.
  2. Çalıştırma dosyaları, düzenli ve veri.
  3. Çıkış grupları. Bunlar, kuralın oluşturabileceği çeşitli "diğer dosya kümeleridir". Bunlara, BUILD'deki filegroup kuralının output_group özelliği ve Java'daki OutputGroupInfo sağlayıcısı kullanılarak erişilebilir.

Çalıştırma dosyaları

Bazı ikili dosyaların çalışması için veri dosyaları gerekir. Buna en iyi örnek, giriş dosyaları gerektiren testlerdir. Bu, Bazel'de "runfiles" kavramıyla temsil edilir. "Çalıştırma dosyaları ağacı", belirli bir ikili dosyanın veri dosyalarının bulunduğu bir dizin ağacıdır. Dosya sisteminde, kaynak veya çıkış ağaçlarındaki dosyalara işaret eden ayrı ayrı sembolik bağlantılar içeren bir sembolik bağlantı ağacı olarak oluşturulur.

Bir dizi runfile, Runfiles örneği olarak gösterilir. Bu, kavramsal olarak, runfiles ağacındaki bir dosyanın yolundan bunu temsil eden Artifact örneğine giden bir haritadır. Bu işlem, iki nedenden dolayı tek bir Map simgesine tıklamaktan biraz daha karmaşıktır:

  • Çoğu zaman, bir dosyanın runfiles yolu, execpath'iyle aynıdır. Bu özelliği, RAM'den tasarruf etmek için kullanırız.
  • Çalıştırma dosyası ağaçlarında, gösterilmesi gereken çeşitli eski giriş türleri de vardır.

Runfile'lar RunfilesProvider kullanılarak toplanır: Bu sınıfın bir örneği, yapılandırılmış bir hedefin (ör. kitaplık) ve geçişli kapanımının ihtiyaç duyduğu runfile'ları temsil eder ve bunlar iç içe yerleştirilmiş bir küme gibi toplanır (aslında, bunlar iç içe yerleştirilmiş kümeler kullanılarak uygulanır): Her hedef, bağımlılıklarının runfile'larını birleştirir, kendi runfile'larından bazılarını ekler ve ardından sonuçta elde edilen kümeyi bağımlılık grafiğinde yukarı doğru gönderir. Bir RunfilesProvider örneği, biri kurala "data" özelliği aracılığıyla bağımlı olunduğunda, diğeri ise diğer tüm gelen bağımlılık türleri için olmak üzere iki Runfiles örneği içerir. Bunun nedeni, bir hedefin bazen bir veri özelliği aracılığıyla bağlı olunduğunda farklı çalışma dosyaları sunmasıdır. Bu, henüz kaldırmadığımız istenmeyen eski bir davranıştır.

İkili dosyaların runfile'ları RunfilesSupport örneği olarak gösterilir. Bu, Runfiles'dan farklıdır. Çünkü RunfilesSupport, Runfiles'nın aksine (yalnızca bir harita) gerçekten inşa edilebilir. Bu nedenle aşağıdaki ek bileşenler gerekir:

  • Giriş runfiles manifest'i. Bu, runfiles ağacının seri hâlinde açıklanmış halidir. Bu, runfiles ağacının içeriği için bir proxy olarak kullanılır ve Bazel, yalnızca manifestin içeriği değişirse runfiles ağacının değiştiğini varsayar.
  • Çıkış runfiles manifest'i. Bu, özellikle Windows'ta runfiles ağaçlarını işleyen çalışma zamanı kitaplıkları tarafından kullanılır. Windows bazen sembolik bağlantıları desteklemez.
  • RunfilesSupport nesnesinin temsil ettiği çalıştırılabilir dosyaları çalıştırmak için komut satırı bağımsız değişkenleri.

Aspects

Aspect'ler, "hesaplamayı bağımlılık grafiğinde aşağıya doğru yaymanın" bir yoludur. Bazel kullanıcıları için bu özellikler burada açıklanmaktadır. İyi bir motivasyon örneği, protokol arabellekleridir: Bir proto_library kuralı, belirli bir dil hakkında bilgi sahibi olmamalıdır. Ancak herhangi bir programlama dilinde bir protokol arabellek mesajının ("temel birim") uygulanması, aynı dildeki iki hedef aynı protokol arabelleğe bağlıysa yalnızca bir kez oluşturulacak şekilde proto_library kuralıyla birlikte oluşturulmalıdır.

Yapılandırılmış hedefler gibi, Skyframe'de SkyValue olarak gösterilirler ve oluşturulma şekilleri, yapılandırılmış hedeflerin oluşturulma şekline çok benzer: RuleContext erişimi olan ConfiguredAspectFactory adlı bir fabrika sınıfına sahiptirler 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ğıya doğru yayılan yönler grubu, Attribute.Builder.aspects() işlevi kullanılarak her özellik için belirtilir. Bu sürece katılan, kafa karıştırıcı şekilde adlandırılmış birkaç sınıf vardır:

  1. AspectClass, bu yönün uygulanmasıdır. Java'da (bu durumda alt sınıftır) veya Starlark'ta (bu durumda StarlarkAspectClass örneğidir) olabilir. RuleConfiguredTargetFactory'ye benzer.
  2. AspectDefinition, yönün tanımıdır. Bu tanım, gerektirdiği sağlayıcıları, sağladığı sağlayıcıları içerir ve uygun AspectClass örneği gibi uygulamasının referansını içerir. RuleClass ile benzerdir.
  3. AspectParameters, bağımlılık grafiğinde aşağıya doğru yayılan bir yönü parametrelendirmenin bir yoludur. Şu anda dize-dize eşlemesi yapılıyor. Bunun 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ğiyle ilgili bilgiler, bağımlılık grafiğinde aşağı doğru yayı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. Yön 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. Rule -> Aspect işlevidir.

Biraz beklenmedik bir durum da yönlerin diğer yönlere bağlanabilmesidir. Örneğin, bir Java IDE'si için sınıf yolunu toplayan bir yön, sınıf yolundaki tüm .jar dosyaları hakkında bilgi edinmek isteyebilir ancak bu dosyaların bazıları protokol arabellekleridir. Bu durumda, IDE yönü ( (proto_library kuralı + Java proto yönü) çiftine bağlanmak isteyecektir.

Yönlerin yönler üzerindeki karmaşıklığı sınıfta AspectCollection yakalanır.

Platformlar ve araç zincirleri

Bazel, çok platformlu derlemeleri destekler. Yani derleme işlemlerinin çalıştırıldığı birden fazla mimarinin ve kodun oluşturulduğu birden fazla mimarinin olabileceği derlemeler. Bu mimariler, Bazel dilinde platformlar olarak adlandırılır (tam dokümanlar burada).

Platform, kısıtlama ayarlarından (ör. "CPU mimarisi" kavramı) kısıtlama değerlerine (ör. x86_64 gibi belirli bir CPU) anahtar-değer eşlemesiyle tanımlanır. @platforms deposunda en sık kullanılan kısıtlama ayarları ve değerlerinin yer aldığı bir "sözlüğümüz" var.

Araç zinciri kavramı, derlemenin hangi platformlarda çalıştığına ve hangi platformların hedeflendiğine bağlı olarak farklı derleyicilerin kullanılması gerekebileceği gerçeğinden kaynaklanır. Örneğin, belirli bir C++ araç zinciri belirli bir işletim sisteminde çalışabilir ve bazı diğer işletim sistemlerini hedefleyebilir. Bazel, kullanılan C++ derleyicisini belirlenen yürütme ve hedef platforma göre belirlemelidir (araç zincirleriyle ilgili dokümanlara buradan ulaşabilirsiniz).

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

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

Bu şekilde yapılmasının nedeni, araç zinciri çözümlemesi yapabilmek için her araç zincirinin kısıtlamalarını bilmemiz gerektiğidir. Dile özgü *_toolchain() kuralları ise bundan çok daha fazla bilgi içerdiğinden yüklenmesi daha uzun sürer.

Yürütme platformları aşağıdaki yöntemlerden biriyle belirtilir:

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

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

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

Yapılandırılmış bir hedef için kullanılacak araç zincirleri kümesi, ToolchainResolutionFunction tarafından belirlenir. Bu işlev şunlara bağlıdır:

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

Sonuç olarak, UnloadedToolchainContext elde edilir. Bu, temelde araç zinciri türünden (ToolchainTypeInfo örneği olarak gösterilir) seçilen araç zincirinin etiketine giden bir haritadır. Yalnızca etiketleri içerdiğinden ve araç zincirlerini içermediğinden "yüklenmemiş" olarak adlandırılır.

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

Ayrıca, tek bir "ana makine" yapılandırmasının olmasını ve hedef yapılandırmaların --cpu gibi çeşitli yapılandırma işaretleriyle temsil edilmesini gerektiren 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 dilinde ve Starlark olmayan bir "küçük dil" kullanıyor.

Sınırlamalar

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

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

Kurala özgü kısıtlamalar çoğunlukla Google'da Java kurallarında kullanılır. Bu kısıtlamalar kullanımdan kaldırılmaktadır ve Bazel'de kullanılamaz ancak kaynak kodunda bunlara referanslar olabilir. Bunu yöneten özelliğe constraints= adı verilir .

environment_group() ve environment()

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.

Desteklenen ortamlar bir kural için çeşitli şekillerde belirtilebilir:

  1. restricted_to= özelliği aracılığıyla. Bu, en doğrudan şartname biçimidir. Kuralın desteklediği ortamların tam kümesini bildirir.
  2. compatible_with= özelliği aracılığıyla. Bu, varsayılan olarak desteklenen "standart" ortamların yanı sıra bir kuralın desteklediği ortamları da bildirir.
  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 benzerlerin (ör. "CPU mimarileri", "JDK sürümleri" veya "mobil işletim sistemleri") bulunduğu bir gruba aittir. Bir ortam grubunun tanımı, restricted_to= / environment() özellikleri tarafından aksi belirtilmediği takdirde bu ortamlardan hangilerinin "varsayılan" olarak 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ı aracılığıyla. Bu, söz konusu kural sınıfının tüm örnekleri için genel varsayılanları geçersiz kılar. Bu özellik, örneğin her örneğin bu özelliği açıkça bildirmesi gerekmeden tüm *_test kuralların test edilebilir hale getirilmesi için kullanılabilir.

environment() normal bir kural olarak uygulanırken environment_group(), hem Target'nin bir alt sınıfı hem de Rule (EnvironmentGroup) değildir ve Starlark'ta (StarlarkLibrary.environmentGroup()) varsayılan olarak kullanılabilen, sonunda aynı adlı bir hedef oluşturan bir işlevdir. Bunun nedeni, her ortamın ait olduğu ortam grubunu, her ortam grubunun da varsayılan ortamlarını bildirmesi gerektiğinden 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 içinde uygulanır.

Platform kısıtlamaları

Bir hedefin hangi platformlarla uyumlu olduğunu açıklamanın mevcut "resmi" yolu, araç zincirlerini ve platformları açıklamak için kullanılan kısıtlamaların aynısını kullanmaktır. Pull isteği #10945 ile uygulandı.

Görünürlük

Çok sayıda geliştiricinin çalıştığı büyük bir kod tabanı üzerinde çalışıyorsanız (ör. Google'da), diğer herkesin kodunuzdan rastgele şekilde yararlanmasını önlemek için dikkatli olmanız gerekir. Aksi takdirde, Hyrum'un yasasına göre, kullanıcılar uygulama ayrıntıları olarak düşündüğünüz davranışlara güvenmeye başlar.

Bazel, visibility adı verilen mekanizma aracılığıyla bunu destekler: visibility özelliğini kullanarak belirli bir hedefe hangi hedeflerin bağlı olabileceğini sınırlayabilirsiniz. Bu özellik biraz özeldir. Etiket listesi içerse de bu etiketler, belirli bir hedefe yönelik işaretçi yerine paket adları üzerinde bir kalıbı kodlayabilir. (Evet, bu bir tasarım hatasıdır.)

Bu özellik 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__) ifade edebilir. Bu, //pkg:* veya //pkg/... kullanılan komut satırı söz diziminden farklıdır.
  • Paket grupları kendi hedefi (PackageGroup) ve yapılandırılmış hedefi (PackageGroupConfiguredTarget) olarak uygulanır. İstersek bunları basit kurallarla değiştirebiliriz. Mantıkları şu öğeler yardımıyla uygulanır: //pkg/... gibi tek bir kalıba karşılık gelen PackageSpecification; tek bir package_group öğesinin packages özelliğine karşılık gelen PackageGroupContents ve bir package_group öğesi ile geçişli includes öğesi üzerinde toplama işlemi yapan PackageSpecificationProvider.
  • Görünürlük etiketi listelerinden bağımlılıklara dönüştürme işlemi DependencyResolver.visitTargetVisibility ve birkaç başka yerde yapılır.
  • Gerçek kontrol CommonPrerequisiteValidator.validateDirectPrerequisiteVisibility() içinde yapılır.

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

Genellikle, yapılandırılmış bir hedef, bağımlılıklarından bir dizi dosyayı toplar, kendi dosyalarını ekler ve toplu grubu geçişli bir bilgi sağlayıcıya sarar. Böylece, kendisine bağlı olan yapılandırılmış hedefler de aynı işlemi yapabilir. Örnekler:

  • Derleme için kullanılan C++ başlık dosyaları
  • Bir cc_library öğesinin geçişli kapanımı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ı kümesi

Örneğin, List veya Set kullanarak bunu basit bir şekilde yapsaydık karesel bellek kullanımıyla karşılaşırdık: N kuraldan oluşan bir zincir varsa ve her kural bir dosya ekliyorsa 1+2+...+N toplama üyesi olurdu.

Bu sorunu çözmek için NestedSet kavramını geliştirdik. Bu, diğer NestedSet örneklerinden ve kendi üyelerinden oluşan bir veri yapısıdır. Bu sayede, kümelerin yönlendirilmiş döngüsüz grafiği oluşturulur. Bu koleksiyonlar değişmezdir ve üyeleri üzerinde yineleme yapılabilir. Birden fazla yineleme sırası (NestedSet.Order) tanımlıyoruz: ön sıralama, son sıralama, topolojik (bir düğüm her zaman atalarından sonra gelir) ve "önemli değil, ancak her seferinde aynı olmalı".

Aynı veri yapısına Starlark'ta depset adı verilir.

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 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 işlemler, "işlem grafiği" adı verilen iki parçalı, yönlendirilmiş, döngüsüz bir grafikte düzenlenir.

Yapılar iki türdedir: kaynak yapıları (Bazel yürütmeye başlamadan önce kullanılabilenler) ve türetilmiş yapılar (oluşturulması gerekenler). Türetilmiş yapay nesneler de birden fazla türde olabilir:

  1. Normal yapılar. Bunların güncel olup olmadığı, sağlama toplamları hesaplanarak kontrol edilir. mtime, kısayol olarak kullanılır. ctime'ı değişmemişse dosyanın sağlama toplamı hesaplanmaz.
  2. Çözümlenmemiş sembolik bağlantı yapıları. Bunlar, readlink() çağrılarak güncel olup olmadıkları kontrol edilir. Normal yapay nesnelerin aksine, bunlar askıda kalan sembolik bağlantılar olabilir. Genellikle bazı dosyaların bir tür arşive paketlendiği durumlarda kullanılır.
  3. Ağaç yapıları. Bunlar tek dosyalar değil, dizin ağaçlarıdır. İçindeki dosyalar ve içerikleri kontrol edilerek güncel olup olmadığı belirlenir. 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: Yalnızca mevcut saat değiştiği için yeniden derleme yapmak istemiyoruz.

Kaynak yapıtların ağaç yapıtları veya çözümlenmemiş sembolik bağlantı yapıtları olmamasının temel bir nedeni yoktur. Bu özellik henüz uygulanmamıştır (ancak uygulanmalıdır. BUILD dosyasında bir kaynak dizine referans vermek, Bazel'deki bilinen birkaç uzun süredir devam eden yanlışlık sorunundan biridir. BAZEL_TRACK_SOURCE_DIRECTORIES=1 JVM özelliğiyle etkinleştirilen, bir şekilde çalışan bir uygulamamız vardır).

İşlemler, çalıştırılması gereken bir komut, ihtiyaç duyduğu ortam ve ürettiği çıkışlar kümesi olarak anlaşılabilir. Aşağıdakiler, bir işlemin açıklamasının temel bileşenleridir:

  • Çalıştırılması gereken komut satırı
  • Gerekli giriş yapıları
  • Ayarlanması gereken ortam değişkenleri
  • Çalışması gereken ortamı (ör. platform) açıklayan notlar \

Ayrıca, içeriği Bazel tarafından bilinen bir dosyayı yazma gibi birkaç özel durum daha vardır. Bunlar, AbstractAction alt sınıfıdır. Çoğu işlem SpawnAction veya StarlarkAction'dir (aynı oldukları için ayrı sınıflar olmamaları gerekir). Ancak Java ve C++'ın kendi işlem türleri (JavaCompileAction, CppCompileAction ve CppLinkAction) vardır.

Sonunda her şeyi SpawnAction'ya taşımak istiyoruz. JavaCompileAction bu konuda oldukça iyi bir seçenek olsa da C++, .d dosyası ayrıştırma ve include tarama 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 gösterilir. Bir işlem grafiği bağımlılığı kenarından bir Skyframe bağımlılığı kenarına yapılan 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 içerir:

  • Türetilmiş yapılar kendi SkyValue'lerine sahip değildir. Bunun yerine, Artifact.getGeneratingActionKey(), anahtarı oluşturulan işlem için kullanılır.
  • İç içe geçmiş kümelerin kendi Skyframe anahtarları vardır.

Paylaşılan işlemler

Bazı işlemler birden fazla yapılandırılmış hedef tarafından oluşturulur. Starlark kuralları, türetilmiş işlemlerini yalnızca yapılandırmaları ve paketleri tarafından belirlenen bir dizine yerleştirmelerine izin verildiğinden daha sınırlıdır (ancak aynı paketteki kurallar bile çakışabilir). Java'da uygulanan kurallar ise 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 başvurulduğunda (el sallama) yürütme süresinde önemli ölçüde tasarruf sağladığı için bu hatayı düzeltmek gerçekten zordur. Bu durum, biraz RAM kaybına neden olur: Paylaşılan bir işlemin her örneğinin ayrı ayrı bellekte depolanması gerekir.

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

Yürütme aşaması

Bu aşamada Bazel, çıktı üreten komutlar gibi derleme işlemlerini gerçekten çalıştırmaya başlar.

Bazel'in analiz aşamasından sonra yaptığı ilk şey, hangi yapıtların oluşturulması gerektiğini belirlemektir. Bunun mantığı TopLevelArtifactHelper içinde kodlanmıştır. Kabaca söylemek gerekirse, bu, komut satırında yapılandırılmış hedeflerin filesToBuild ve "bu hedef komut satırındaysa bu yapıtları oluştur" ifadesini açıkça belirtmek amacıyla kullanılan özel bir çıkış grubunun içeriğidir.

Bir sonraki adım, yürütme kökünü oluşturmaktır. Bazel, kaynak paketlerini dosya sistemindeki farklı konumlardan okuma seçeneğine sahip olduğundan (--package_path), yerel olarak yürütülen işlemlere tam bir kaynak ağacı sağlaması gerekir. Bu, SymlinkForest sınıfı tarafından işlenir ve analiz aşamasında kullanılan her hedefi not ederek ve her paketi kullanılan bir hedefle gerçek konumundan sembolik olarak bağlayan tek bir dizin ağacı oluşturarak çalışır. Alternatif olarak, komutlara doğru yolları (--package_path dikkate alınarak) iletebilirsiniz. Bu durumun istenmemesinin nedeni:

  • Bir paket, paket yolu girişinden diğerine taşındığında işlem komut satırlarını değiştirir (eskiden sıkça görülürdü).
  • Bir işlem uzaktan çalıştırıldığında yerel olarak çalıştırıldığından farklı komut satırları oluşturulur.
  • Kullanılan araca özel bir komut satırı dönüşümü gerektirir (ör. Java sınıf yolları ve C++ dahil etme yolları arasındaki farkı göz önünde bulundurun).
  • Bir işlemin komut satırının değiştirilmesi, işlem önbelleği girişini geçersiz kılar.
  • --package_path yavaş ve istikrarlı bir şekilde desteği sonlandırılıyor

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

Bir işlemi çalıştırmak maliyetli olduğundan Skyframe'in arkasında birkaç katmanlı önbelleğe alma sistemi bulunur:

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

Yerel işlem önbelleği

Bu önbellek, Skyframe'in arkasında yer alan başka bir katmandır. Bir işlem Skyframe'de yeniden yürütülse bile yerel işlem önbelleğinde hâlâ bir isabet olabilir. Yerel dosya sisteminin durumunu temsil eder ve diske seri hale getirilir. Bu nedenle, yeni bir Bazel sunucusu başlatıldığında Skyframe grafiği boş olsa bile yerel işlem önbelleği isabetleri elde edilebilir.

Bu önbellek, ActionCacheChecker.getTokenIfNeedToExecute() yöntemi kullanılarak isabetler açısından kontrol edilir .

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

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

Ayrıca, önbelleğe çok fazla gitmeyi önlemek için geçişli karma değerleri kullanan ve hâlâ geliştirme aşamasında olan 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 dizi girişe sahip olmaktan daha karmaşıktır. Bir işlemin girişler kümesinde yapılan değişiklikler iki şekilde olabilir:

  • Bir işlem, yürütülmeden önce yeni girişler keşfedebilir veya girişlerinden bazılarının aslında gerekli olmadığına karar verebilir. Bunun kanonik örneği C++'tır. Burada, her dosyayı uzak yürütücülere göndermememiz için bir C++ dosyasının geçişli kapanışından hangi üstbilgi dosyalarını kullandığı konusunda bilinçli bir tahminde bulunmak daha iyidir. Bu nedenle, her üstbilgi dosyasını "giriş " olarak kaydetmemek, ancak geçişli olarak dahil edilen üstbilgiler için kaynak dosyasını taramak ve yalnızca #include ifadelerinde belirtilen üstbilgi dosyalarını giriş olarak işaretlemek için bir seçeneğimiz vardır (tam bir C ön işlemcisi uygulamamız gerekmemesi için fazla tahmin yaparız). Bu seçenek şu anda Bazel'de "false" olarak sabitlenmiştir ve yalnızca Google'da kullanılmaktadır.
  • Bir işlem, yürütülmesi sırasında bazı dosyaların kullanılmadığını fark edebilir. C++'ta buna ".d dosyaları" denir: Derleyici, hangi üstbilgi dosyalarının kullanıldığını sonradan bildirir ve Make'ten daha kötü bir artımlılığa sahip olmanın utancını önlemek için Bazel bu durumdan yararlanır. Bu, derleyiciye dayandığı için tarayıcıyı dahil etmeye kıyasla daha iyi bir tahmin sunar.

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

  1. Action.discoverInputs() işlevi çağrılır. Gerekli olduğu belirlenen iç içe yerleştirilmiş bir Yapay Nesne kümesi döndürmelidir. Bu öğeler, yapılandırılmış hedef grafikte eşdeğeri olmayan işlem grafiğinde bağımlılık kenarları olmaması için kaynak yapıtlar olmalıdır.
  2. İşlem, Action.execute() çağrılarak yürütülür.
  3. Action.execute() sonunda işlem, Bazel'e tüm girişlerinin gerekmediğini bildirmek için Action.updateInputs() işlevini çağırabilir. Bu durum, kullanılan bir giriş kullanılmamış olarak bildirildiğinde 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) eşleşme döndürdüğünde, giriş kümesinin daha önce yapılan giriş keşfi ve budama işleminin sonucunu yansıtması için Bazel updateInputs() kendisini çağırır.

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

İşlemleri çalıştırmanın ç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ürlerdeki sanal ortamlarda veya uzaktan yürütülebilir. Bu kavram, ActionContext (veya yeniden adlandırma işlemini yalnızca yarıda bıraktığımız için Strategy) olarak adlandırılır.

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

  1. Yürütme aşaması başlatıldığında BlazeModule örneklerine hangi işlem bağlamlarına sahip oldukları sorulur. Bu, ExecutionTool oluşturucusunda gerçekleşir. İşlem bağlamı türleri, Class arayüzünün bir alt arayüzüne başvuran ve işlem bağlamının hangi arayüzü uygulaması gerektiğini belirten bir Java ActionContext örneğiyle tanımlanır.
  2. Uygun işlem bağlamı, mevcut olanlar arasından seçilir ve ActionExecutionContext ile BlazeExecutor'ye yönlendirilir .
  3. İşlemler, ActionExecutionContext.getContext() ve BlazeExecutor.getStrategy() kullanarak istek bağlamı oluşturur (Bunu yapmanın aslında tek bir yolu olmalıdır.)

Stratejiler, görevlerini yerine getirmek için diğer stratejileri ücretsiz olarak çağırabilir. Bu, örneğin hem yerel hem de uzaktan işlemleri başlatan, ardından önce tamamlanan işlemi kullanan dinamik stratejide kullanılır.

Önemli bir strateji, kalıcı çalışan süreçleri uygulayan stratejidir (WorkerSpawnStrategy). Bu stratejinin temelinde, bazı araçların uzun bir başlangıç süresine sahip olması ve bu nedenle her işlem için yeniden başlatılmak yerine işlemler arasında yeniden kullanılması gerektiği fikri yatar (Bu, Bazel'in çalışan sürecinin istekler arasında gözlemlenebilir durum taşımadığı sözüne dayanması nedeniyle olası bir doğruluk sorununu temsil eder).

Araç değişirse çalışan işleminin 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. Bu, işlemin hangi girişlerinin aracın bir bölümünü, hangilerinin ise girişleri temsil ettiğini bilmeye dayanır. Bu, İşlemi oluşturan kişi tarafından belirlenir: Spawn.getToolFiles() ve Spawn öğesinin runfiles'ı, aracın parçaları olarak kabul edilir.

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

  • İşlemleri çalıştırmaya yönelik çeşitli stratejiler hakkında bilgiyi burada bulabilirsiniz.
  • Hem yerel olarak hem de uzaktan bir işlem çalıştırarak hangisinin önce tamamlandığını görmek için kullandığımız dinamik strateji hakkında bilgiye buradan ulaşabilirsiniz.
  • Yerel olarak işlem gerçekleştirmenin ayrıntıları 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şlem sayısı, işleme göre değişir. Bir işlem ne kadar çok kaynak gerektiriyorsa yerel makinenin aşırı yüklenmesini önlemek için aynı anda o kadar az örnek çalıştırılmalıdır.

Bu, ResourceManager sınıfında uygulanır: Her işlem, ResourceSet örneği (CPU ve RAM) biçiminde gerektirdiği yerel kaynakların tahminiyle açıklama eklenmelidir. Ardından, işlem bağlamları yerel kaynak gerektiren bir işlem yaptığında ResourceManager.acquireResources() çağrısı yapılır ve gerekli kaynaklar kullanılabilir hale gelene kadar işlem engellenir.

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

Çıkış dizininin yapısı

Her eylem, çıkışlarını yerleştirdiği çıkış dizininde ayrı bir yer gerektirir. Türetilmiş öğelerin 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? Çakışan iki istenen özellik vardır:

  1. Aynı derlemede iki yapılandırma oluşabiliyorsa her ikisinin de aynı işlemin kendi sürümüne sahip olabilmesi için farklı dizinleri olmalıdır. Aksi takdirde, iki yapılandırma aynı çıktı dosyasını üreten bir işlemin komut satırı gibi konularda anlaşmazlığa düşerse Bazel hangi işlemi seçeceğini bilemez ("işlem çakışması").
  2. İki yapılandırma "kabaca" aynı şeyi temsil ediyorsa aynı ada sahip olmalıdır. Böylece, komut satırları eşleştiğinde birinde yürütülen işlemler diğerinde yeniden kullanılabilir. Örneğin, Java derleyicisindeki komut satırı seçeneklerinde yapılan değişiklikler, C++ derleme işlemlerinin yeniden çalıştırılmasına neden olmamalıdır.

Şimdiye kadar, yapılandırma kırpma sorununa benzeyen bu sorunu çözmek için ilkeli bir yöntem bulamadık. Seçenekler hakkında daha ayrıntılı bir açıklamayı burada bulabilirsiniz. Sorunlu olan başlıca alanlar, genellikle Bazel'e aşina olmayan yazarlar tarafından oluşturulan Starlark kuralları ve "aynı" çıktı dosyasını üretebilen öğelere başka bir boyut ekleyen yönlerdir.

Mevcut yaklaşımda, yapılandırma için yol segmenti <CPU>-<compilation mode>'dır. Java'da uygulanan yapılandırma geçişlerinin işlem çakışmasına neden olmaması için çeşitli sonekler eklenir. Ayrıca, kullanıcıların işlem çakışmasına neden olmaması için Starlark yapılandırma geçişleri kümesinin 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 bölümü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 (hataları düzeltmek veya zamanlama verilerini toplamak için)
  • Parçalama testleri (aynı testteki test durumlarını hız için birden fazla işlemde bölme)
  • Güvenilir olmayan testleri yeniden çalıştırma
  • Testleri test paketleri halinde gruplandırma

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

  • Derlenmesi testin çalıştırılmasına neden olan yapılar. Bu, seri hale getirilmiş bir TestResultData mesajı içeren bir "cache status" dosyasıdır.
  • Testin çalıştırılma sayısı
  • Testin bölüneceği parça sayısı
  • Testin nasıl çalıştırılması gerektiğiyle ilgili bazı parametreler (ör. test zaman aşımı)

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

Hangi testlerin çalıştırılacağını belirlemek karmaşık bir süreçtir.

İlk olarak, hedef kalıbı ayrıştırma sırasında test paketleri yinelemeli olarak genişletilir. Genişletme, TestsForTargetPatternFunction bölgesinde uygulanır. Biraz şaşırtıcı bir durum da test paketi hiçbir test içermediğini belirtirse paketindeki her testin kastedilmesidir. Bu, test paketi kurallarına $implicit_tests adlı örtülü bir özellik eklenerek Package.beforeBuild() içinde uygulanır.

Ardından, testler komut satırı seçeneklerine göre boyut, etiket, zaman aşımı ve dil için filtrelenir. Bu, TestFilter içinde uygulanır ve hedef ayrıştırma sırasında TargetPatternPhaseFunction.determineTests()'den çağrılır. Sonuç TargetPatternPhaseValue.getTestsToRunLabels()'ye yerleştirilir. Filtrelenebilen kural özelliklerinin yapılandırılamamasının nedeni, bu işlemin analiz aşamasından önce gerçekleşmesidir. Bu nedenle, yapılandırma kullanılamaz.

Bu işlem daha sonra BuildView.createResult()'da daha ayrıntılı olarak işlenir: Analizi başarısız olan hedefler filtrelenir ve testler, özel ve özel olmayan testler olarak ayrılır. Ardından AnalysisResult içine yerleştirilir. ExecutionTool hangi testlerin çalıştırılacağını bu şekilde bilir.

Bu ayrıntılı süreci biraz daha şeffaf hale getirmek için tests() sorgu operatörü (TestsFunction içinde uygulanır), komut satırında belirli bir hedef belirtildiğinde hangi testlerin çalıştırıldığını söylemek için kullanılabilir. Bu işlev, maalesef yeniden uygulanmıştır. Bu nedenle, yukarıdaki işlevden birçok küçük farklılık gösterebilir.

Testleri çalıştırma

Testler, önbellek durumu yapıları istenerek çalıştırılır. Bu işlem, TestRunnerAction komutunun yürütülmesine neden olur. Bu komut da sonunda testi istenen şekilde çalıştıran --test_strategy komut satırı seçeneği tarafından seçilen TestActionContext komutunu çağırır.

Testler, testlerden ne beklendiğini bildirmek için ortam değişkenlerini kullanan ayrıntılı bir protokole göre yürütülür. Bazel'in testlerden ve testlerin Bazel'den beklentilerinin ayrıntılı açıklaması burada yer almaktadır. En basit haliyle, çıkış kodu 0 ise işlem başarılı, başka bir kod ise başarısız demektir.

Her test süreci, önbellek durumu dosyasına ek olarak başka dosyalar da oluşturur. Bunlar, hedef yapılandırmanın çıkış dizininin testlogs adlı alt dizini olan "test log directory" (test günlüğü dizini) içine yerleştirilir:

  • test.xml, test parçası içindeki bağımsız test senaryolarını ayrıntılı olarak açıklayan JUnit tarzı bir XML dosyası
  • test.log, testin konsol çıkışı. stdout ve stderr ayrılmaz.
  • test.outputs, "bildirilmemiş çıkışlar dizini"; bu, terminale yazdırdıklarına ek olarak dosya çıkışı yapmak isteyen testler tarafından kullanılır.

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

Bazı testlerin özel modda (ör. diğer testlerle paralel olarak değil) yürütülmesi gerekir. Bu, test kuralına tags=["exclusive"] eklenerek veya testin --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ı tarafından çalıştırılır. Bu, SkyframeExecutor.runExclusiveTest() içinde uygulanır.

Normal işlemlerin aksine, işlem tamamlandığında terminal çıkışı boşaltılır. Kullanıcı, uzun süren bir testin ilerleme durumu hakkında bilgi sahibi olmak için testlerin çıkışının yayınlanmasını isteyebilir. Bu, --test_output=streamed komut satırı seçeneğiyle belirtilir ve farklı testlerin çıkışlarının birbirine karışmaması için özel test yürütmeyi ifade eder.

Bu, uygun şekilde adlandırılmış StreamedTestOutput sınıfında uygulanır ve söz konusu testin test.log dosyasındaki değişiklikleri yoklayarak ve Bazel kurallarının geçerli olduğu terminale yeni baytlar dökerek çalışır.

Yürütülen testlerin sonuçları, çeşitli etkinlikler (ör. TestAttempt, TestResult veya TestingCompleteEvent) gözlemlenerek etkinlik veri yolunda kullanılabilir. Bu sonuçlar, Build Event Protocol'e aktarılır ve AggregatingTestListener tarafından konsola gönderilir.

Kapsam toplama

Kapsam, LCOV biçimindeki testlerle dosyalarda bildirilir. bazel-testlogs/$PACKAGE/$TARGET/coverage.dat

Kapsamı toplamak için her test yürütme işlemi collect_coverage.sh adlı bir komut dosyasına sarılır .

Bu komut dosyası, kapsam toplama işlemini etkinleştirmek ve kapsam dosyalarının kapsam çalışma zamanları tarafından nereye yazılacağını belirlemek için test ortamını ayarlar. 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ış bölümlerden oluşabilir (ayrı kapsam toplama çalışma zamanlarıyla). Sarmalayıcı komut dosyası, gerekirse sonuç dosyalarını LCOV biçimine dönüştürmek ve bunları tek bir dosyada birleştirmekle sorumludur.

collect_coverage.sh yerleştirme işlemi test stratejileri tarafından yapılır ve test girişlerinde collect_coverage.sh bulunması gerekir. Bu, yapılandırma işareti --coverage_support değerine çözümlenen :coverage_support örtülü özelliğiyle gerçekleştirilir (bkz. TestConfiguration.TestOptions.coverageSupport).

Bazı dillerde çevrimdışı ölçüm yapılır. Bu, kapsam ölçümünün derleme zamanında (ör. C++) eklendiği anlamına gelir. Diğer dillerde ise çevrimiçi ölçüm yapılır. Bu da kapsam ölçümünün yürütme zamanında eklendiği anlamına gelir.

Diğer bir temel kavram ise temel kapsamdır. Bu, içinde kod çalıştırılmamışsa bir kitaplığın, ikili programın veya testin kapsamıdır. Bu araç, 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ğlanmamış kod olabilmesidir. Bu nedenle, yaptığımız işlem, yalnızca kapsam topladığımız dosyaları içeren ve kapsanan satır içermeyen her ikili için bir kapsam dosyası oluşturmaktır. Bir hedef için varsayılan temel kapsam dosyası bazel-testlogs/$PACKAGE/$TARGET/baseline_coverage.dat konumundadır ancak kuralların, kaynak dosyaların adlarından daha anlamlı içeriklere sahip kendi temel kapsam dosyalarını oluşturması önerilir.

Her kural için kapsam toplama amacıyla iki dosya grubu izleriz: enstrümanlı dosyalar kümesi ve enstrüman meta verileri dosyaları kümesi.

Araçlandırılmış dosyalar kümesi, yalnızca araçlandırılacak dosyalar kümesidir. Çevrimiçi kapsam çalışma zamanları için bu, hangi dosyaların kullanılacağına karar vermek üzere çalışma zamanında kullanılabilir. Temel kapsamı uygulamak için de kullanılır.

Enstrümantasyon meta veri dosyaları grubu, bir testin Bazel'in gerektirdiği LCOV dosyalarını oluşturmak için ihtiyaç duyduğu ek dosyalar grubudur. Uygulamada bu, çalışma zamanına özgü dosyalardan oluşur. Örneğin, gcc derleme sırasında .gcno dosyaları oluşturur. Bunlar, kapsam modu etkinse test işlemlerinin girişler kümesine eklenir.

Kapsamın toplanıp toplanmadığı bilgisi BuildConfiguration içinde saklanır. Bu, testi değiştirmenin kolay bir yolu olduğundan kullanışlıdır. Bu bit'e bağlı olarak işlem ve işlem grafiği, ancak bu bit çevrilirse tüm hedeflerin yeniden analiz edilmesi gerekir (C++ gibi bazı diller, kapsam toplayabilen kod yayınlamak için farklı derleyici seçenekleri gerektirir. Bu durumda, yeniden analiz yapılması gerektiğinden bu sorun bir ölçüde azaltılır).

Kapsam desteği dosyaları, Bazel'in farklı sürümleri arasında farklılık göstermelerine olanak tanıyan çağırma politikası tarafından geçersiz kılınabilmeleri için örtülü bir bağımlılıkta etiketler aracılığıyla bağımlı hale getirilir. İdeal olarak bu farklılıklar kaldırılır ve bunlardan biri standartlaştırılır.

Ayrıca, bir 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 gerçekleştirilir ve BuildView.createResult() üzerinden çağrılır . Çalıştırılan ilk testin :coverage_report_generator özelliğine bakarak ihtiyaç duyduğu araçlara erişir.

Sorgu motoru

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

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

Bunların her biri alt sınıf oluşturularak uygulanır AbstractBlazeQueryEnvironment. Ek sorgu işlevleri, QueryFunction alt sınıflandırılarak yapılabilir. . Sorgu sonuçlarının bir veri yapısında toplanması yerine akışına izin vermek için query2.engine.Callback, QueryFunction'ye iletilir. QueryFunction, döndürmek istediği sonuçlar için query2.engine.Callback'yı çağırır.

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

Bazı sorgu çıkışı biçimlerinin (kesinlikle proto) küçük bir gereksinimi, çıkışın karşılaştırılıp belirli bir hedefin değişip değişmediğinin belirlenebilmesi için Bazel'in paket yüklemenin sağladığı _tüm_ bilgileri yayınlamasıdır. Bu nedenle, özellik değerlerinin serileştirilebilir olması gerekir. Bu nedenle, karmaşık Starlark değerlerine sahip özellikler içermeyen özellik türlerinin sayısı çok azdır. Genellikle geçici çözüm olarak bir etiket kullanılır ve karmaşık bilgiler bu etikete sahip kurala eklenir. Bu, pek tatmin edici bir geçici çözüm değil ve bu şartın kaldırılması çok iyi olurdu.

Modül sistemi

Bazel'e modül ekleyerek kapsamını genişletebilirsiniz. Her modül, BlazeModule (adı, Bazel'in Blaze olarak adlandırıldığı geçmişinden kalmıştır) alt sınıfı olmalı ve bir komutun yürütülmesi sırasında çeşitli etkinliklerle ilgili bilgileri almalıdır.

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

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

BlazeModule teklifleri uzantı noktaları kümesi biraz düzensizdir. İyi tasarım ilkeleri örneği olarak kullanmayın.

Etkinlik veri yolu

BlazeModules'ün Bazel'in geri kalanıyla iletişim kurmasının temel yolu bir etkinlik veri yoludur (EventBus): Her derleme için yeni bir örnek oluşturulur, Bazel'in çeşitli bölümleri bu veri yoluna etkinlikler gönderebilir ve modüller, ilgilendikleri etkinlikler için dinleyiciler kaydedebilir. Örneğin, aşağıdakiler etkinlik olarak gösterilir:

  • Oluşturulacak derleme hedeflerinin listesi belirlendi (TargetParsingCompleteEvent)
  • Üst düzey yapılandırmalar belirlenmiştir (BuildConfigurationEvent)
  • Bir hedef oluşturuldu (başarılı veya başarısız) (TargetCompleteEvent)
  • Test çalıştırıldı (TestAttempt, TestSummary)

Bu etkinliklerin bazıları, Bazel dışında Build Event Protocol'de (BuildEvent) temsil edilir. Bu, yalnızca BlazeModule'nın değil, Bazel işlemi 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 (Build Event Service 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 monorepo'da (derleme için gereken her şeyi içeren tek bir kaynak ağacı) kullanılmak üzere tasarlanmış olsa da bu durumun her zaman geçerli olmadığı bir dünyada kullanılmaktadır. "Harici depolar", bu iki dünyayı bir araya getirmek için kullanılan bir soyutlamadır: Derleme için gerekli olan ancak ana kaynak ağacında bulunmayan kodu temsil ederler.

WORKSPACE dosyası

Harici depolar kümesi, WORKSPACE dosyası ayrıştırılarak belirlenir. Örneğin, şöyle bir beyan:

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

@foo adlı depoda sonuçlar kullanılabilir. Bu durumun karmaşık hale geldiği nokta, Starlark dosyalarında yeni depo kuralları tanımlanabilmesidir. Bu kurallar daha sonra yeni Starlark kodunu yüklemek için kullanılabilir. Bu kod da yeni depo kuralları tanımlamak için kullanılabilir ve bu şekilde devam eder.

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

Depoları getirme

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

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

  1. PackageLookupFunction bir depoya ihtiyacı olduğunu fark eder ve SkyKey olarak RepositoryName oluşturur. Bu işlem RepositoryLoaderFunction'ü çağırır.
  2. RepositoryLoaderFunction, isteği net olmayan nedenlerle RepositoryDelegatorFunction'ye yönlendiriyor (kod, Skyframe yeniden başlatıldığında öğelerin yeniden indirilmesini önlemek için yapıldığını söylüyor ancak bu pek sağlam bir gerekçe değil).
  3. RepositoryDelegatorFunction, istenen depo bulunana kadar WORKSPACE dosyasının parçalarını yineleyerek getirmesi istenen depo kuralını bulur.
  4. Uygun RepositoryFunction bulunur. Bu, Starlark'ın depo uygulaması veya Java'da uygulanan depolar için sabit kodlanmış bir haritadır.

Bir depoyu getirmek çok maliyetli olabileceğinden çeşitli önbelleğe alma katmanları vardır:

  1. İndirilen dosyalar için sağlama değerleriyle anahtarlanan bir önbellek vardır (RepositoryCache). Bu, sağlama değerinin WORKSPACE dosyasında kullanılabilir olmasını gerektirir ancak bu, her durumda hermetiklik için iyidir. Bu, aynı iş istasyonundaki her Bazel sunucu örneği tarafından, hangi çalışma alanında veya çıkış tabanında çalıştıklarına bakılmaksızın paylaşılır.
  2. $OUTPUT_BASE/external altında her depo için, getirmek üzere 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 özellik RepositoryDelegatorFunction.DigestWriter içinde uygulanır .
  3. --distdir komut satırı seçeneği, indirilecek yapıtları aramak için kullanılan başka bir önbelleği belirtir. Bu, Bazel'in internetten rastgele şeyler getirmemesi gereken kurumsal ayarlarda kullanışlıdır. Bu özellik, DownloadManager tarafından uygulanır .

Bir depo indirildikten sonra içindeki yapılar kaynak yapı olarak kabul edilir. Bazel genellikle kaynak yapıtların güncelliğini kontrol etmek için stat() işlevini çağırdığından ve bu yapıtlar bulundukları deponun tanımı değiştiğinde de geçersiz kılındığından bu durum sorun yaratır. Bu nedenle, FileStateValues, harici bir depodaki bir yapının harici deposuna bağlı olmalıdır. Bu işlem ExternalFilesHelper tarafından gerçekleştirilir.

Depo eşlemeleri

Birden fazla deponun aynı depoya farklı sürümlerde bağımlı olması mümkündür (bu, "elmas bağımlılığı sorununun" bir örneğidir). Örneğin, derlemede ayrı depolardaki iki ikili dosya Guava'ya bağlı olmak istiyorsa muhtemelen her ikisi de @guava// ile başlayan etiketlerle Guava'ya başvuracak ve bunun farklı sürümler anlamına gelmesini bekleyecektir.

Bu nedenle Bazel, harici depo etiketlerinin yeniden eşlenmesine olanak tanır. Böylece @guava// dizesi, bir ikilinin deposunda bir Guava deposuna (ör. @guava1//), diğerinin deposunda ise başka bir Guava deposuna (ör. @guava2//) başvurabilir.

Alternatif olarak, bu araç elmasları birleştirmek için de kullanılabilir. Bir depo @guava1//'ya, başka bir depo ise @guava2//'ya bağlıysa depo eşleme, her iki deponun da standart bir @guava// deposunu kullanacak şekilde 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. Daha sonra Skyframe'de WorkspaceFileValue üyesi olarak görünür ve şu işlemlere yönlendirilir:

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

JNI bitleri

Bazel'in sunucusu çoğunlukla Java ile yazılmıştır. Java'nın tek başına yapamadığı veya uyguladığımız sırada tek başına yapamadığı kısımlar bu durumun istisnasıdır. Bu durum, çoğunlukla dosya sistemi, işlem kontrolü ve çeşitli diğer düşük düzeyli işlemlerle etkileşimle sınırlıdır.

C++ kodu src/main/native altında bulunur ve yerel yöntemler içeren Java sınıfları şunlardır:

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

Konsol çıktısı

Konsol çıkışı verme basit bir işlem gibi görünse de birden fazla işlemi (bazen uzaktan) çalıştırma, ayrıntılı önbelleğe alma, güzel ve renkli bir terminal çıkışı elde etme isteği ve uzun süredir çalışan bir sunucuya sahip olma gibi faktörlerin birleşimi bu işlemi zorlaştırır.

RPC çağrısı istemciden geldikten hemen sonra, RpcOutputStream iki örnek oluşturulur (stdout ve stderr için). Bu örnekler, yazdırılan verileri istemciye yönlendirir. Bunlar daha sonra OutErr (bir (stdout, stderr) çifti) içine alınır. Konsola yazdırılması gereken her şey bu akışlardan geçer. Ardından bu yayınlar BlazeCommandDispatcher.execExclusively() adresine teslim edilir.

Çıkış, varsayılan olarak ANSI kod dışı bırakma sıralarıyla yazdırılır. İstenmediğinde (--color=no) 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 yine de istemcinin terminal çıkışında (sunucunun terminal çıkışından farklıdır) yer alabilir. Bir işlem ikili çıkış ürettiğinde (ör. bazel query --output=proto), stdout'un değiştirilmemesine dikkat edilir.

Kısa mesajlar (hatalar, uyarılar vb.) EventHandler arayüzü üzerinden ifade edilir. Bunların, EventBus'ya gönderilenlerden farklı olduğunu unutmayın (bu kafa karıştırıcı olabilir). Her Event öğesinin bir EventKind (hata, uyarı, bilgi ve birkaç başka) öğesi vardır ve Location (olayın gerçekleşmesine neden olan kaynak kodundaki yer) öğesi olabilir.

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

Bazı EventHandler'lar, sonunda etkinlik veri yoluna ulaşan etkinliklerin yayınlanmasına da izin verir (normal Event'lar burada _görünmez_). Bunlar ExtendedEventHandler uygulamalarıdır ve temel olarak önbelleğe alınmış EventBus etkinliklerini yeniden oynatmak için kullanılır. Bu EventBus etkinliklerinin tümünde Postable uygulanır ancak EventBus'ya gönderilen her şeyde bu arayüz uygulanmaz. Yalnızca bir ExtendedEventHandler tarafından önbelleğe alınanlar uygulanır (Bu iyi olur ve çoğu şeyde uygulanır ancak zorunlu değildir).

Terminal çıkışı çoğunlukla UiEventHandler üzerinden verilir. Bu, Bazel'in yaptığı tüm gelişmiş çıkış biçimlendirmesinden ve ilerleme durumu raporlamasından sorumludur. İki girişi vardır:

  • Etkinlik veri yolu
  • Reporter aracılığıyla içine aktarılan etkinlik akışı

Komut yürütme mekanizmasının (ör. Bazel'in geri kalanı) istemciye RPC akışıyla kurduğu tek doğrudan bağlantı, bu akışlara doğrudan erişime izin veren Reporter.getOutErr() üzerinden gerçekleşir. Yalnızca bir komutun büyük miktarda olası ikili veriyi (ör. bazel query) dökmesi gerektiğinde kullanılır.

Bazel'de profil oluşturma

Bazel hızlıdır. Bazel de yavaştır. Bunun nedeni, derlemelerin dayanılabilirliğin sınırına kadar büyüme eğiliminde olmasıdır. Bu nedenle Bazel, derlemelerin ve Bazel'in kendisinin profilini oluşturmak için kullanılabilecek bir profiler içerir. Profiler adlı sınıfta uygulanır. Varsayılan olarak açıktır ancak ek yükü kabul edilebilir düzeyde olması için yalnızca kısaltılmış verileri kaydeder. Komut satırı --record_full_profiler_data, her şeyi kaydetmesini sağlar.

Chrome profiler biçiminde bir profil oluşturur. En iyi görüntüleme deneyimi için Chrome'da görüntülenmesi önerilir. Veri modeli, görev yığınları şeklindedir: Görevlere başlanabilir ve görevler sonlandırılabilir. Bu görevlerin birbirine düzgün bir şekilde yerleştirilmesi gerekir. Her Java iş parçacığı kendi görev yığınına sahiptir. YAPILACAKLAR: Bu özellik, işlemler ve devamlılık aktarma stiliyle nasıl çalışır?

Profiler, sırasıyla BlazeRuntime.initProfiler() ve BlazeRuntime.afterCommand() ile başlatılır ve durdurulur. Her şeyi profillendirebilmemiz için mümkün olduğunca uzun süre canlı kalmaya çalışır. Profile bir şey eklemek için Profiler.instance().profile() işlevini çağırın. Bu işlev, kapanışı görevin sonunu temsil eden bir Closeable döndürür. En iyi performansı, kaynaklarla deneme ifadeleriyle birlikte kullanıldığında gösterir.

Ayrıca MemoryProfiler'da temel bellek profili oluşturma işlemi 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'de iki ana test türü vardır: Bazel'i "kara kutu" olarak gözlemleyen testler ve yalnızca analiz aşamasını çalıştıran testler. Birincisine "entegrasyon testleri", ikincisine ise "birim testleri" diyoruz. Ancak bunlar, daha az entegre olan entegrasyon testlerine daha çok benziyor. Ayrıca, gerekli olduğu durumlarda bazı gerçek birim testlerimiz de vardır.

İki tür entegrasyon testi 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 alt sınıfları olarak uygulanır.

BuildIntegrationTestCase, çoğu test senaryosuna uygun olduğundan tercih edilen entegrasyon testi çerçevesidir. Java çerçevesi olduğundan hata ayıklama olanağı sunar ve yaygın olarak kullanılan birçok geliştirme aracıyla sorunsuz bir şekilde entegre olur. Bazel deposunda BuildIntegrationTestCase sınıflarıyla ilgili birçok örnek vardır.

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